本篇內(nèi)容均摘自《Linux命令行與shell腳本編程大全》杜跷,個人認(rèn)為需要重點(diǎn)學(xué)習(xí)的章節(jié)。【免費(fèi)】Linux命令行與Shell腳本編程大全 第3版 PDF全本 21MB 百度網(wǎng)盤下載 - 今夕是何夕 - 博客園
重復(fù)執(zhí)行一系列命令在編程中很常見搀别。通常你需要重復(fù)一組命令直至達(dá)到某個特定條件退个,比如處理某個目錄下的所有文件、系統(tǒng)上的所有用戶或是某個文本文件中的所有行妄呕。bash shell提供了for命令,允許你創(chuàng)建一個遍歷一系列值的循環(huán)嗽测。每次迭代都使用其中一個值來執(zhí)行已定義好的一組命令绪励。下面是bash shell中for命令的基本格式:
for var in list
do
commands
done
在list參數(shù)中肿孵,你需要提供迭代中要用到的一系列值∈栉海可以通過幾種不同的方法指定列表中的值停做。在每次迭代中,變量var會包含列表中的當(dāng)前值大莫。第一次迭代會使用列表中的第一個值蛉腌,第二次迭代使用第二個值,以此類推只厘,直到列表中的所有值都過一遍烙丛。在do和done語句之間輸入的命令可以是一條或多條標(biāo)準(zhǔn)bash shell命令。在這些命令中羔味,$var變量包含著這次迭代對應(yīng)的當(dāng)前列表項(xiàng)中的值河咽。
[說明] 也可以將do語句和for語句放在同一行,但必須用分號將其同列表中的值分開:
for var in list; do
讀取列表中的值
for命令最基本的用法就是遍歷for命令自身所定義的一系列值赋元。
$ cat test1
#!/bin/bash
for test in Alabama Alaska Arizona Arkansas California Colorado
do
echo The next state is $test
done
$ ./test1
The next state is Alabama
The next state is Alaska
The next state is Arizona
The next state is Arkansas
The next state is California
The next state is Colorado
每次for命令遍歷值列表忘蟹,它都會將列表中的下個值賦給test變量。 test變量可以像for命令語句中的其他腳本變量一樣使用搁凸。在最后一次迭代后媚值, $test變量的值會在shell腳本的剩余部分一直保持有效。它會一直保持最后一次迭代的值(除非你修改了它)护糖。
$ cat test1b
#!/bin/bash
for test in Alabama Alaska Arizona Arkansas California Colorado
do
echo "The next state is $test"
done
echo "The last state we visited was $test"
test=Connecticut
echo "Wait, now we're visiting $test"
$ ./test1b
The next state is Alabama
The next state is Alaska
The next state is Arizona
The next state is Arkansas
The next state is California
The next state is Colorado
The last state we visited was Colorado
Wait, now we're visiting Connecticut
$test變量保持了其值褥芒,也允許我們修改它的值,并在for命令循環(huán)之外跟其他變量一樣使用嫡良。
讀取列表中的復(fù)雜值
有時會遇到難處理的數(shù)據(jù)喂很。下面是給shell腳本程序員帶來麻煩的典型例子:
$ cat badtest1
#!/bin/bash
for test in I don't know if this'll work
do
echo "word:$test"
done
$ ./badtest1
word:I
word:dont know if thisll
word:work
shell看到了列表值中的單引號并嘗試使用它們來定義一個單獨(dú)的數(shù)據(jù)值,有兩種辦法可解決這個問題:
1.使用轉(zhuǎn)義字符(反斜線)來將單引號轉(zhuǎn)義;
2.使用雙引號來定義用到單引號的值皆刺。
$ cat test2
#!/bin/bash
for test in I don\'t know if "this'll" work
do
echo "word:$test"
done
$ ./test2
word:I
word:don't
word:know
word:if
word:this'll
word:work
記住for循環(huán)假定每個值都是用空格分割的少辣。如果有包含空格的數(shù)據(jù)值,你就陷入麻煩了羡蛾。
$ cat badtest2
#!/bin/bash
for test in Nevada New Hampshire New Mexico New York North Carolina
do
echo "Now going to $test"
done
$ ./badtest1
Now going to Nevada
Now going to New
Now going to Hampshire
Now going to New
Now going to Mexico
Now going to New
Now going to York
Now going to North
Now going to Carolina
這不是我們想要的結(jié)果漓帅。 for命令用空格來劃分列表中的每個值。如果在單獨(dú)的數(shù)據(jù)值中有空格痴怨,就必須用雙引號將這些值圈起來忙干。
$ cat test3
#!/bin/bash
for test in Nevada "New Hampshire" "New Mexico" "New York"
do
echo "Now going to $test"
done
$ ./test3
Now going to Nevada
Now going to New Hampshire
Now going to New Mexico
Now going to New York
現(xiàn)在for命令可以正確區(qū)分不同值了。另外要注意的是浪藻,在某個值兩邊使用雙引號時捐迫, shell并不會將雙引號當(dāng)成值的一部分。從變量讀取列表通常shell腳本遇到的情況是爱葵,你將一系列值都集中存儲在了一個變量中施戴,然后需要遍歷變量中的整個列表反浓。也可以通過for命令完成這個任務(wù)。
$ cat test4
#!/bin/bash
list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Connecticut"
for state in $list
do
echo "Have you ever visited $state?"
done
$ ./test4
Have you ever visited Alabama?
Have you ever visited Alaska?
Have you ever visited Arizona?
Have you ever visited Arkansas?
Have you ever visited Colorado?
Have you ever visited Connecticut?
list變量包含了用于迭代的標(biāo)準(zhǔn)文本值列表赞哗。注意雷则,代碼還是用了另一個賦值語句向$list變量包含的已有列表中添加(或者說是拼接)了一個值。這是向變量中存儲的已有文本字符串尾部添加文本的一個常用方法肪笋。
從命令讀取值
生成列表中所需值的另外一個途徑就是使用命令的輸出月劈。可以用命令替換來執(zhí)行任何能產(chǎn)生輸出的命令藤乙,然后在for命令中使用該命令的輸出猜揪。
$ cat test5
#!/bin/bash
file="states"
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
$ cat states
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia
$ ./test5
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
Visit beautiful Arkansas
Visit beautiful Colorado
Visit beautiful Connecticut
Visit beautiful Delaware
Visit beautiful Florida
Visit beautiful Georgia
這個例子在命令替換中使用了cat命令來輸出文件states的內(nèi)容。你會注意到states文件中每一行有一個州坛梁,而不是通過空格分隔的而姐。 for命令仍然以每次一行的方式遍歷了cat命令的輸出,假定每個州都是在單獨(dú)的一行上罚勾。但這并沒有解決數(shù)據(jù)中有空格的問題。如果你列出了一個名字中有空格的州吭狡, for命令仍然會將每個單詞當(dāng)作單獨(dú)的值尖殃。說明: test5的代碼范例將文件名賦給變量,文件名中沒有加入路徑划煮。這要求文件和腳本位于同一個目錄中送丰。如果不是的話,你需要使用全路徑名(不管是絕對路徑還是相對路徑)來引用文件位置弛秋。
更改字段分隔符
造成這個問題的原因是特殊的環(huán)境變量IFS器躏,叫作內(nèi)部字段分隔符( internal field separator)。IFS環(huán)境變量定義了bash shell用作字段分隔符的一系列字符蟹略。默認(rèn)情況下登失, bash shell會將下列字符當(dāng)作字段分隔符:空格,制表符(\t)挖炬,換行符(\n)揽浙。如果bash shell在數(shù)據(jù)中看到了這些字符中的任意一個,它就會假定這表明了列表中一個新數(shù)據(jù)字段的開始意敛。在處理可能含有空格的數(shù)據(jù)(比如文件名)時馅巷,這會非常麻煩,就像你在上一個腳本示例中看到的草姻。要解決這個問題钓猬,可以在shell腳本中臨時更改IFS環(huán)境變量的值來限制被bash shell當(dāng)作字段分隔符的字符。例如撩独,如果你想修改IFS的值敞曹,使其只能識別換行符账月,那就必須這么做:
IFS=$'\n'
將這個語句加入到腳本中,告訴bash shell在數(shù)據(jù)值中忽略空格和制表符异雁。對前一個腳本使用這種方法捶障,將獲得如下輸出。
$ cat test5b
#!/bin/bash
file="states"
IFS=$'\n'
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
$ ./test5b
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
Visit beautiful Arkansas
Visit beautiful Colorado
Visit beautiful Connecticut
Visit beautiful Delaware
Visit beautiful Florida
Visit beautiful Georgia
Visit beautiful New York
Visit beautiful New Hampshire
Visit beautiful North Carolina
現(xiàn)在纲刀, shell腳本就能夠使用列表中含有空格的值了项炼。
警告:在處理代碼量較大的腳本時,可能在一個地方需要修改IFS的值示绊,然后忽略這次修改锭部,在腳本的其他地方繼續(xù)沿用IFS的默認(rèn)值。一個可參考的安全實(shí)踐是在改變IFS之前保存原來的IFS值面褐,之后再恢復(fù)它拌禾。這種技術(shù)可以這樣實(shí)現(xiàn):
IFS.OLD=$IFS
IFS=$'\n' #在代碼中使用新的IFS值
IFS=$IFS.OLD
這就保證了在腳本的后續(xù)操作中使用的是IFS的默認(rèn)值。
還有其他一些IFS環(huán)境變量的絕妙用法展哭。假定你要遍歷一個文件中用冒號分隔的值(比如在/etc/passwd文件中)湃窍。你要做的就是將IFS的值設(shè)為冒號。
IFS=:
如果要指定多個IFS字符匪傍,只要將它們在賦值行串起來就行您市。
IFS=$'\n':;"
這個賦值會將換行符、冒號役衡、分號和雙引號作為字段分隔符茵休。如何使用IFS字符解析數(shù)據(jù)沒有任何限制。
用通配符讀取目錄
最后手蝎,可以用for命令來自動遍歷目錄中的文件榕莺。進(jìn)行此操作時,必須在文件名或路徑名中使用通配符棵介。它會強(qiáng)制shell使用文件擴(kuò)展匹配钉鸯。文件擴(kuò)展匹配是生成匹配指定通配符的文件名或路徑名的過程。如果不知道所有的文件名邮辽,這個特性在處理目錄中的文件時就非常好用亏拉。
$ cat test6
#!/bin/bash
for file in /home/rich/test/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
$ ./test6
/home/rich/test/dir1 is a directory
/home/rich/test/myprog.c is a file
/home/rich/test/myprog is a file
/home/rich/test/myscript is a file
/home/rich/test/newdir is a directory
/home/rich/test/newfile is a file
/home/rich/test/newfile2 is a file
/home/rich/test/testdir is a directory
/home/rich/test/testing is a file
/home/rich/test/testprog is a file
/home/rich/test/testprog.c is a file
for命令會遍歷/home/rich/test/*輸出的結(jié)果。該代碼用test命令測試了每個條目(使用方括號方法)逆巍,以查看它是目錄(通過-d參數(shù))還是文件(通過-f參數(shù))及塘。
注意:我們在這個例子的if語句中做了一些不同的處理:
if [ -d "$file" ]
在Linux中,目錄名和文件名中包含空格當(dāng)然是合法的锐极。要適應(yīng)這種情況笙僚,應(yīng)該將$file變量用雙引號圈起來。如果不這么做灵再,遇到含有空格的目錄名或文件名時就會有錯誤產(chǎn)生肋层。
./test6: line 6: [: too many arguments
./test6: line 9: [: too many arguments
在test命令中亿笤, bash shell會將額外的單詞當(dāng)作參數(shù),進(jìn)而造成錯誤栋猖。
也可以在for命令中列出多個目錄通配符净薛,將目錄查找和列表合并進(jìn)同一個for語句。
$ cat test7
#!/bin/bash
for file in /home/rich/.b* /home/rich/badtest
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
else
echo "$file doesn't exist"
fi
done
$ ./test7
/home/rich/.backup.timestamp is a file
/home/rich/.bash_history is a file
/home/rich/.bash_logout is a file
/home/rich/.bash_profile is a file
/home/rich/.bashrc is a file
/home/rich/badtest doesn't exist
for語句首先使用了文件擴(kuò)展匹配來遍歷通配符生成的文件列表蒲拉,然后它會遍歷列表中的下一個文件肃拜。可以將任意多的通配符放進(jìn)列表中雌团。