背景

开发测试中需要执行一些程序,每次手动启动耗时又耗力。如果有工具可以UI启停,监测进程,可以节省大量时间,是一个很好的辅助作用。

简述

Shelljs是Node.js下的脚本语言解析器,具有丰富且强大的底层操作(Windows/Linux/OS X)权限。Shelljs本质就是基于node的一层命令封装插件,让前端开发者可以不依赖linux也不依赖类似于cmder的转换工具,而是直接在我们最熟悉不过的javascript代码中编写shell命令实现功能。
即:shelljs做的事就是自动化,从耗时的重复性常规动作里解放出来,提升开发效率和工作心情。

方案对比

自动化脚手架本质上是一段基于某种特定的脚本语言编写的简单程序。执行该程序可以按照工程的骨架结构,代码规范,以及一些组件模板,创建指定新模块或者子工程的代码文件以及填充一些代码片段。理论来说,可以使用任意一种可以调用操作系统API的语言实现,比如C, Java, Bash, Node.js等。考虑到使用的场景,我们需要更加轻便灵巧的方式达到这一目的,那么shell和Node.js是比较好的选择。
我们对这两种方式的实现做了一个对比:

对比项 bash Node.js
上手难度(对于前端) 稍难 容易
与前端工程的融合性 一般 很好(本质上使用Javascript语言实现)
环境兼容性 Unix / Linux下原生支持,Window平台下需要安装特定的bash环境 所有平台原生并不支持,但在装有Node后可以跨平台使用

基于以上对比,我们发现在前端工程中,使用Node.js作为项目脚手架的开发语言,要比使用bash更有优势。

shelljs常用命令

基本使用
  //引入shelljs
  var shell = require('shelljs')

  //检查控制台是否以运行`git `开头的命令
  if (!shell.which('git')) {
    //在控制台输出内容
    shell.echo('Sorry, this script requires git');
    shell.exit(1);
  }

  shell.rm('-rf','test/baishu');//强制递归删除`test/baishu目录`
  shell.cp('-R','stuff/','test/baishu');//将`stuff/`中所有内容拷贝至`test/baishu`目录

  shell.cd('lib');//进入`lib`目录
  //找出所有的扩展名为js的文件,并遍历进行操作
  shell.ls('*.js').forEach(function (file) {
    /* 这是第一个难点:sed流编辑器,建议专题学习,-i表示直接作用源文件 */
    //将build_version字段替换为'v0.1.2'
    shell.sed('-i', 'BUILD_VERSION', 'v0.1.2', file);
    //将包含`REMOVE_THIS_LINE`字符串的行删除
    shell.sed('-i', /^.*REMOVE_THIS_LINE.*$/, '', file);
    //将包含`REPLACE_LINE_WITH_MACRO`字符串的行替换为`macro.js`中的内容
    shell.sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, shell.cat('macro.js'), file);
  });

  //返回上一级目录
  shell.cd('..');

  //run external tool synchronously
  //即同步运行外部工具
  if (shell.exec('git commit -am "Auto-commit"').code !== 0){
      shell.echo('Error: Git commit failed');
      shell.exit(1);
  }
shell.which(command)
  在环境变量PATH中寻找指定命令的地址,判断该命令是否可执行,返回该命令的绝对地址
echo
  在控制台输出指定内容
  .to(index.txt)写入文件
exit(code)
  以退出码为code退出当前进程
rm([options,] file [,file …])
  删除一个目录中一个或多个文件或目录,一旦删除,无法恢复。

  常用参数:

      1. -f:强制删除文件;
      2. -i:删除之前先询问用户;
      3. -r:递归处理目录;
      4. -v:显示处理过程;

  e.g:  shell.rm('-rf', test);
  
cp([options,] source_array, dest)
  cp('-R','index.txt', '~/newCopy/') 
  cp('-R',['index.txt', 'old.txt'], '~/newCopy/')

  用来将一个或多个源文件或目录复制到指定的文件或目录。

  常用参数:

       1. -f:force (default behavior)
       2. -L: follow symlinks
       3. -r,-R: recursive
       4. -n: no-clobber
       5. -u: only copy if source is newer than dest
       6. -P: don't follow symlinks
