資料
ABS:http://www.tldp.org/LDP/abs/html
在線 Bash 手冊頁:https://www.gnu.org/software/bash/manual/bash.html
Bash 手冊頁 man bash
常見錯誤:http://mywiki.wooledge.org/BashPitfalls
AWK 手冊:https://www.gnu.org/software/gawk/manual/html_node/index.html
問題
- 函數(shù)如何傳遞返回值?寫全局變量不好猾封,$()的方式會影響返回狀態(tài)的捕獲
常見的方法還是命令替換捕獲輸出的方式 -
:(){ :|:& }();:
帶有管道的后臺到底把哪個進程放到后臺了澄耍? - 如何提取文本中的正則匹配項?
簡單的用grep -o
晌缘,復雜的可使用 awk 的match
函數(shù)
*POSIX Character Classes 為什么還要加一個方括號齐莲?[[:alpha:]]
陷阱
- ssh 遠程命令無法返回。
原因:ssh 需要確定沒有任何的輸入輸出之后才能返回枚钓。
解決:要執(zhí)行的命令添加 < /dev/null 和 > /dev/null 或 使用 ssh -f - 子shell變量修改
原因:父進程中的變量在子進程中是可讀的铅搓,但是當子進程寫這個變量時,是不會影響到父進程的(可能是寫時在子進程中創(chuàng)建了同名變量)搀捷。 - 預防 SIGHUP 信號
解決:使用:
nohup
setsid # 父進程為 init
( command & ) # 父進程為 init
screen # 服務器端保存會話狀態(tài) - sort 排序占滿磁盤空間
原因:sort 使用的是外排序星掰,會將中間結果保存在文件中,默認存儲目錄為 /tmp嫩舟,可以使用 -T 執(zhí)行目錄
解決:-T 指定別的大的磁盤分區(qū)氢烘,量級更大的任務使用 mapreduce 等工具
一. 基礎知識
Bash 環(huán)境設置
-
set -u
保證每個變量在使用前都被初始化 -
set -e
腳本中一旦遇到錯誤就退出 -
set -x
顯示詳細執(zhí)行過程,用來 debug 腳本 -
set -o pipefail
管道命令 command1 | command2 | ... | commandn 從左往右執(zhí)行家厌,即使中間命令出錯也繼續(xù)執(zhí)行下去播玖。正常情況下整條命令的返回狀態(tài)是最后一條命令執(zhí)行的狀態(tài)。打開該開關后整條命令的返回狀態(tài)變?yōu)樽詈笠粋€異常退出命令的狀態(tài)饭于。當所有命令都正常退出時蜀踏,整條命令的返回狀態(tài)是0。
進程
bash 中可輸入的內(nèi)容很多掰吕,要注意區(qū)分關鍵字(keyword)果覆,內(nèi)置命令(built-in command),外部命令
1. 關鍵字
- 使用
compgen -k
查看關鍵字 - 關鍵字解析先于變量替換殖熟,這樣可以作特殊處理局待,如:
string_with_spaces='some spaces here'
# 注意,$string_with_spaces 沒有被引用依然工作良好菱属,因為 [[ 是關鍵字
[[ -n $string_with_spaces ]]
# 這樣寫就會報錯:bash: [: too many arguments
# 原因是 [ 是一個內(nèi)部命令钳榨,先做了變量替換,然后調(diào)用命令發(fā)現(xiàn)參數(shù)個數(shù)不對
[ -n $string_with_spaces ]
- 關鍵字使用不會產(chǎn)生子進程
2. 內(nèi)置命令
- 使用
type command
驗證一個命令是否是內(nèi)部命令纽门,builtin help
或compgen -b
列出所有內(nèi)部命令 - 內(nèi)置命令常駐內(nèi)存
- 內(nèi)置命令不會fork子進程(待核實)
- 執(zhí)行外部命令需要加載磁盤文件到內(nèi)存薛耻,并fork子進程(具體過程待核實)
3. 進程狀態(tài)捕獲
- $$ 當前 shell pid
- $! 最近執(zhí)行的后臺進程pid
- $? 最近退出的前臺進程退出狀態(tài)
- wait $! 獲得最近的后臺進程退出狀態(tài)(會阻塞直到這個后臺進程退出)
- ps $! 查看后臺進程是否退出
4. 名字空間
命令替換會產(chǎn)出子進程,在命令替換中修改父進程的變量是無效的赏陵。
元字符
bash 中的標識符昭卓、參數(shù)等默認都為字符串愤钾。字符串可加單引號或雙引號或者不加瘟滨。單引號內(nèi)字符串不做任何解析候醒,可用來屏蔽元字符,雙引號內(nèi)的做參數(shù)替換杂瘸,命令替換倒淫。
由于參數(shù)替換和命令替換結果中可能含空白符,建議含二者的字符串總是用雙引號擴起败玉,傳遞時才會作為一個字符串敌土。一個例外情況:
files="a.txt b.txt c.txt"
for f in ${files}; do
cat "${f}"
done
如果給${files}
加上雙引號,被被認為是一個字符串运翼,導致循環(huán)失敗返干。
變量
1. 數(shù)值計算
- 整數(shù)計算
(( ))
括號中為 C 語言形式的表達式,要取表達式的值血淌,使用$(( expression ))
- 浮點數(shù)計算
bc
awk
等工具
sun@st01-oped-sunrui09 ~/play$ bc <<< "4.5-2.34"
2.16
另外 awk 也可以計算
2. 字符串處理
可參:man bash
Parameter Expansion 節(jié)
注:ABS 文檔可能有誤矩欠,字符串Strip和替換說是正則表達式,但是并不是
x=abcd
${#x} # 獲取字符串長度
expr index $x "b" # 查找子串悠夯,下標從1開始
echo ${x:1} # 獲取子串癌淮,下標從0開始,從下標為1的位置到最后
echo ${x:0:2} # 獲取子串沦补,從下標0乳蓄,選取長度為2的子串
echo ${x:-nuclear} # 若不存在取默認值
echo ${x#word} # 左端 strip 字符串,最短匹配
echo ${x##word} # 左端 strip 字符串夕膀,最長匹配
# strip虚倒,查找并替換,查找并刪除等
if [[ $1 =~ $regex ]]; then # 正則表達式
...
expr length “this is a test” #
expr substr “this is a test” 3 5
expr index "sarasara" a
3. 數(shù)組产舞,關聯(lián)數(shù)組
declare -a array# 顯示聲明數(shù)組(index 只能為0以上的整數(shù))
array=() # 同上
array+=("sun") # 數(shù)組追加
declare -A array # 顯示聲明關聯(lián)數(shù)組(index可以為字符串)
4. 變量作用范圍
一般為當前進程空間魂奥,子進程、父進程都不可見庞瘸,相當只對本腳本生效捧弃,如果使用 export 導出,或者用 declare -x 聲明擦囊,則子孫進程可見(不能修改)违霞,父進程不可見
一旦一個變量被導出過一次,就變?yōu)榄h(huán)境變量瞬场,后續(xù)的修改都是對環(huán)境中該變量的修改买鸽,可被所有子孫進程看到饰恕,如在 .bashrc 中寫了 export JAVA_HOME=/jdk/1.7
洁桌,在某個應用腳本中 JAVA_HOME=/jdk/1.8
將直接對所有子孫進程生效。
函數(shù)中使用local
定義為作用域僅限函數(shù)
5. 變量解析(Parameter Expansion)植康、復雜變量、eval
- 一般使用 $PARAM 或者 ${PARAM} 的方式解析看幼,
- 使用${!PARAM} 的方式進行間接解析(indirect expansion)批旺,例如:
name='sun'
PARAM=name
${!PARAM} # 將會解析為 sun
PARAM 稱為復雜變量。上面做了2次變量拓展诵姜,bash 環(huán)境執(zhí)行腳本或命令時汽煮,首先做各種替換(變量替換,算術替換和命令替換)棚唆,然后執(zhí)行替換后的文本暇赤。所以一般情況下變量只會被替換一次,( 變量中包含 * 會被再次替換)
需要注意的是宵凌,在變量賦值的過程中鞋囊,bash 環(huán)境中的元字符(單雙引號等)會被消化掉。具體行為為:
- 將最外層擴住字符串的引號消掉
- 將轉(zhuǎn)義字符(\)消掉瞎惫,只保留轉(zhuǎn)義后的字符
所以在做變量的多次擴展時要小心保留這些字符溜腐。
還可以使用 eval 實現(xiàn)多次拓展。eval [arg...]
的行為:
bash 處理腳本的基本過程為先對整個文本做變量替換微饥,命令替換等逗扒,然后執(zhí)行。所以對于存在 eval 的腳本欠橘,bash 也是先對整個文本做變量命令等替換(包括eval 后面的內(nèi)容)矩肩,然后執(zhí)行。eval 的作用是將所有參數(shù)再交給 bash 環(huán)境做一次處理肃续。
function xargs() {
holder=$1
func=$2
shift 2
while read line; do
eval $holder=$line
eval $func $@
done
}
# 將文件中每個單詞按逗號分隔黍檩,注意,占位符要用單引號始锚,否則會被提前替換
cat a.txt | xargs word echo -n '$word',
函數(shù)
-
[ function ] name() { list }
形式定義刽酱,無參數(shù)列表,function 關鍵字可省略 - 函數(shù)像普通命令一樣被調(diào)用瞧捌,后接各種參數(shù)棵里,這些參數(shù)在函數(shù)內(nèi)部用
$1,$2姐呐,...$n
訪問殿怜,而$0
始終是腳本文件 - 函數(shù)在當前shell環(huán)境中執(zhí)行,不產(chǎn)生新的進程
- 僅作用于函數(shù)內(nèi)部的變量使用 local 關鍵字聲明曙砂,而不加 local 的變量作用域是整個進程空間头谜,和子進程空間(只讀)
- 函數(shù)的返回狀態(tài)取決于函數(shù)內(nèi)最后執(zhí)行的命令,默認為0
-
return
是返回調(diào)用的函數(shù)鸠澈,exit
則會退出程序柱告,他們都會重置$?
變量截驮,優(yōu)雅的程序應該單入單出,一般函數(shù)中都使用return
际度,使調(diào)用者進行錯誤處理葵袭;只在main
函數(shù)中使用exit
,或全都使用return
(甲脏?) - 函數(shù)返回值捕獲眶熬,一般使用命令替換的方式
value=$(func arg)
但是這種方法有個缺點:函數(shù)的退出狀態(tài)(return 或 exit 的值)無法通過$?
獲得,被賦值覆蓋了块请,命令替換是在子進程中執(zhí)行的,若函數(shù)中存在對當前環(huán)境中變量的寫操作拳缠,將失效墩新。 - 函數(shù)可以遞歸調(diào)用,且沒有深度限制
:(){ :|:& }();:
fork 炸彈窟坐,參http://www.ha97.com/2618.htmlhttps://en.wikipedia.org/wiki/Fork_bomb
重定向
重定向是從右向左解析的:
-
cat <file >file
會清空原文件的內(nèi)容海渊,因為根據(jù)最右邊生成一個名為 file的文件(如果已存在就被替換了),然后從一個名為 file 的文件讀取內(nèi)容并寫入 file哲鸳。 -
cat < file >> file
則不會停止臣疑。 -
> file 2>&1
這樣寫是沒問題的,把標準錯誤也寫到標準輸出里徙菠,但是2>&1 >file
這樣寫就不行讯沈,因為先解析發(fā)現(xiàn)標準輸出寫到文件里,然后才發(fā)現(xiàn)標準錯誤寫到標準輸出里婿奔。2>&1
中的&
是標識 后面的1
不是普通名為1
的文件缺狠,而是標準輸出。使用&> /dev/null
將標準輸出和標準錯誤同時輸出到 /dev/null 中萍摊。
管道挤茄,進程替換(process substitution)
詳參 man bash
管道符之前的命令的標準輸出作為管道符后命令的標準輸入。每一個 |
后面的命令都會作為當前 shell 進程的一個子進程執(zhí)行冰木。
進程替換命令的輸出結果都可以看作是一個臨時文件穷劈,將這個臨時文件的內(nèi)容作為標準輸入傳遞下去,與管道可以達到相同效果踊沸,但是每個管道會產(chǎn)生子進程(外部命令本來就是作為一個子進程執(zhí)行歇终,但是內(nèi)部命令在這種情況下也會作為子進程執(zhí)行),而進程替換不會雕沿。進程替換與命令替換$()
是類似的练湿,區(qū)別是命令替換的結果被當作一個字符串,進程替換的結果當作一個文件审轮。
ls | grep zink # 相當于 ls > tmp肥哎,grep zink tmp
grep zink < <(ls) # 效果同上辽俗,但不產(chǎn)生子進程
find . -name 'control' -type f | xargs grep zink
#管道前的 find 命令產(chǎn)生臨時文件,xargs 命令的作用是取文件的每一行拼接后面的命令篡诽。
sun@st01-oped-sunrui09 ~/play$ ls -l <(ls)
lr-x------ 1 sun sun 64 Aug 25 10:59 /dev/fd/63 -> pipe:[368734728]
mkfifo 命名管道
命名管道當做一個文件使用崖飘,但是實際實現(xiàn)是通過內(nèi)存。命名管道讀者會阻塞杈女,直到有進程往其中寫朱浴,并以EOF結尾,讀者才會喚醒并獲取所有數(shù)據(jù)达椰。
一種使命名管道常開的方法:
mkfifo pipe
sleep 10000 > pipe &
cat pipe &
echo "some data" > pipe
echo "more data" > pipe
使用 tee 命令并結合進程替換可以實現(xiàn)分流:
nc -l 5000 | tee >(./process1 | ./collect ) >(./process2 | ./collect ) | ./do_other.sh
執(zhí)行環(huán)境
編寫復雜腳本翰蠢,注意區(qū)分父子進程。bash元字符在父子進程間傳遞解析啰劲。
會產(chǎn)生子進程的條件:
- 管道
- 命令替換
命令分組
參考:https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html
命令分組有兩種形式:
(list)
{list; }
注意:使用大括號的形式時梁沧,括號要與list使用空格分開,并且list末尾要使用分號終止蝇裤。
here 文件和 here 字符串
- 使用
<<
表示here文件廷支,<<<
表示here字符串
#!/bin/bash
# 直接使用pstree,在每一行前加了行號
while read i; do
echo -e "$((++j))" "\t $i"
done < <( pstree )
# 使用 here-document栓辜,直接對多行文本進行逐行處理
while read line; do
echo "this is $line"
done < <(cat <<EOF
xxx
yyy
zzz
EOF)
# 上面使用了進程替換的方式恋拍,進程替換 <(cmd args...) 相當于一個匿名文件,然后將文件內(nèi)容重定向到標準輸入流藕甩,更直接的:
while read line; do
echo "this is $line"
done <<EOF
xxx
yyy
zzz
EOF
# 使用 here-string
while read line; do
echo "this is $line"
done <<< "xxx
yyy
zzz"
問題
- here 文檔和 here 字符串的區(qū)別是什么施敢?
幾乎沒有區(qū)別。只是here文檔需要使用前后標識辛萍。 - here 文檔和 here 字符串用在什么場景悯姊?
原命令需要從標準輸入讀取數(shù)據(jù),但是由于是在腳本中贩毕,here文檔(字符串)實現(xiàn)了把腳本中的字符串內(nèi)容輸入到原命令標準輸入悯许。
命令替換提供了匿名文件的能力,here文檔(字符串)提供了將內(nèi)容置于腳本內(nèi)的能力辉阶,二者相互結合先壕,可實現(xiàn)在腳本中混合編程,例:
#! /bin/bash
echo "from bash"
python <(cat << EOF
#! /usr/bin/python3
import sys
if __name__ == "__main__":
print("from python " + sys.argv[1])
EOF
) hello
echo "from" | awk -f <(cat <
/from/{
print "from awk"
}
EOF
)
可以用 here 文檔和 here 字符串作塊注釋:
<<!
注釋1
!
<<<'
注釋2
'
信號
trap command signal 捕獲信號谆甜,只有信號 9 是無法捕獲的
常用 artifact
awk
nc
expect 與進程交互
ss -apn (netstat -apn)
lsof -i:5000
grep -v 排除
wait 等待后臺進程
stat 查看文件狀態(tài)
getopts 處理命令行參數(shù)
time
timeout
taskset 設置進程的cpu親和性
strace 跟蹤進程的系統(tǒng)調(diào)用
二. 高級主題
2.1 協(xié)程
coproc [NAME] command [redirections]
協(xié)程異步在子shell中執(zhí)行垃僚,本命令會總是返回成功。注意规辱,如果 command 是簡單命令的話谆棺,NAME 一定不能提供,此時名稱為 COPROC罕袋,協(xié)程的PID 為 NAME_PID
#!/bin/bash
# create the co-process
coproc myproc {
bash
}
# send a command to it (echo a)
echo 'echo a' >&"${myproc[1]}"
# read a line from its output
read line <&"${myproc[0]}"
# show the line
echo "$line"
wait ${myproc_PID} # 等待協(xié)程結束
awk 協(xié)程
awk '
{
hive = "hive -S 2>/dev/null"
print ("dfs -du -s " $0 ";") |& hive
hive |& getline line
if(line ~ /^[0-9]+/) print line
}
END {
close(hive)
}
' data_pathes
當使用 |&
時改淑,會在一個子進程中啟動任務并打開雙向的管道碍岔。管道可用任意程序輸入,但只能用awk的getline
讀取標準輸出朵夏。
在整個 awk 程序執(zhí)行期間每當出現(xiàn)該任務的字符串會復用該進程蔼啦。需要在不用時顯式close
。
close
還可以選擇只關閉輸入或輸出:close(hive, "to")
仰猖,close(hive, "from")
捏肢。