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