bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.htmlhtml
在寫while循環的時候,發現了一個問題,在while循環內部對變量賦值、定義變量、數組定義等等環境,在循環外面失效。shell
一個簡單的測試腳本以下:數組
#!/bin/bash
echo "abc xyz" | while read line
do
new_var=$line
done
echo new_var is null: $new_var?
執行結果證實,$new_var的結果是空值。bash
問題出在管道上。先看看下面的內容。函數
while循環的寫法有好幾種,它的語法結構爲:工具
while test_cmd_list; do cmd_list; done測試
但更常常地,while循環更多地用於讀取標準輸入的內容來實現循環。有如下幾種寫法:spa
寫法一:使用管道傳遞內容,這是用的最多、但卻最爛的寫法code
echo "abc xyz" | while read line htm
do
...
done
寫法二:
while read line
do
...
done <<< "abc xyz"
寫法三:從文件中讀取內容
while read line
do
...
done </path/filename
方法四:採用進程替換
while read var
do
...
done < <(cmd_list)
方法五:改變標準輸入
exec <filename
while read var
do
...
done
儘管寫法有多種,但它們並不等價。
陷阱一:
方法一中使用的是管道符號,這使得while語句在子shell中執行,這意味着while語句內部設置的變量、數組、函數等在循環外部都再也不生效。這正是文章開頭所說的陷阱。更簡單的:echo haha | a=5,在命令執行結束後,變量a的值也再也不是5。其他4種寫法,while語句都不在子shell中執行,所以都不會出現文章開頭所說的問題。
例如,使用寫法二的here string代替寫法一:
#!/bin/bash
while read line
do
new_var=$line
done <<< "abc xyz"
echo new_var is null: $new_var?
或者使用寫法四的進程替換:
#!/bin/bash
while read line
do
new_var=$line
done < <(echo "abc xyz")
echo new_var is null: $new_var?
陷阱二:
關於這幾種while循環的寫法,還有一點要注意:寫法一和寫法四傳遞數據的源都是一個單獨的進程,它們傳遞的數據一被while循環讀取,全部數據就丟棄了,而以實體文件做爲重定向傳遞的數據,while讀取了以後並不會丟棄。更標準一些的說法是,當標準輸入是非實體文件時(如管道傳遞的、獨立進程產生的)只供一次讀取;當標準輸入是直接重定向實體文件時,可供屢次讀取,但只要某一次讀取了該文件的所有內容就沒法再提供讀取。
舉個例子,老師讓咱們聽寫10個單詞,而我記憶力比較爛,他念完10個單詞時我可能只寫出了3個,剩餘的7個由於記不住就無法再寫出來。但若是我有小抄,我就能夠慢悠悠的一個一個寫,寫了一個還能夠等一段時間再寫第二個,但當我寫完10個以後,小抄這種東西就應該銷燬掉。
回到IO重定向上,不管什麼數據資源,只要被讀取完畢或者主動丟棄,那麼該資源就不可再得。①對於獨立進程傳遞的數據(管道左側進程產生的數據、進程替換產生的數據),它們都是"虛擬"數據,要不被一次讀取完畢,要不讀一部分剩餘的丟棄,這是真正的一次性資源。②而實體文件重定向傳遞的數據,只要不是一次性被所有讀取,它就是可再得資源,直到該文件數據所有讀取結束,這是"僞"一次性資源。其實①是進程間通訊時數據傳遞的現象,只不過這個問題容易被人忽略。
大多數時候,獨立進程傳遞的數據和文件直接傳遞的數據並無什麼區別,但有些命令能夠標記當前讀取到哪一個位置,使得下次該命令的讀取動做能夠從標記位置處恢復並繼續讀取,特別是這些命令用在循環中時。據我到目前的總結,這樣的命令有"head -n N"和"grep -m",經測試,tail並無位置標記的功能。
說了這麼多,如今終於開始驗證。下面的循環中,本該head每次讀取2行,但實際執行結果中總共就只讀取了一次2行。
[root@xuexi ~]# i=0 [root@xuexi ~]# cat /etc/fstab | while head -n 2 ; [[ "$i" -le 3 ]];do echo $i;let ++i;done # 0 1 2 3
使用進程替換的結果是同樣的。
[root@xuexi ~]# i=0 [root@xuexi ~]# while head -n 2; [[ "$i" -le 3 ]];do echo $i;let ++i;done < <(cat /etc/fstab) # 0 1 2 3
但若是是直接將實體文件進行重定向傳遞給head,則結果和上面的不同。
[root@xuexi ~]# i=0;while head -n 2 ; [[ "$i" -le 3 ]];do echo $i;let ++i;done < /etc/fstab # 0 # /etc/fstab # Created by anaconda on Thu May 11 04:17:44 2017 1 # # Accessible filesystems, by reference, are maintained under '/dev/disk' 2 # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info # 3 UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8 / xfs defaults 0 0 UUID=367d6a77-033b-4037-bbcb-416705ead095 /boot xfs defaults 0 0
能夠看到結果中每次讀取兩行並echo一次"$i",並且每次讀取的兩行是不一樣的,後一次讀取的兩行是從前一次讀取結束的地方開始的,這是由於head有"讀取到指定行數後作上位置標記"的功能。
要肯定命令、工具是否具備作位置標記的能力,只需像下面例子同樣作個簡單的測試。以head和sed爲例,即便sed的"q"命令能讓sed匹配到內容就退出,但卻不作位置標記,並且數據資源使用一次就丟棄。
[root@xuexi ~]# (head -n 2;head -n 2) </etc/fstab # # /etc/fstab # Created by anaconda on Thu May 11 04:17:44 2017
[root@xuexi ~]# (sed -n /default/'{p;q}' ;sed -n /default/'{p;q}') </etc/fstab UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8 / xfs defaults 0 0
其實在實際應用過程當中,這根本就不是個問題,由於搜索和處理文本數據的工具雖然很多,但絕大多數都是用一次文本就"丟"一次,幾乎不可能所以而產生問題。之因此說這麼多廢話,主要是想說上面的5種while寫法中,使用最普遍的寫法一雖然最簡單、方便,但實際上是最爛的一種。