在日常工作中客情,學(xué)會使用shell編程溺森,可以在很大程度上替代手工重復(fù)性質(zhì)的工作巫橄,提高工作效率淘邻。從這點(diǎn)上來說,了解shell中循環(huán)的寫法非常關(guān)鍵湘换。下面介紹shell中的while循環(huán)和for循環(huán)宾舅。
1统阿、兩種循環(huán)基本寫法
常見的while和for循環(huán)的寫法,大概有如下幾種:
(1) 通過輸入重定向到while循環(huán)
while read line
do
echo $line
done < file(待讀取的文件)
(2) 通過cat命令輸出重定向到while循環(huán)
cat file(待讀取的文件) | while read line
do
echo $line
done
(3) for循環(huán)讀取命令輸出
for line in `cat file(待讀取的文件)`
do
echo $line
done
2筹我、兩種循環(huán)的區(qū)別
按照我的理解扶平,準(zhǔn)確的說,上面例子中while和for循環(huán)的區(qū)別在于:while循環(huán)會將每行的內(nèi)容讀入到line變量蔬蕊;for循環(huán)中结澄,將讀入的內(nèi)容以IFS(shell中的環(huán)境變量,Internal Field Seperator岸夯,字段分隔符)為界分隔麻献,然后將各個分隔開的內(nèi)容,逐一讀入變量line猜扮。本質(zhì)上說勉吻,for循環(huán)讀取的是字段,只不過可以設(shè)置IFS為\n
這樣能夠逐行讀取旅赢。
為了方便測試齿桃,我們用echo命令來實(shí)現(xiàn)多行文字的輸出。其中煮盼,echo命令的-e
選項(xiàng)短纵,意思就是可以識別轉(zhuǎn)義字符能夠輸出行分隔符。如下例:
$ echo -e "a 12\nb 10"
a 12
b 10
$
(1) while逐行讀文件
$ echo -e "a 12\nb 10" | while read line
> do
> echo $line
> done
a 12
b 10
$
(2) for循環(huán)的默認(rèn)行為
$ for line in `echo -e "a 12\nb 10"`
> do
> echo $line
> done
a
12
b
10
$
(3) 通過改變IFS實(shí)現(xiàn)for循環(huán)按行讀入
$ IFS=$'\n'
$ for line in `echo -e "a 12\nb 10"`
> do
> echo $line
> done
a 12
b 10
$
除了上面常見循環(huán)的寫法孕似,while循環(huán)在逐行讀入的同時踩娘,還能夠根據(jù)IFS將整行的內(nèi)容分隔成多個字段,依次賦值給read后跟的變量名喉祭。如果變量數(shù)目多余字段的實(shí)際個數(shù)养渴,少的那些變量值為空;如果變量的數(shù)目少于字段實(shí)際個數(shù)泛烙,最后一個變量對所有后面的字段照單全收理卑。下面是一個例子:
$ echo -e "Tom 13\nLily 10 120cm\nJohn" | while read name age
> do
> echo "${name}: ${age}"
> done
Tom: 13
Lily: 10 120cm
John:
$
3、一個簡單的shell循環(huán)應(yīng)用
假定有這樣一個場景蔽氨,需要在一個目錄中藐唠,查找好多關(guān)鍵詞。如果用shell搞定鹉究,我們就需要先將待搜索的關(guān)鍵詞寫入一個文件宇立,比如keyword.txt,每行一個關(guān)鍵詞自赔。然后妈嘹,寫一個腳本讀這個文件,取出每個關(guān)鍵詞绍妨,然后用grep命令查找润脸。下面是一個參考腳本的例子:
keyword_file='keyword.txt'
search_dir='/xx/path/'
result_file=result.txt
echo "Results:" | tee $result_file
cat $keyword_file | while read keyword
do
echo "${keyword}:" | tee -a $result_file
#word match, recursively search in directory and sub directory. only .java file will be searched. case insensitive. -l means only list file name
grep -irw --include="*.java" "$keyword" "$search_dir" -l | tee -a $result_file
echo "" | tee -a $result_file
done
運(yùn)行結(jié)果如下:
$ cat keyword.txt
Polymerize
SortMeta
DataTube
$ sh search.sh
Results:
Polymerize:
/xx/path/src/com/poly/merge/test/TestMergeSortDesc.java
/xx/path/src/com/poly/merge/test/TestMergeSortDescMultiSort.java
/xx/path/src/com/poly/merge/basic/Polymerize.java
SortMeta:
/xx/path/src/com/poly/merge/test/TestMergeSort.java
/xx/path/src/com/poly/merge/test/TestMergeSort16.java
DataTube:
/xx/path/UnitTest/com/poly/merge/basic/PolymerizeTest.java
/xx/path/src/com/poly/merge/test/TestMergeSort.java
/xx/path/src/com/poly/merge/test/DataTubeImp16.java
/xx/path/src/com/poly/merge/basic/Polymerize.java
/xx/path/src/com/poly/merge/basic/DataTube.java
$
注意:這里的keyword文件涉及到按行讀文件柬脸,所以這里要注意行分隔符必須是Unix/OS X
風(fēng)格的LF
, 也就是\n
。如果文件的行分隔符是Windows風(fēng)格的CRLF
毙驯,也就是\r\n
倒堕,會什么都找不到。(我在windows電腦上爆价,將keyword文件分隔符設(shè)置為CRLF時垦巴,用git bash運(yùn)行搜索腳本,然后發(fā)現(xiàn)什么都搜不到铭段,最終發(fā)現(xiàn)是行分隔符的問題魂那。將行分隔符換成LF之后,搜索就一切正常了稠项。)
4涯雅、Shell腳本按行讀文件是行分隔符的坑
在用Shell編寫按行讀文件的腳本時,經(jīng)常會遇到行分隔符的坑展运,現(xiàn)象看起來十分詭異活逆。這里做下解釋。
說明:
在vim中可以通過命令:set ff=xxx
來改變文本文件的行分隔符(ff
是fileformat
的縮寫)拗胜。其中xxx
可以是dos
(代表windows風(fēng)格的行分隔符蔗候,即CRLF,\r\n
)埂软、unix
(代表Unix和OS X風(fēng)格的行分隔符锈遥,即LF,\n
)或者mac
(代表Mac風(fēng)格的行分隔符勘畔,即CR所灸,\r
)。
假設(shè)有一個文件有如下內(nèi)容炫七,行分隔符是windows格式的\r\n
:
$ cat text.txt
in the house, there is a little horse.
finally, it won over a long race near the small inn.
all above is just a makeup story.
vaginally
寫如下的shell腳本test.sh
爬立,按行讀該文件,然后輸出:
cat text.txt | while read line
do
echo AAAA${line}BB;
done
輸出如下:
$ sh test.sh
BBAAin the house, there is a little horse.
BBAAfinally, it won over a long race near the small inn.
BBAAall above is just a makeup story.
BBAAvaginally
這個輸出十分詭異万哪,AAAA
和BB
并沒有按照預(yù)期的顯示在每行內(nèi)容的兩側(cè)侠驯,BB
反而覆蓋掉了AAAA
中的前兩個AA
。原因就是因?yàn)檫@里按行讀的時候奕巍,分隔符是\n
吟策,但是文本文件的行分隔符是\r\n
。這樣的止,就導(dǎo)致每行讀出的內(nèi)容最后檩坚,有一個\r
。而這個字符的意思是回車,也就是將光標(biāo)移動到行開頭效床。這樣,以第一行為例权谁,輸出BBBBin the house, there is a little horse.
之后剩檀,輸出\r
將光標(biāo)移動到行開頭,然后輸出AA
就蓋掉了最前面的BB
旺芽,出現(xiàn)了前面的效果沪猴。
\r
字符也不是所有時候都是坑,比如我們想在命令行顯示一個跳動的時間采章,就要用到這個字符运嗜,讓后面的輸出蓋掉前面的。下面是一個例子time.sh
:
i=0
while [ $i -lt 30 ];
do echo -ne "\r"`date` #you should remove new line too;
sleep 1;
i=$(($i + 1));
done
我在MacOS自帶的命令行上執(zhí)行上面的腳本悯舟,如果直接將腳本內(nèi)容復(fù)制到命令行執(zhí)行担租,這時候,會得到一個跳動的時間抵怎,不會輸出多行奋救。
如果通過命令sh time.sh
來執(zhí)行,輸出效果是逐行輸出反惕。這里懷疑是sh
指定的shell不是Bash
尝艘,可能echo命令對于選項(xiàng)ne
的支持有問題(這里可以看出echo的可移植性不好,在腳本編寫的時候姿染,盡量使用printf)背亥。,看了下悬赏,果然是:
$ which sh
/bin/sh
通過添加執(zhí)行權(quán)限和手動指定Bash執(zhí)行狡汉,都可以達(dá)到跳動的效果,如下:
$ chmod a+x time.sh
$ ./time.sh
2019年 8月14日 星期三 09時03分26秒 $ /bin/bash time.sh
2019年 8月14日 星期三 09時03分35秒 $