cd
  切换工作目录至指定的相对路径或绝对路径。cd..为返回上一级,cd-回到前一目录。
ls
  用来显示目标列表。

  常用参数:
  
        1.-a:显示所有文件;
        2.-C:多列显示查询结果;
        3.-l:单列长格式显示查询结果(与-C相反);
        4.-R:递归处理目录;
        5.-A:所有文件(包括开头的文件.,除了.和..)
        6.-d:列出目录本身,而不是其内容

  ls(path.join('bundle', 'css/')).forEach(cssName => {
    ***
  })
sed([options,] search_regex, replacement, file_array
  将file_array中符合search_regex的内容替换为replacement,支持正则的捕获组自引用。一次处理一行内容,处理完成后把缓冲区内容送往屏幕,然后处理下一行,循环直至结束。

  常用参数:

        1.-i:直接作用源文件
cat([options,] file [, file …])
  将一个或多个文件内容读入,指定一个文件时读入该文件,指定多个文件时将内容连接在一起读入。

  可用选项:
        1. -n: number all output lines
exec(command [, options] [, callback])
  执行所传入的命令

  1.async:是否异步执行,默认false,传入callback时自动开启
  2.slient:不输出信息到console,默认false
  3.encoding:默认utf8

除非另有说明,否则command 同步执行给定的给定。在同步
模式下,它返回一个ShellString(与ShellJS v0.6.x兼容,返回
表单的对象{ code:…, stdout:… , stderr:… })。否则,这将返回子进程
对象,并callback获取参数(code, stdout, stderr)。
注意:对于长期存在的进程,最好以exec()异步方式运行

chmod
  设置文件调用权限

  1.-c:output a diagnostic for every file processed
  2.-v: like verbose, but report only when a change is made
  3.-R: change files and directories recursively

  u表示该文件拥有者,g表示同一群体者,o表示其他,a表示所有
  +表示增加权限,-表示取消权限,=表示唯一设定权限
  r表示可读,w表示可写,x表示可执行,X表示当该文件是个子目录

  chmod(755, '/Users/test');
  chmod('755', '/Users/test'); // same as above
  chmod('u+x', '/Users/test');
  chmod('-R', 'a-w', '/Users/test');
pushd([options,] [dir |’-N’|’+ N’])
  可用选项
  -n:在向堆栈添加目录时禁止正常更改目录,以便仅操作堆栈

  参数
  1.dir:使当前工作目录成为堆栈的顶部,然后执行等效的cd dir
  2.+N:通过旋转堆栈将第N个目录(从dirs打印的列表的左侧开始,从零开始)到列表的顶部
  3.-N:通过旋转堆栈将第N个目录(从dirs打印的列表右侧开始,从零开始计数)到列表顶部
popd([options,] [‘ - N’|’+ N’])
  可用选项:
  -n:从堆栈中删除目录时禁止正常更改目录,以便仅操作堆栈

  参数:
  1.+N:删除第N个目录(从dirs打印的列表左侧开始计算),从零开始
  2.-N:删除第N个目录(从dirs打印的列表右侧开始计算),从零开始。

  echo(process.cwd()); // '/usr'
  pushd('/etc');       // '/etc /usr'
  echo(process.cwd()); // '/etc'
  popd();              // '/usr'
  echo(process.cwd()); // '/usr'

  如果没有给出参数,popd将从堆栈中删除顶级目录并执行cd到新的顶级目录。从dirs列出的第一个目录开始,元素从0开始编号; 即,popd相当于popd +0。返回堆栈中的路径数组
dirs([options |’+ N’|’-N’]
  可用选项:
  -c:通过删除所有元素清除目录堆栈

  参数:
  +N:显示第N个目录(从没有选项调用时由dirs打印的列表左侧开始计数),从零开始
  -N:显示第N个目录(从没有选项调用时由dirs打印的列表右侧开始计数),从零开始

  显示当前记住的目录列表。返回堆栈中的路径数组,如果指定了+ N或-N,则返回单个路径。
find(path [,path …])
  查找文件

  console.log(find('../etc/host'))

  返回path_array
grep([options,] regex_filter,file [,file …])
  不同于find查找文件,grep用于查找内容

  可用选项:

  1.-v:反转正则表达式的意义并打印不符合条件的行
  2.-l:仅打印匹配文件的文件名

  grep('-v', 'GLOBAL_VARIABLE', '*.js');
  grep('GLOBAL_VARIABLE', '*.js');

  从给定文件中读取输入字符串,并返回一个字符串,其中包含 与给定文件匹配的文件的所有行regex_filter
head([{‘ - n’:},] file [,file …])
  读取文件的开头

  可用选项:
  -n :显示文件的第一行

  console.log(head('test.js'))  
  console.log(head({'-n':1},'test.js'))  // 获取第一行
tail([{‘ - n’:},] file [,file …])
  读取文件的结尾

  可用选项: -n :显示文件的最后几行

  var str = tail({'-n': 1}, 'file*.txt');
  var str = tail('file1', 'file2');
  var str = tail(['file1', 'file2']); // same as above
ln([options,] source,dest)
  创建链接

  ln('file', 'newlink');  // /Users.../newlink
  ln('-sf', 'file', 'newlink');  //如果newlink存在,则强制链接🔗
mkdir([options,] dir [,dir …])
  创建文件夹

  可用选项:
  1.-p:完整路径(如有必要,将创建中间目录

  shell.mkdir('bundle')
  shell.mkdir('-p', ['bundle', 'js'])
touch([options,] file [,file …])
  可用选项:
  1.-a:仅更改访问时间
  2.-c:不要创建任何文件
  3.-m:仅更改修改时间
  4.-d DATE:指定时间
  5.-r FILE:用FILE的时间替代新文件时间
mv([options,] source [,source …],dest’)
  移动
  可用选项:
  1.-f:force(默认行为)
  2.-n:no-clobber

  mv('a1', 'a2');  // 将a1文件移动到a2文件夹
PWD()
  查看当前目录
set(选项)
  设置全局配置变量
  可用选项:

  +/-e:出错时退出(config.fatal)
  +/-v:verbose:show all commands(config.verbose)
  +/-f:禁用文件名扩展(globbing)

  set('-e'); // exit upon first error
  set('+e'); // this undoes a "set('-e')"
sort([options,] file [,file …])
  内容排序
  可用选项:

  1.-r:反转比较结果
  2.-n:根据数值比较

  sort('foo.txt', 'bar.txt');
  sort('-r', 'foo.txt');

  返回文件的内容,逐行排序。排序多个
test()
  文件类型判断

  可用选项:
  1.'-b', 'path':如果path是块设备,则为true
  2.'-c', 'path':如果path是字符设备,则为true
  3.'-d', 'path':如果path是目录,则为true
  4.'-e', 'path':如果路径存在,则为true
  5.'-f', 'path':如果path是常规文件,则为true
  6.'-L', 'path':如果path是符号链接,则为true
  7.'-p', 'path':如果path是管道(FIFO),则为true
  8.'-S', 'path':如果path是套接字,则为true

  if (!test('-f', path)) continue;
uniq([options,] [input,[output]])
  可用选项:

  1.-i:比较时忽略大小写
  2.-c:按出现次数排列前缀
  3.-d:仅打印重复的行,每行对应一行
ShellString()
  构造器,将一个字符串转化为Shell字符串,转化后的字符串支持链式调用特殊的shell命令

  ShellString('hello world')
ShellString.Prototype.to()
  将shellString输出至指定文件,相当于脚本语言中的>
ShellString.Prototype.toEnd()
  将shellString追加至指定文件,相当于脚本语言中的>>
env[‘VAR_NAME’]
  指向process.env
Pipes链式调用支持
  sed,grep,cat,exec,to,toEnd均支持链式调用
Configuration
  1.config.silent sh.config.silent
  2.config.fatal config.fatal
  3.config.verbose config.verbose
  4.config.globOptions config.globOptions
  5.config.reset()重置全局 shell.config.reset()