简洁的 Bash Programming 技巧(三)
這是簡潔的 Bash Programming 技巧系列的第三篇文章,這一系列的文章專門介紹 Bash 編程中一些簡潔的技巧,幫助大家提高平時 Bash 編程的效率。有興趣的同學可以回顧下之前的兩篇文章(一)和續(xù)篇。
1. 替換語法${parameter/pattern/string}的妙用
${parameter/pattern/string}將parameter中匹配pattern的部分替換成string,例如下面的例子將字符串中的e替換成x:
$ str="three" $ echo "${str/e/x}" # thrxe如果pattern部分以/開頭,表示替換parameter中所有匹配的內容,例如:
$ str="three" $ echo "${str//e/x}" # thrxx如果pattern部分以#開頭,表示僅當parameter開始處匹配pattern的時候替換,例如:
str="three" $ echo "${str/#e/x}" # three $ echo "${str/#t/x}" # xhree與此對應地是,如果pattern部分以%開頭,表示僅當parameter結尾處匹配pattern的時候替換,例如:
$ str="three" $ echo "${str/%e/x}" # threx如果string部分為空,匹配pattern的部分被刪除(替換為空),例如:
$ str="three" $ echo "${str/h/}" # tree這個時候第二個斜杠可以刪除,即:echo "${str/h}" 如果parameter是一個數組會怎么樣呢?有興趣的可以看看Bash的man手冊說明:
man -P 'less -p "\\$\{parameter/pattern/string}"' bash2. +=運算符
有一天,我看到這樣一個用法:
$ arr=(1 2 3) $ arr+=(4 5)原來數組還可以這樣相加,后來我看了下Bash的手冊,確實有一段這么說明的,這里未引用這段文字,有興趣的可以查看Bash Reference Manual。 自然地我們會想到如果一個變量是數字,是否也可以用+=作運算呢?
$ i=1 $ i+=1但是,運行后你會發(fā)現i的結果并不為2,而是11,這里bash并不認為i是一個整數,而是作為字符串。 這時可以通過declare聲明一個變量為整數,上面的問題就解決了:
$ declare -i int=1 $ int+=1 $ echo $int 23. Here document不為人知的用法
Shell學得越多,越會發(fā)現一些神奇的用法,每天都覺得自己實在是一個剛入門的菜鳥。
一般的here document的用法是這樣的:
$ cat b.sh cat<<EOF hello, $USER EOF $ sh b.sh hello, kodangohere document中的變量都是會被展開的,那能不能不展開呢?答案是可以的,將EOF有引號括起來就可以:
$ cat b.sh cat<<"EOF" hello, $USER EOF $ sh b.sh hello, $USER一般here document用得最多的是在幫助函數(helpusage)函數里面,因為在這里我們要寫一大段的腳本用法。
如果你有強迫癥(比如我),有時候使用here document的時候會很不爽,因為here document里面每行首部的空格都會被保留,而如果要頂格寫,在縮進的地方又會有點打亂結構,例如:
$ cat b.sh # part 1 if :; thencat << EOFhello, $USER EOF fi# part 2 if :; thenif :; thencat << EOF hello, $USER EOFfi fi上面的腳本執(zhí)行的結果為:
$ sh b.sh hello, kodango # part 1 result hello, kodango # part 2 result有沒有辦法既兼顧到縮進又能不保留行首空格呢?
答案也是肯定的,只不過語法又要稍稍變一下,現在在<<的后面加一個短橫,這個用法下,行首的Tab字符都會被忽略:
$ cat b.sh if :; thencat <<- EOFhello, $USER EOF fi $ sh b.sh hello, kodango fi一定要是Tab鍵哦,空格也是不可以的,在vim里面還要注意如果設置了smarttab選項,行首插入的Tab鍵會替換成相應個數的空格(這里可以按ctrl+v tab插入實際的空格)。
關于這一節(jié)的內容,可以進一步參考[Redirection#here_documents [Bash Hackers Wiki]](http://wiki.bash-hackers.org/syntax/redirection#here_documents)。
4. 使用內置命令declare顯示腳本中定義的函數
declare的-F選項可以列出腳本中定義的函數名稱:
$ cat b.sh function one() {: }function two() {: }declare -F | sed 's/declare -f //' $ sh b.sh one two5. 嵌套函數還可以這么用
Bash中可以嵌套函數定義,即在一個函數中定義另外一個函數,例如:
[root@localhost ~]# cat nest.sh #!/bin/bashfunction out() {echo "out"function inner() {echo "inner"} }inner out inner這里out函數里面定義了inner函數,形成嵌套函數。但是,執(zhí)行上面的例子會出錯(nest.sh: line 12: inner: command not found),這是因為這是后inner函數還沒定義。一旦out函數執(zhí)行之后,inner函數就被定義了。整個例子的執(zhí)行結果是這樣的:
[root@localhost ~]# sh nest.sh nest.sh: line 12: inner: command not found out inner看到這里,你可能會想嵌套函數有什么用?事實上,在大多數情況下,我們基本不會用到嵌套函數。但是它并非一無是處,比如下面的例子就向我們展示了嵌套函數的神奇用法。
假設,我們要定義一個調試函數,同時需要一個開關控制該函數是否輸出調試日志,最簡單的寫法是:
function log() {if [ "$verbose" = "1" ]; thenecho "$@"fi }它可以完成任務,但是唯一美中不足的是,每次調用該函數都要判斷verbose的值是否為1。這時候可以使用嵌套函數來彌補這個不足:
#!/bin/bashverbose=${1:-1}function log() {if [ $verbose -eq 1 ]; thenfunction log() {echo "$@"}echo "$@"elsefunction log() {:}fi }log what is your name log my name is kodango上面的例子中,根據verbose的值定義了兩個同名的log函數來覆蓋之前的舊函數,以后調用的函數就都是后定義的函數了。
6. 刪除ps auxf | grep python結果中的grep進程
在shell腳本中,經常需要利用ps和grep命令一起在查找進程相關的信息,尤其是針對python/java/shell等腳本進程,因為pidof本身不大支持查找腳本進程對應的pid。
在用ps auxf | grep python的時候,一個很惱人的事情是,經常會出現多余的grep進程:
$ ps auxf | grep python kodango 18832 0.0 0.0 674192 10444 ? Sl 23:19 0:00 python test.py kodango 63860 0.0 0.0 61180 752 pts/2 S+ 23:28 0:00 grep python所以我們需要再加一個grep -v grep來排除它。
之前一直弄不明白為什么會這樣,今天在看BashPitfalls的時候,終于明白原因了,stackoverflow上也有一個回答解釋得很好。
shell在執(zhí)行以上命令的時候,其實創(chuàng)建了一個管道,并且fork了兩個子進程:ps auxf與grep python,并且將管道讀的這一端綁定到grep的標準輸入,管道寫的這一段綁定到ps的標準輸出。ps將自己的輸出寫到管道,grep從管道中讀取輸入。可能在這個時候,ps與grep是同時執(zhí)行的,所以ps的結果中也會包含grep進程的信息。
還有一個解決方法是巧用正則表達式:
$ ps auxf | grep [p]ython7. Shell如何實現timeout功能
有時候我們不希望某個命令執(zhí)行太久,所以如果在給定的時間內沒有完成,能夠殺掉這個命令對應的進程,這就是timeout功能,可惜bash沒有提供該功能。所以就得我們自己來實現。
實現代碼如下所示:
function timeout() {local time cmd pidif echo "$1" | grep -Eq '^[0-9]+'; thentime=$1shift && cmd="$@"elsetime=5cmd="$@"fi$cmd &pid=$!while kill -0 $pid &>/dev/null; dosleep 1let time-=1if [ "$time" = "0" ]; thenkill -9 $pid &>/dev/nullwait $pid &>/dev/nullfidone }假設有一個測試腳本sleep.sh:
$ cat sleep.sh echo "sleep $1 seconds" sleep $1 echo "awake from sleep"現在利用我們寫的timeout函數來達到超時kill功能:
$ time sh timeout.sh 2 'sh sleep.sh 100' sleep 100 secondsreal 0m2.005s user 0m0.002s sys 0m0.001s看最終執(zhí)行的時間,差不多就是2秒鐘。
上面timeout函數實現的代碼中,利用了兩個技巧:
- kill -0 $pid:發(fā)送信號0給進程,可以檢查進程是否存活,如果進程不存在或者沒有權限,則返回錯誤,錯誤碼為1;
- wait $pid &>/dev/null:等待某個進程退出返回,這樣相對比較優(yōu)雅,同時將錯誤重定向到黑洞,從而隱藏后臺進程被kill的錯誤輸出;
8. 利用/etc/inittab實現watchdog
還在為實現watch dog而頭疼嗎,其實inittab中已經包含了該功能。可以將自己的腳本或者程序寫到inittab文件中:
tt:2345:respawn:/home/kodango/sleep.sh 100然后執(zhí)行telinit q使其生效,ps看下該腳本是否已經在運行了,嘗試kill后,又會被起起來。
9. 慎用波浪號展開
在shell中對比下面兩種用法:
$ home1=~kodango $ home2="~kodango" $ echo -e "$home1\n$home2" /Users/kodango ~kodango第一個變量賦值,波浪號正確展開,所以我們得到了kodango用戶的家目錄地址;第二個變量,我們使用了雙引號,這個時候波波浪號并沒有展開。這是一個比較容易出錯的地方。
還有一點要注意的地方是,波浪號展開只在:或者=號后面才會執(zhí)行。所以:
$ path=1~kodango $ echo "$path" 1~kodango$ path=1:~kodango $ echo "$path" 1:/Users/kodango為什么要在:后面也可以展開呢?想想PATH的定義吧。
$. 最近淘到的一些實用的shell文章
- BashPitfalls - Greg's Wiki
- ProcessManagement - Greg's Wiki
- BashGuide - Greg's Wiki
- BashFAQ - Greg's Wiki
總結
以上是生活随笔為你收集整理的简洁的 Bash Programming 技巧(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UE4.26官方文档网页浏览录屏打包下载
- 下一篇: 探究VS2017运行库