Advanced Shell
基础知识 - 变量和函数
Linux 下的 Shell 有很多, sh, bash, csh, zsh 等, 这里主要记录一下 bash 和 zsh 的一些不同之处
Parameter Expansion
Use an alternate value
1 | ${var:+WORD} |
如果 var
没有设置或为空,则这个变量展开为 Nothing (注意:不是空 empty, 是 nothing), 如果被设置了(不包括被设置成空),它展开为 +
后面的 WORD.
如果冒号被省略,则 var
即使被设置为空,它也展开为 +
后面的 WORD
Indirect
1 | ${!var} |
如果 var
的值是 MESA_DEBUG
, 那么这个形式展开后是变量 MESA_DEBUG
的值,例如 export MESA_DEBUG=1
, var=MESA_DEBUG
, 则最后的展开结果是 1
Quoted
1 | ${var@Q} |
带 @Q
指变量展开后的值被单引号引起来,例如 export ABC=abc
, echo "ABC=${ABC@Q}"
的结果是 ABC='abc'
反斜杠 backslash \
\
在 shell 中是用来转义字符的,就是说 echo "\\\\"
显示的实际只有一个 \
, 而且 while read var
时要注意加 -r
选项,读入原始字串
经典命令
exec
exec
命令有 2 个特点:
- 直接覆盖当前进程,就是说进程 PID 不变,但执行的代码被更换了
- 原来的 shell 环境被销毁,这样当前的代码结束后,也就不会返回原 shell(没得返回), 直接退出, 所以循环中慎用 exec
- 可以把它想像成系统调用
execve()
下面是 linux kernel 安装 bzImage 的一段代码,其中就使用了 exec
, 保证列出的 4 个安装脚本中,只执行第 1 个存在的,不会重复安装
1 | User/arch may have a custom install script |
timeout
timeout
命令用来给一个 COMMAND 设定一个 DURATION, 这在自动化中很有用,比如
1 | for i in `fd --type x`; do timeout -k 0.1 10 ./$i; done |
假如当前在 VulkanExamples 的 bin 目录下,上面的命令表示让每个 demo 执行 10s 后结束(timeout
给它发 TERM
信号), 如果发了 TERM
信号后,又经过 0.1s (-k 0.1
),这个 demo 还未能退出,就再发 KILL
信号,强制结束它
tree
tree
像一个简单的文件浏览器,但它并不是 shell 内置的命令,apt install tree
或 pacman -S tree
都可以安装。有时一个目录中包含太多的文件,tree 的默认输出就不太好浏览,这时可以只打印目录,并限制搜索深度
1 | tree -L 2 -d |
crontab
crontab -e
(添加定时任务), 不光可以添加周期性的定时任务,也可以添加开机时一次性任务
1 | @reboot /home/luc/mystart.sh |
crontab 是每用户的, 就是说当前用户设定的任务,只有当前用户的权限,所以如果有些情况下任务执行需要 root 权限,就需要切换到 root 用户后 crontab -e
文本处理 - awk, sed, grep(rg) 三剑客
Linux 下的文本处理三剑客: grep, sed, awk, 除了它们其实还有一些小巧的命令,如 tr
, cut
也可以帮助我们快速处理和格式化文本。
下面以一个例子为例。
awk
1 | awk 'program' inputfile1 inputfile2 ... |
awk 的调用形式就是上面这样的,而其中的 program 由若干条 rules 组成,而一条 rule 由一个 pattern 和一个 action 组成
1 | pattern { action } |
sed
sed 和 awk 一样,都是按行处理文本的。
sed -n '2 {s/^/#/; p; q}' file
- sed 默认会将每一行都打印出来,
-n
取消这一行为 - sed 可以在操作的前面指定位置和范围, 如
- 行号
- 正则表达式
/^foo/
- 两个正则表达式锁定范围
/^foo/, /bar$/
- 如果
-n
后,完全都不打印了,但如果又想将处理后的行打印出来,使用p
命令 q
命令的作用是立即退出,sed 的默认行为是对第2行处理完后,虽然后面的行都不需要处理,但 sed 仍然会继续将后面的每行往模式空间加载。
- sed 默认会将每一行都打印出来,
grep(rg)
在 Linux 内核源码目录下,搜索 drivers/gpu/drm
下所有的 DRIVER_NAME
定义,并排序后格式化输出
命令如下:
1 | rg '#define DRIVER_NAME' drivers/gpu/drm --no-heading \ |
rg
(ripgrep) 比 grep 更快,更强大tr
在不带任何选项时,默认执行替换,例子是中将 tab 替换成 空格,-s
表示squeeze-repeats
, 就是去掉重复的字符,例如多个空格只保留一个awk
天生支持 C-Style printf
引号
当混合使用 awk 和 sed 时,比较便利地处理引号的方法是定义 awk 变量 -v Q="'"
- awk 的
printf()
函数第一个参数必须使用双引号printf("%s: %s%d%s\n", $1, Q, $2, Q)
- awk 中的用户自定义变量和内置变量,使用时都不需要加
$
, 如NR
,NF
- awk 中的用户自定义变量和内置变量,使用时都不需要加
- awk 和 sed 的命令字串必须用引号括起来,当用 awk 生成 sed 命令时,将单引号定义为 awk 变量尤其方便,可读性也强
行范围 (Line range)
sed, awk 都支持行范围,如 sed -n '3,5p'
, awk 'NR >= 3 && NR <= 5'
,它们的效果是一样的,都是只打印给定文件的 3 到 5 行, 除了行号, sed, awk 还支持通过正则匹配来指定行位置
1 | sed -n '/foo/,$p' |
$
表示最后一行p
是 sedprint
命令的缩写
1 | awk '/foo/ {f=1} f' |
- awk 实现行范围的方式与 sed 稍有不同,它在满足匹配
foo
这个条件时,将布尔变量f
设置为 1,相当于从这一行开始,开启print
打印这个默认操作 - awk 的一条 rule 里,可以省略 pattern, 或者省略 action, 但不能同时两个都省略;如果省略 pattern, 则对每个输入行都执行 action, 如果省略 action, 则对每个匹配行执行
print
action。这个例子里面, awk 的 program 包括两条 rules/foo/ {f=1}
这条 rule 什么都没有省略f
这条 rule 省略了 action, 所以对每个输入行执行print
action
它们两个的效果也是一样的,都是从第一个匹配 foo
的行开始,一直打印到文件结束。
正则匹配 (Regex match)
awk 中一个 rule 中的 pattern 部分可以只是一条 /
括起来的正则表达式,默认这个正则表达式的匹配对象是 $0
, 也就是整行, 如
1 | awk '/foo/' |
它实际上是
1 | awk '$0~/foo/ {print $0}' |
它意思是只要当前行里包含 foo
这个字串,就打印这一行。所以 awk 的正则 pattern, 准确来说是用来搜索的
默认情况下是 $0~/foo/
, 那么不默认情况下,可以指定哪个 field
去“匹配”, 如
1 | awk '$1~/foo/ && $3!~/bar/ {print}' |
它意思是当前这条记录,如果第1个字段包含 foo
且 第3个字段不包含 bar
, 那么就打印整条记录