Lec 02: Shell as a script and a tool
0. 概览
shell 脚本是一种比较复杂的工具,可以用来优化创建命令/执行/读取的过程。这样比对应 c++ 程序要简单/高效得多。
1. Shell 脚本
1.1 变量
如果要给变量赋值,我们可以使用 foo=bar,如果要访问变量的值,我们要使用 $foo 来访问。
值得注意的是,用 ‘ 和 “ 包括字符串的意义并不相同。前者不会转义,后者则会转义。
1  | foo=bar  | 
1.2 脚本
举个例子,如果我希望评测 code.cpp 中的代码,就可以这样写一个 judge 文件:
1  | 
  | 
第一行是为了告诉系统这是一个 bash 脚本,这样系统就会依次执行后面的代码。
下面我们写一个跑很多测试点的脚本:
1  | 
  | 
1.3 参数
bash 有很多特殊的变量来表示参数、错误代码和相关变量。下面是一些例子:
$0-脚本名称$1-脚本的第一个参数$@-脚本的所有参数$#-参数个数$$-当前进程识别码$?-前一条指令的返回值!!-完整的上一条指令
所有的非 0 返回值都代表运行时有错误,例如程序 false。
1.4 替换
另一个常见的模式是以变量的形式获取一个命令的输出,这可以通过 命令替换(command substitution)实现。
当您通过 $( CMD ) 这样的方式来执行 CMD 这个命令时,它的输出结果会替换掉 $( CMD ) 。
例如,如果执行 for file in $(ls) ,shell 首先将调用 ls ,然后遍历得到的这些返回值。
还有一个冷门的类似特性是 进程替换(process substitution), <( CMD ) 会执行 CMD 并将结果输出到一个临时文件中,并将 <( CMD ) 替换成临时文件名。这在我们希望返回值通过文件而不是STDIN传递时很有用。例如, diff <(ls foo) <(ls bar) 会显示文件夹 foo 和 bar 中文件的区别。
1.5 综合的例子
这段脚本会遍历我们提供的参数,使用 grep 搜索字符串 foobar,如果没有找到,则将其作为注释追加到文件中。
1  | 
  | 
感觉还是容易看懂的。需要注意的事情是比较操作最好用 [[]] 包括,这样会降低犯错的几率。
1.6 glob
bash 允许我们基于文件拓展名展开表达式。
- 例如我可以用 
rm test/*.v来删除 test 目录下的所有 verilog 源文件 - 又比如我可以通过 
{}来展示一些有公共子串的输入 
1  | convert image.{png,jpg}  | 
2. Shell 作为一种工具
2.1 查找文件
find 是一种绝佳的查找工具(但是很慢)。它会递归地搜索符合条件的文件:
1  | # 查找所有名称为src的文件夹  | 
除了单纯的查找,我们还能对查找到的文件进行操作。这是通过 -exec 简述实现的。例如:
1  | # 删除全部扩展名为.tmp 的文件  | 
2.2 查找代码
grep 指令是一个非常好的工具。其中有很多有用的参数:
-C: 获取查找结果的上下午,例如grep -C 10就是显示上下十行-v选出不匹配的结果-R递归进行子目录
它也有一些替代品,例如 rg,你可以通过 sudo apt install ripgrep 来安装之。下面是一些使用的例子:
1  | brucelee@invo1ution:~/Interest/Missing-Semester/2-Shell_script$ rg -t md "#" // 查找所有用了 '#' 的 .md 文件  | 
2.3 查找 shell 命令
经典 history | grep find。这在编译器反复配 ravel 环境的时候帮了大忙。
同时,你也可以使用 Ctrl + R 来回溯,并输入字串进行匹配。
你可以修改 shell history 的行为,例如,如果在命令的开头加上一个空格,它就不会被加进 shell 记录中。当你输入包含密码或是其他敏感信息的命令时会用到这一特性。 为此你需要在 .bashrc 中添加 HISTCONTROL=ignorespace 或者向 .zshrc 添加 setopt HIST_IGNORE_SPACE。 如果你不小心忘了在前面加空格,可以通过编辑 .bash_history 或 .zhistory 来手动地从历史记录中移除那一项。
2.4 文件夹导航
fasd 工具可以帮助我们根据日常习惯来访问经常访问的目录。具体细节可以查看该仓库的 README.md