Linux越來越重要,對于開發(fā)者來說學習必要的bash技術必不可少,對運維來說更是如此。學習shell可以參考很多學習材料和圖書,比如ChinaUnix論壇網中人大哥總結的《Shell十三問》的帖子,《ABS 指導》(《Advanced Bash-Scripting Guide》),《linux shell腳本攻略》等大家可以參考。本文蟲蟲給大家一些常用的shell技巧分享給大家,希望可以對大家有所幫助。
首先我們要說下bash腳本的啟動,Linux中系統初始化都是按照一定順序加載各個文件初始化腳本(Shell腳本)。腳本所在文件啟動順序很重要,下面這個圖顯示了Linux系統個初始化腳本和環(huán)境變量的加載順序,包括了bash、sh和zsh等常見的shell。
bash的加載遵循上圖,從上到下順序執(zhí)行加載,尤其要注意non-login、非交互(上面藍色線)執(zhí)行,它不會加載很多腳本,比如/etc/profile(總profile),/etc/bash.bashrc(總basrc),和個人的.profile, .bash_profie,.bash_login和.bashrc等。所以如果你腳本處于這樣環(huán)境下(比如cron腳本),你就要在腳本中自我設置一些環(huán)境變量。這樣就可以避免由于環(huán)境變量導致的一些莫名其妙的的問題,比如寫的好腳本為什么登陸就可以執(zhí)行,放到cron里面自動執(zhí)行就不行。
我們知道Linux下一切皆文件,包括硬件設備,其中三個特殊的文件句斌,標準輸入(stdin)、標準輸出(stdout)和標準錯誤(stderr),其句柄號為三個整數0,1和2。
< :用于重定向改變標準輸入(stdin)。
0< :0< 和<一樣 ,<符號之前需要制定一個FD,默認為0。
<< :輸入一段文本,直到讀到<<后指定的內容,這就是常說的heredoc。
> :用于重定向改變標準輸出(stdout)。
1> :1> 和>一樣 ,> 默認的FD為1。
2> :改變標準錯誤(stderr)。
>> :重定向輸出到文件,追加到文件末尾。
set -o noclobber :設置 > 不能覆蓋已經存在的文件。
set +o noclobber :這支 > 可以覆蓋已經存在的文件。
>| :設置set -o noclobber后,使用 >| 可以臨時覆蓋已經存在的文件。
這兩個文件是兩個比較特殊設備。/dev/null,或稱空設備,它會丟棄一切寫入它的數據,但是讀取它則會拋出錯誤。在shell中常用它來表示放棄執(zhí)行的結果(或者錯誤),這樣只關注于程序執(zhí)行,比如在cron中的任務,輸出和錯誤都沒有意義。
command > /dev/null
可以將stdout、stderr都重定向輸出到/dev/null:
command > /dev/null 2>&1 或者
command &> /dev/null
command >& /dev/null
/dev/zero 設備用于提供無限制的空null內供讀取。當讀取該設備(文件)時候,它會提供無限的空字符 null。我們可以從/dev/zero 讀取任意數量的 null 字符。和 /dev/null 不同,/dev/zero主要用于作為null數據源供讀取,當然/dev/zero也支持寫入,可以用做數據黑洞,。
/dev/zero主要用途是提供字符流來初始化數據存儲,也就是使用空字符覆蓋目標數據。還可以用來一個特定大小的空白文件。
比如用空白文件覆蓋分區(qū),刪除分區(qū)的數據可以用(慎用!):
dd if=/dev/zero of=/dev/<destination partition>
創(chuàng)建一個一個1M的文件,可以使用
dd if=/dev/zero of=cc count=1024 bs=1024
反引號(Tab鍵上面的鍵)和$(),兩者基本上類似,在shell中都表示執(zhí)行命令。
echo`ls`
echo $(ls)
最常用的用法是將一些命令結果輸出傳遞個其他命令作為參數。比如要查詢當前目錄和子目錄下所有PHP中的包含'eval'函數(似疑木馬),可以用下面命令:
grep eval `find -type f -name '*.php'`
兩者主要的區(qū)別在于嵌套更簡單。
對``來說要實現嵌套就要增加很多反斜杠來轉義,比如:
echo `echo \`echo \\\`echo hello,Chongchong\!\\\`\``
而用$()就簡介的多了:
echo $(echo $(echo $(echo hello,Chongchong\!)))
這是個很有意義的操作符,他有點類似于$(),而對括弧中命令的輸出重用。但是在<上下文中其輸出結果被會視為文件,可以用于以文件名為參數的命令中。
比如我們要執(zhí)行下面一些例子
grep keys file1> /tmp/a
grep keys file2> /tmp/b
diff /tmp/a /tmp/b
用<()以上可以用oneline單行來代替:
diff <(grep keys file1)<(grep keys file2)
bash引用比較復雜,我們先從簡單的開始。
首先,引號中的變量:
A='123'
echo '$A'
echo '$A'
這個結果很簡單,雙引號會解釋變量,輸出為其值123,而單引號不會解釋,輸出為$A。
下面三個echo 都會打印啥呢?
mkdir -p tmp
cd tmp
touch a
echo *
echo '*'
echo '*'
Bash中還有很多引用方式的快捷操作符,其中一些可以極大的提高我們的工作效率
!$
表示重復最后一個命令的最后一個參數。如果你正在處理一個文件,通過該操作符可以在命令后重新引入參數,從而節(jié)省大量重復輸入。
grep xxx /long/path/to/some/file/or/other.txt
vim !$
!:1-$
該操作符,更有意思,它獲取上一個命令的所有參數并將它們引入。所以:
grep isthere /long/path/to/some/file/or/other.txt
egrep !:1-$
fgrep !:1-$
!表示'查看上一個命令',:是一個分隔符,1表示'取第一個單詞', -表示范圍'直到',$表示'最后一個單詞'。
我們可以用!*實現同樣的目的。但是基于上面規(guī)則,我們就可以隨意定制參數的范圍了。比如如使用!:2-3。
:h
如果把該操作符放在一個文件名后面,它會刪除文件夾以外的文件名稱。比如:
grep XXX /long/path/to/some/file/or/other.txt
cd !$:h
通配符合正則表達式中都會用*,看起來很相似,但它們差異很大
請解釋下面這個命令:
rename -n 's/(.*)/HEAD$1/' *
上面兩個星號表達意思完全不同:
第一個由于用單引號括住,shell不會對其解釋,作為參數都提交給rename命令。rename會對其解釋 ,而s/(.*)/HEAD$1/是一個正則替換。在正則表達式中 *表示對前面字符的0個或者多個模式。.表示一個字符,所以.*表示匹配所有內容;()表示對括住內容進行引用,在替換后面部分用\1或者$1表示它。
第二個會被shell解釋,*在shell中表示通配符,表示當前工作文件夾中所有文件的列表替換。
所以整個命令表示將文件下所有文件名都增加上HEAD 前綴。請嘗試下面兩個命令:
ls *
ls .*
第二個看起來更像是一個正則表達式,但是實際是什么樣的呢?請你嘗試對輸出解釋下。
請嘗試執(zhí)行一下語句會輸出什么?
if grep XXX /dev/null
then
echo Chongchong
else
echo lo
fi
grep的返回代碼使得像這樣的代碼更直觀地作為其作為退出狀態(tài)解釋時候帶來的副作用。
下面兩個語句的輸出呢?
if [ $(grep XXX /dev/null) = '' ]
then
echo -n Chongchong
else
echo -n lo
fi
if [[ $(grep XXX /dev/null) = '' ]]
then
echo -n Chongchong
else
echo -n lo
fi
[是測試的原始形式,然后[[引入了更靈活和直觀的方式。在上面的第一個if塊中,if語句因為$(grep XXX /dev/null)被評估為空,導致這種比較:[=''],這會拋出錯誤,針對這種情況就需要使用[[]]來避免這種異常,還有一個常用的技巧是使用:
if [x$(grep XXX /dev/null)='x']
所以,如果命令沒有返回任何內容,它仍然可以正常運行,而不會拋出異常,中斷執(zhí)行。
我們知道每次shell執(zhí)行,推出是都會給shell一個狀態(tài)碼。
如果命令執(zhí)行正常,則會返回給shell狀態(tài)碼0。如果不成功,則會得到一個非零代碼,用來表具體錯誤的種類,其中 1表示'一般錯誤',還有其他更多信息,可以在程序中具體定義。狀態(tài)碼,可以通過$?查看。但有些命令會設置有特殊設置比如grep,請嘗試下面的命令:
grep XXX /dev/null
echo $?
grep中使用退出代碼來指示它是否匹配。
Bash中也提供了可配置的選項,可以即時設置環(huán)境變量等。
set -e
如果任何命令返回非零退出狀態(tài),則從腳本退出。
set -X
會輸出在運行時運行的命令。
所以腳本可能會使用這樣的開頭:
#!/bin/bash
set -e
set -x
grep XXX /dev/null
echo $?
如果要寫一個較大的程序,涉及的輸入比較復雜,則需要使用getopts來簡化你的工作,用他來幫你做輸入和參數處理的工作,比如下面一個例子:
getopts后面跟的是參數列表字符串,每個字母表示一個選項,帶:選項表示選項還會有值,比如上面例子中對應的-j /home/soft/java 和-m /home/soft/maven 。而getopts字符串中沒有跟隨:的字母就是開關型選項,不需要指定值,等同于true/false,只要帶上了這個參數就是true。
getopts是shell內部命令,可以在shell腳本中直接使用,同時shell也支持一個外部命令getopt有很多發(fā)行版本也都自帶。詳細可以搜索參考具體文檔,本文不在贅述。
拋磚引玉,本文中列出了shell中一些常見的技巧和容易混淆的知識點,希望能對大家有所幫助,如果大家有更好的想法技巧也可以一起分享出來,一起參考學習。
聯系客服