学习POSIX shell建议使用dash,因为它很快:https://unix.stackexchange.com/a/148098
man dash
: Only features designated by POSIX, plus a few
Berkeley extensions, are being incorporated into this shell.
¶ 条件判断
man dash
,然后搜索test expression
,可以看到完整的列表。
¶ if elif else
|
¶ 判断某环境变量是否存在
参考:https://blog.csdn.net/blade2001/article/details/7243143?utm_source=blogxgwz3
上面的文章好像写反了
例子:判断环境变量DISPLAY是否存在(若不存在说明没有提供显示设备)
if [ "$DISPLAY" ]; then |
或者
if [ ! "$DISPLAY" ]; then |
¶ 字符串
功能 | 例子 |
---|---|
为空 | [ -z "$1" ] 或者 [ ! "$1" ] |
非空 | [ -n "$1" ] 或者 [ "$1" ] |
相等 | [ "$1" = "$2" ] |
不相等 | [ "$1" != "$2" ] |
字典序小于 | [ "$1" \< "$2" ] |
字典序大于 | [ "$1" \> "$2" ] |
所以如果判断目录是否非空,可以这样:if [ "$(ls -A xxx)" ]
参考:https://www.cyberciti.biz/faq/linux-unix-shell-check-if-directory-empty/
¶ 整数的大小判断
代码 | 含义 | 例子 |
---|---|---|
-eq | = | [ $1 -eq 2 ] |
-ne | != | [ $1 -ne 2 ] |
-le | <= | [ $1 -le 2 ] |
-lt | < | [ $1 -lt 2 ] |
-ge | >= | [ $1 -ge 2 ] |
-gt | > | [ $1 -gt 2 ] |
-a | && | [ $1 -gt 0 -a $1 -lt 10 ] |
-o | || | [ $1 -lt 0 -o $1 -gt 10 ] |
¶ 浮点数的大小判断
if awk "BEGIN {exit !(1.234 >= .233)}"; then |
来源:https://stackoverflow.com/a/45591665/13688160
¶ 判断文件类型
man dash
,搜test expression
-b | 块设备 |
-c | 字符设备 |
-d | 目录 |
-e | 路径存在 |
-f | 普通文件 |
-h | 符号链接 |
例子:
|
另外,-s
表示文件存在且不为空。来源:https://stackoverflow.com/questions/9964823/how-to-check-if-a-file-is-empty-in-bash
¶ 判断文件权限
代码 | 含义 |
---|---|
-r | 存在且可读 |
-w | 存在且可写 |
-x | 存在且可执行 |
参考:https://stackoverflow.com/questions/10319652/check-if-a-file-is-executable
¶ 函数
参考:https://www.runoob.com/linux/linux-shell-func.html
¶ 定义
如果想要修改全局变量的话:
a=1 |
如果不想修改全局变量的话,就让整个函数在一个subshell里:
# https://stackoverflow.com/a/18600920/13688160 |
也可以不return。
参数用法与脚本类似。$#
表个数,$1, $9, ${10}
表具体参数。
¶ 使用
FunctionName par1 par2 par3 |
¶ 循环
¶ while
参考:https://wiki.jikexueyuan.com/project/shell-tutorial/shell-while-loop.html
while Command |
或者
while [ Condition ]; do |
也可以对命令返回值取反,比如
while ! Command; do |
¶ 重定向
# 将`stdout`重定向到`stdout.txt` |
¶ 将多行字面量作为stdin
Command <<标记 |
例如:
sh <<EOF |
¶ 命令行参数
arg_num() { |
shift [n] |
n
似乎默认是1。
¶ 把变量展开成多个参数
如果确保参数中没有空格,可以这样:
arg_num() { |
注意,引号会被原样传递:
args="\"a b\"" |
所以如果某个参数中有空格,那么不能用上面的方式,而是要这样:
args="\"a b\" \"c d\"" |
set [{ -options | +options | -- }] arg ... |
¶ Command substitution
把命令的输出会保存到变量:
a=$(command args...) |
但如果是要把输出当字符串用,需要在周围加上双引号。但是要注意,$(
和)
之间的内容不需要再加一层转义。举个例子,判断当前目录是不是a b
:
check() { |
其中"$(basename "$(pwd)")"
相当于先执行basename "$(pwd)"
,再把输出用双引号包起来变成字符串。
如果改成"$(basename \"$(pwd)\")"
,就变成执行basename \"$(pwd)\"
,也就是先执行pwd
,然后执行basename \"a b\"
,结果就变成a
了。
相关文档:https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
¶ 进程
获取当前subshell的PID:
pid=$(exec sh -c 'echo "$PPID"') |
来源:https://unix.stackexchange.com/a/484464
¶ set
可以用set
来设置一些模式。
¶ 子命令返回值不为0时退出
set -e
相当于在每条命令后面加上了|| exit $?
但是要注意的是,对于用管道连接起来的几个命令,最终的exit
code似乎是管道里的最后一个命令。例如对于command A | command B
,如果command A
出错返回非0,但是command B
正常退出,那么这个组合命令的exit
code会被设置成0,这样set -e
就不会生效。
bash支持set -o pipefail
,其效果是将exit
code赋值为被管道组合起来的命令中最后一个返回非0值的命令,这样被管道组合起来命令中任何一个命令返回非0都会退出。
来源:Get exit status of process that’s piped to another
¶ trap
格式:
trap 命令 事件 |
常用事件:
EXIT
:exit
被调用
常用信号:
- HUP
:
SIGHUP,父进程退出的时候会向子进程发送SIGHUP。但注意,如果父进程是被SIGKILL
杀死的,那SIGHUP不一定会发送到子进程。
- INT
: SIGINT,可以由ctrl+c触发
- TERM
: SIGTERM
例子:
a.sh |
sh a.sh
,然后ctrl+c
,INT
事件被触发,从而调用exit 1
,进而触发EXIT
事件,从而调用echo cleanup
,然后屏幕上会打印一行cleanup
。
来源:https://newbe.dev/exit-trap-in-dash-vs-ksh-and-bash
但是要注意的是,如果脚本正在执行一个外部命令,比如sleep
,这时脚本里的trap
是不会被调用的。因此在需要进行信号处理的脚本中如果需要执行时间很长的命令,那么要把这个命令放到子进程里跑,然后在主进程里wait。例如test.sh
:
|
./test.sh & |
这时候这个进程是杀不死的,因为sleep 1000000
阻塞了脚本,所以trap
没有执行。但我们可以把sleep
放到子进程里:
|
再执行上面的命令,这个脚本就可以被顺利杀死了。
如果是pipeline命令,直接将其放入后台的话,$!
只是pipeline里的最后进程,将其杀掉只会导致pipeline前面的进程变成孤儿进程。为了一次性将整个pipeline杀死,可以用setsid
将其放入一个单独的进程组里,然后向整个进程组发信号:
|
详情请参考:Linux shell向进程组发信号
¶ wait
如果不带参数,就是等待所有后台任务完成。
也可以等待一个特定进程:wait <PID>
bash中可以wait -n
来等待任意一个后台任务完成,但POSIX标准中没有这个。
在POSIX shell中如果要等待多个任务中的任意一个完成,然后把其他的杀掉,可以这样:
|
其中$$
是当前shell的PID,pkill -P $$
表示把父进程是当前shell的所有进程杀掉。
来源:https://unix.stackexchange.com/a/231678
如果是带pipeline的复杂命令,需要这样:
|
但是需要注意的是,$$
在subshell中会被展开为shell的PID,而不是subshell的PID。因此如果在subshell中,需要这样:
|
|
但是这个方法因为会把自己杀掉,所以会打印一行Terminated
。
¶ case
case 表达式 in |
¶ Conditional substitution
{VAR:+内容} |
如果VAR存在,它就会展开成内容
,如果VAR不存在,就展开成空的。
跟case配合可以实现往PATH
里添加路径:
append_path () { |
如果PATH
不存在,${PATH:+$PATH:}$1
就展开为$1
,如果PATH
存在,就展开为$PATH:$1
。
同理,也可以往PATH
的前面添加路径:
prepend_path () { |
¶ unset
文档:https://pubs.opengroup.org/onlinepubs/009696699/utilities/unset.html
默认是unset
环境变量。但如果是要unset函数,要用unset -f
。
¶ 存在的问题
不支持数组
不支持
wait -n