Shell逐行處理文本求和,我人傻了...
本文轉(zhuǎn)載自微信公眾號「編程珠璣」,作者守望先生 。轉(zhuǎn)載本文請聯(lián)系編程珠璣公眾號。
假設(shè)要要計算文本test.data的第二列的數(shù)字之和:
- 1 12
 - 2 23
 - 3 34
 - 4 56
 
當(dāng)然你可能會這樣處理:
- awk '{s+=$2} END {print s}' test.data
 
很快就得到了結(jié)果。不過,本文要說的點與awk無關(guān)。我們通過另外一種方式來計算,即逐行分析處理的方式。
嘗試一
我們嘗試第一種方式,shell實現(xiàn)如下:
- #!/usr/bin/env bash
 - sum=0
 - cat test.data | while read line
 - do
 - temp_num=$(echo "$line" | cut -d ' ' -f 2)
 - sum=$(( $sum + $temp_num ))
 - done
 - echo "we get sum:$sum"
 
輸出結(jié)果:
- we get sum:0
 
這是為什么!為什么得到的結(jié)果會是0呢?
這事壞就壞在腳本中的|,眾所周知,這是一個管道命令,而這也就意味著,while循環(huán)的執(zhí)行結(jié)果都是在一個subshell中,一旦這個subsell退出了,它里面的結(jié)果也就沒有了。
其實這個問題利用有了這個神器,再也不怕shell寫得不對了中提到的工具很容易發(fā)現(xiàn):
- $ shellcheck myscript
 - Line 3:
 - cat test.data | while read line
 - ^-- SC2002: Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead.
 - ^-- SC2162: read without -r will mangle backslashes.
 - Line 6:
 - sum=$(( $sum + $temp_num ))
 - ^-- SC2030: Modification of sum is local (to subshell caused by pipeline).
 - ^-- SC2004: $/${} is unnecessary on arithmetic variables.
 - ^-- SC2004: $/${} is unnecessary on arithmetic variables.
 - Line 8:
 - echo "we get sum:$sum"
 - ^-- SC2031: sum was modified in a subshell. That change might be lost.
 - $
 
嘗試二
既然管道命令不建議用,那么我們使用下面的方式看看:
- #!/usr/bin/env bash
 - sum=0
 - for line in $(cat test.data)
 - do
 - echo "get line :$line"
 - temp_num=$(echo "$line" | cut -d ' ' -f 2)
 - sum=$(( $sum + $temp_num ))
 - done
 - echo "we get sum:$sum"
 
輸出結(jié)果:
- get line :1
 - get line :12
 - get line :2
 - get line :23
 - get line :3
 - get line :34
 - get line :4
 - get line :56
 - we get sum:135
 
從結(jié)果中看出,如果文本中存在空格或者tab等,則看似每次讀取一行,實際上是遇到空格,tab或換行就停止讀取了,并沒有達(dá)到我們的目的。
我們預(yù)期的應(yīng)該是遇到換行才停止讀取,為了達(dá)到這個目的,我們可以設(shè)置這個標(biāo)記,即通過設(shè)置IFS來達(dá)到目的。在上面的shell開頭加上:
- IFS=$'\n'
 
但是修改為這樣之后,在自己的系統(tǒng)上并沒有得到我想要的效果,有知道的讀者可以告知一下。
嘗試三
讓我們再換一種方式:
- #!/usr/bin/env bash
 - sum=0
 - while read line
 - do
 - echo "line $line"
 - temp_num=$(echo "$line" | cut -d ' ' -f 2)
 - sum=$(( $sum + $temp_num ))
 - done < "test.data"
 - echo "we get sum:$sum"
 
這種方式我們是能得到正確結(jié)果的。
當(dāng)然,如果你要讀取指定列,你還可以像下面這樣做:
- #!/usr/bin/env bash
 - sum=0
 - while read col1 col2
 - do
 - sum=$(( $sum + $col2 ))
 - done < "test.data"
 - echo "we get sum:$sum"
 
其中col1,col2就分別代表了第一列,第二列,使用的時候,可以直接使用對應(yīng)列的內(nèi)容。
但是,如果我們要讀取的內(nèi)容包括了轉(zhuǎn)義字符會怎么辦?例如:
- \n 12
 - \n 23
 - \n 34
 - \n 56
 
執(zhí)行結(jié)果:
- line
 - 12
 - line
 - 23
 - line
 - 34
 - line
 - 56
 - we get sum:125
 
從結(jié)果可以看到,雖然內(nèi)容能否讀取到,但是內(nèi)容被打印出來的時候,已經(jīng)變了,\被當(dāng)成轉(zhuǎn)義字符處理了,如果不想讓它轉(zhuǎn)義處理怎么辦?只需要加上-r參數(shù)即可:
- while read -r line
 
總結(jié)
在逐行處理文本過程中,主要關(guān)注以下幾種情況:
- 行中有空格,tab
 - 行中有轉(zhuǎn)義字符
 
另外,通過shellcheck工具也會發(fā)現(xiàn),它并不推薦for in file這種方式逐行處理文本:
- Line 3:
 - for line in $(cat test.data)
 - ^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop.
 
作者:守望,linux應(yīng)用開發(fā)者,目前在公眾號【編程珠璣】,分享Linux/C/C++/數(shù)據(jù)結(jié)構(gòu)與算法/工具等原創(chuàng)技術(shù)文章和學(xué)習(xí)資源
原文鏈接:https://mp.weixin.qq.com/s/rW0Va8g0U3apxNwR0Ziw9w















 
 
 










 
 
 
 