脚本

Updated at 2017.10.10, 2442 words, 11 mins
Table of Contents

脚本(script) 原指拍摄电影或者戏剧时,演员依据其对白演出的文件,也称之为剧本。 在编程的世界里,有时需要重复执行某些指令,这些指令或简或繁,然而反复做重复的事情是令人烦恼的。 有句话是这样说的,人类所有的发明推进都是因为人类的懒惰,因为人们懒得走路,我们发明了自行车,因为懒得骑自行车,所以我们发明了汽车, 因为汽车太慢,所以我们发明了高铁、飞机等,种种都是因为人们追求 higher、faster、stronger。 脚本亦是如此,因为程序员懒得自己敲某些重复的代码,因此我们把这些重复的常用到的指令放在一起,当我们需要的时候,直接运行这些指令即可。 参照 “脚本” 的原意,让计算机按照我们写好的指令去执行或者 “演出” 的文件,我们称之脚本文件,生成脚本文件的语言我们称之为脚本语言(Scripting language)。

一个脚本通常是解释运行而非编译,脚本语言通常都有简单、易学、易用的特性,目的就是希望能让程序员快速完成程序的编写工作。 而宏语言则可视为脚本语言的分支,两者也有实质上的相同之处。虽然许多脚本语言都超越了计算机简单任务自动化的领域,不仅是大量重复的简单指令, 有些时候我们可以使用脚本编写更为复杂精巧的程序,但仍然还是被称为脚本。几乎所有计算机系统的各个层次都有一种脚本语言。 包括操作系统层,如计算机游戏,网络应用程序,字处理文档,网络软件等。在许多方面,高级编程语言和脚本语言之间互相交叉,二者之间没有明确的界限。

常见的脚本语言有多种,比如 C Shell、Python、PHP、VBScript、Ruby 等,还有前端的核心之一 JavaScript。

Create your first script file

找到一个空的目录,在该目录下打开终端键入 touch myshell.sh 创建我们的第一个脚本文件,以 .sh 结尾的作用是让计算机方便的认出该文件“可能”是一个脚本文件, 会以默认的执行程序去运行该文件而已,否则后缀没有任何意义,举个例子,你可以用记事本去打开后缀为.html的文件进行编辑,可见后缀名没有对你的操作有丝毫影响。 创建成功后键入 vim myshell.sh 编辑新建的脚本文件。键入如下命令:

mkdir  ./demo  ./demo/css  ./demo/js
touch  ./demo/index.html  ./demo/css/style.css  ./demo/js/main.js

完成以上步骤之后退出编辑,在终端键入 ls -l 发现 demo.sh 文件前面的一串字符为 -rw-r--r--,末尾的字符为 - 说明该文件不具备执行权限。 在终端键入 chmod +x demo.sh 为该文件添加执行权限,chmod 意思为 change mode,+x 意思为添加执行权限 (读取权限缩写为 r,写入权限的缩写为 w,执行权限的缩写为 x)。执行完成再运行 ls -l 发现 demo.sh 前面的字符变为 -rwxr-xr-x, 说明该文件已具有执行权限。

