學習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
#!/usr/bin/env sh
if [ condition1 ]; then
# Do something
elif [ condition2 ]; then
# Do something
else
# Do something
fi
判斷某環境變數是否存在
參考:https://blog.csdn.net/blade2001/article/details/7243143?utm_source=blogxgwz3
上面的文章好像寫反了
例子:判斷環境變數DISPLAY是否存在(若不存在說明沒有提供顯示裝置)
if [ "$DISPLAY" ]; then
# DISPLAY存在
else
# DISPLAY不存在
fi
或者
if [ ! "$DISPLAY" ]; then
# DISPLAY不存在
字串
功能 | 例子 |
---|---|
為空 | [ -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
echo "yes"
fi
來源:https://stackoverflow.com/a/45591665/13688160
判斷檔案型別
來源:https://jingyan.baidu.com/article/95c9d20d5ac536ec4e7561ad.html
#!/usr/bin/env sh
if [ -z "$1" ]; then #如果沒有輸入引數,也就是第一個引數的字串長度為0
: #空語句
else
if [ -e "$1" ]; then #如果檔案存在的話
if [ -f "$1" ]; then #如果檔案是個普通檔案?
echo "$1 is a text file."
elif [ -d "$1" ]; then #如果檔案是個目錄檔案?
echo "$1 is a directory."
elif [ -c "$1" ]; then #如果檔案是個字元裝置?
echo "$1 is a char device."
elif [ -b "$1" ]; then #如果檔案是個塊裝置?
echo "$1 is a block device."
else #否則
echo "$1 is unknow file."
fi
fi
另外,-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
定義
FunctionName() {
do_some_thing_here
return Interger
}
也可以不return。
引數用法與指令碼類似。$#
表個數,$1, $9, ${10}
表具體引數。
使用
FunctionName par1 par2 par3
迴圈
while
參考:https://wiki.jikexueyuan.com/project/shell-tutorial/shell-while-loop.html
while Command
do
Statement(s) to be executed if Command is true
done
或者
while [ Condition ]; do
Statement(s) to be executed if Condition is true
done
也可以對命令返回值取反,比如
while ! Command; do
Statement(s) to be executed if Command is false
done
重定向
# 將`stdout`重定向到`stdout.txt`
Command > stdout.txt
# 將`stderr`重定向到`stderr.txt`
Command 2> stderr.txt
# 將stderr重定向到stdout
Command 2>&1
# 將stderr和stdout都重定向到一個檔案
# 參考:<https://blog.csdn.net/u011630575/article/details/52151995>
Command > shell.log 2>&1
# 將`stdin`重定向到`stdin.txt`
Command < stdin.txt
# 將Command1的stdout輸入到Command2的stdin
Command1 | Command2 # 例如 echo 'whoami' | sh,可以讓sh執行whoami。
將多行字面量作為stdin
Command <<標記
第一行
第二行
...
標記
例如:
sh <<EOF
whoami
echo 2333
EOF
命令列引數
arg_num() {
# 引數個數(不含$0)
echo $#
}
# 訪問某引數
echo "$2"
# 相當於arg_num "$1" "$2"
arg_num "$@"
# 相當於arg_num "$1 $2"
arg_num "$*"
shift [n]
Shift the positional parameters n times. A shift sets the value of $1 to the value of $2, the value of $2 to the
value of $3, and so on, decreasing the value of $# by one. If n is greater than the number of positional parameters,
shift will issue an error message, and exit with return status 2.
n
似乎預設是1。
Command substitution
把命令的輸出會儲存到變數:
a=$(command args...)
但如果是要把輸出當字串用,需要在周圍加上雙引號。但是要注意,$(
和)
之間的內容不需要再加一層轉義。舉個例子,判斷當前目錄是不是a b
:
check() {
# 而不是 "$(basename \"$(pwd)\")"
if [ "$(basename "$(pwd)")" = "a b" ]; then
echo yes
else
echo no
fi
}
check
mkdir -p "a b"
cd "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 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
trap 'exit 1' INT
trap 'echo cleanup' EXIT
sleep 3
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
:
#!/usr/bin/env sh
trap 'echo exit 1; exit 1' TERM
sleep 1000000
./test.sh &
pid=$!
sleep 1
kill -TERM $pid
這時候這個程序是殺不死的,因為sleep 1000000
阻塞了指令碼,所以trap
沒有執行。但我們可以把sleep
放到子程序裡:
#!/usr/bin/env sh
(sleep 100000) &
pid=$!
trap 'kill -TERM $pid; echo exit 1; exit 1' TERM
wait
再執行上面的命令,這個指令碼就可以被順利殺死了。
存在的問題
不支援陣列。