Bash編程

資料

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 helpcompgen -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"

問題

  1. here 文檔和 here 字符串的區(qū)別是什么施敢?
    幾乎沒有區(qū)別。只是here文檔需要使用前后標識辛萍。
  2. 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")捏肢。

2.2 重定向

參考:http://www.tldp.org/LDP/abs/html/io-redirection.html

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市饥侵,隨后出現(xiàn)的幾起案子鸵赫,更是在濱河造成了極大的恐慌,老刑警劉巖爆捞,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奉瘤,死亡現(xiàn)場離奇詭異,居然都是意外死亡煮甥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門藕赞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來成肘,“玉大人,你說我怎么就攤上這事斧蜕∷簦” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵批销,是天一觀的道長洒闸。 經(jīng)常有香客問我,道長均芽,這世上最難降的妖魔是什么丘逸? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮掀宋,結果婚禮上深纲,老公的妹妹穿的比我還像新娘。我一直安慰自己劲妙,他們只是感情好湃鹊,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镣奋,像睡著了一般币呵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侨颈,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天余赢,我揣著相機與錄音芯义,去河邊找鬼。 笑死没佑,一個胖子當著我的面吹牛毕贼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛤奢,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鬼癣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了啤贩?” 一聲冷哼從身側響起待秃,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痹屹,沒想到半個月后章郁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡志衍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年暖庄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楼肪。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡培廓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出春叫,到底是詐尸還是另有隱情肩钠,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布暂殖,位于F島的核電站价匠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呛每。R本人自食惡果不足惜踩窖,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望莉给。 院中可真熱鬧毙石,春花似錦、人聲如沸颓遏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叁幢。三九已至滤灯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鳞骤。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工窒百, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豫尽。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓篙梢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親美旧。 傳聞我的和親對象是個殘疾皇子渤滞,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,380評論 0 5
  • 1. 清空文件內(nèi)容 $ > file 這一行命令用到了輸出重定向操作符>。輸出重定向發(fā)生時榴嗅,文件會被打開準備寫入妄呕。...
    MrHamster閱讀 599評論 0 0
  • Bash編程013——環(huán)境變量 環(huán)境變量可以幫助提升你的Shell體驗。很多程序和腳本都通過環(huán)境變量來獲取系統(tǒng)信息...
    若夢兒閱讀 1,009評論 0 7
  • Bash編程002——變量 在任何一門程序設計語言中嗽测, 變量都是必不可少的绪励。在shell中變量的涵義跟其他語言中的...
    若夢兒閱讀 131評論 0 1
  • 閑賦登高抬望遠 滿目青山映龍泉 云開見日乘風遠 纖巧嬌娘東山眠 虎踞龍盤守福地 護村佑民旺收益 草木皆綠意顯春 沂...
    篤誠閱讀 792評論 1 2