找到计算机上的 .bashrc 文件,如果不存在该文件新建即可。编辑 .bashrc 文件,在末尾换行键入 export PATH="<file_path>$PATH", 该指令将 demo.sh 文件的绝对路劲添加至环境变量 $PATH 列表之中,其中 <file_path>demo.sh 文件的绝对路径 (添加至环境变量 $PATH 之后即可在终端中直接执行命令 demo.sh,bash 会在 PATH 列表中查找该“指令(文件)”的地址, 如果存在则执行该文件,不存在则报错,可在终端键入 which demo.sh 查看该“指令(文件)”的位置。完成以上步骤之后, 执行 source ~/.bashrc,重新载入 .bashrc。至此,脚本文件的创建已初步完成,在终端键入 demo.sh 后会在当前目录下创建一个名为 demo 的目录, 该目录下有名为 cssjs 的目录以及名为 index.html 的文件,子目录下分别有名为 style.cssmain.js 的文件。

Custom directory name

进一步优化,可以实现目录名称自定义,bash 脚本给我们提供了参数列表,其中 $0 表示命令行本身,$1 表示第一个参数,$2 表示第二个参数,以此类推。 接下来优化我们的脚本,修改 demo 文件内容如下:

mkdir  ./$1  ./$1/css  ./$1/js
touch  ./$1/index.html  ./$1/css/style.css  ./$1/js/main.js

编辑完成保存并退出,在终端键入 demo test 即可在当前目录下创建名为 test 的目录,该目录下有名为 cssjs 的目录以及名为 index.html 的文件, 子目录下分别有名为 style.cssmain.js 的文件。运行 demo 命令时后面不带参数则会报错。

Avoid repeative file name

文件重名会怎样呢?会不会覆盖掉已经存在的目录呢?如果会覆盖的话,那么之前目录下的文件会不会丢失?我们需要在该脚本新建目录之前进行判断:

// pseudo code
if (is_dir_exist) {
  // directory already exist
  // return
} else {
  // mkdir $1
  // return
}

继续修改我们的脚本文件 vim demo,因为我们的脚本是 shell 脚本,因此需要遵循 shell 语法:

if [ -d $1 ]; then
  echo  "$1 already existed!"
  exit
else
  mkdir ./$1 ./$1/css ./$1/js
  touch ./$1/index.html ./$1/css/style.css ./$1/js/main.js
  echo "$1 create successful!"
  exit
fi

编辑完成保存并退出。至此,我们的 demo 脚本文件已经成型了,如果需要同时往文件中写入字符,那么在脚本文件中使用 echo 指令即可。

Node.js script file

使用 Node.js 编写脚本必然需要遵循 Node.js 的语法规则,新建名为 script 的文件,写入以下内容:

var fs = require('fs');
var dirName = process.argv[2];
fs.exists(dirName, function(exists) {
  if (exists) {
    console.log(dirName + ' already existed!');
    return;
  } else {
    console.log(dirName + ' is creating...');
    fs.mkdirSync(dirName);
    process.chdir('./' + dirName);
    fs.mkdirSync('css');
    fs.mkdirSync('js');
    fs.writeFileSync('./index.html', "<h1>Hello, I'm Captain!</h1>");
    fs.writeFileSync('./css/style.css', 'h1{ color: red; }');
    fs.writeFileSync('./js/main.js', "alert('Surprise!');");
    console.log(dirName + ' create successful!');
  }
});

以上内容涉及 Node.js 知识,其中第一行指令调用 file system 接口,然后将第三个参数传递给dirName变量, 之后调用 fs.exists() 方法,该方法可以判断一个文件是否存在,判断流程与之前思路一致。编辑完成后保存并退出, 在终端键入 node script node-script 即可在当前目录下创建名为 node-script 的目录,其中内容与运行脚本文件 demo 后所生成目录中的内容一致。

Shebang ‘#!’

Shebang 的名字来自于 SHArpbang,或 Hash bang 的缩写,Shebang 是指代 #! 两个符号的典型 Unix 名称。 Unix 术语中,# 号通常称为 sharphashmesh,而 ! 号则常常称为 bang。 也有看法认为,Shebang 名字中的 sh 来自于默认 shell Bourne shell 的名称 sh#! 出现在文件的第一行的前两个字符,在文件中存在 Shebang 的情况下,类 Unix 操作系统的程序载入器会分析 Shebang 后的内容, 将这些内容作为解释器指令,并调用该指令,并将载有 Shebang 的文件路径作为该解释器的参数。例如在 script 脚本的前面添加下面的 shebang

#!/usr/bin/env node

那么解释器就会分析 #! 后面的路径并按照指定的解释程序 node 去执行文件。如果该文件没有执行权限的话,那么会报错 permission denied, 如果指定的解释程序不存在,那么会报错 No such file or directory#! 之后的解释程序,需要写其绝对路径, 它是不会自动到 PATH 列表中寻找解释器(或者执行文件)的。

当然,如果你使用 sh script node-script 这样显式的指明解释器来执行脚本,那么 #! 这一行将会被忽略掉,解释器当然是用命令行中显式指定的 sh 去执行文件。

完。