POSIX-shell學習筆記

寻找繁星發表於2024-07-30

學習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+cINT事件被觸發,從而呼叫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

再執行上面的命令,這個指令碼就可以被順利殺死了。

存在的問題

不支援陣列。