您好,登錄后才能下訂單哦!
這篇文章給大家介紹Linux實踐中使用重定向和管道符遇到的坑有哪些,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
我很喜歡 Linux 系統,尤其是 Linux 的一些設計很漂亮,比如可以將一些復雜的問題分解成若干小問題,通過管道符和重定向機制靈活地用現成的工具解決,寫成 shell 腳本就很高效。
先說第一個問題,執行如下命令會發生什么?
$ cat file.txt > file.txt
讀取再寫入同一個文件,感覺什么也不會發生對吧?
實際上,上述命令運行的結果是清空file.txt文件中的內容。
PS:有的 Linux 發行版可能會直接報錯,可以執行cat < file.txt > file.txt繞開這個檢測。
前文 Linux 進程和文件描述符 說過,程序本身沒有必要關心自己的標準輸入/輸出指向哪里,是 shell 通過管道符和重定向符號修改了程序的標準輸入/輸出的位置。
所以執行cat file.txt > file.txt這個命令時,shell 會先打開file.txt,由于重定向符號是>,所以文件中的內容會被清空,然后 shell 將cat命令的標準輸出設置為file.txt,這時候cat命令才開始執行。
也就是如下過程:
1、shell 打開file.txt并清空其內容。
2、shell 將cat命令的標準輸出指向file.txt文件。
3、shell 執行cat命令,讀了一個空文件。
4、cat命令將空字符串寫入標準輸出(file.txt文件)。
所以,最后的結果就是file.txt變成了空文件。
我們知道,>會清空目標文件,>>會在目標文件尾部追加內容,那么如果將重定向符>改成>>會怎樣呢?
$ echo hello world > file.txt # 文件中只有一行內容 $ cat file.txt >> file.txt # 這個命令會死循環
file.txt中首先被寫入一行內容,執行cat file.txt >> file.txt后預期的結果應該是兩行內容。
但是很遺憾,運行結果并不符合預期,而是會死循環不斷向file.txt中寫入 hello world,文件很快就會變得很大,只能用 Control+C 停止命令。
這就有意思了,為什么會死循環呢?其實稍加分析就可以想到原因:
首先要回憶cat命令的行為,如果只執行cat命令,就會從命令行讀取鍵盤輸入的內容,每次按下回車,cat命令就會回顯輸入,也就是說,cat命令是逐行讀取數據然后輸出數據的。
那么,cat file.txt >> file.txt命令的執行過程如下:
1、打開file.txt,準備在文件尾部追加內容。
2、將cat命令的標準輸出指向file.txt文件。
3、cat命令讀取file.txt中的一行內容并寫入標準輸出(追加到file.txt文件中)。
4、由于剛寫入了一行數據,cat命令發現file.txt中還有可以讀取的內容,就會重復步驟 3。
以上過程,就好比一邊遍歷列表,一遍往列表里追加元素一樣,永遠遍歷不完,所以導致我們的命令死循環。
我們經常會遇到這樣的需求:截取文件的前 XX 行,其余的都刪除。
在 Linux 中,head命令可以完成截取文件前幾行的功能:
$ cat file.txt # file.txt 中有五行內容 1 2 3 4 5 $ head -n 2 file.txt # head 命令讀取前兩行 1 2 $ cat file.txt | head -n 2 # head 也可以讀取標準輸入 1 2
如果我們想保留文件的前 2 行,其他的都刪除,可能會用如下命令:
$ head -n 2 file.txt > file.txt
但是這就犯了前文說的錯誤,最后file.txt會被清空,不能實現我們的需求。
那我們是這樣寫命令是否可以避坑呢:
$ cat file.txt | head -n 2 > file.txt
結論是不行,文件內容依然會被清空。
What?是不是管道漏了,把數據全漏掉了?
前文 Linux 進程和文件描述符 也說過管道符的實現原理,本質上就是將兩個命令的標準輸入和輸出連接起來,讓前一個命令的標準輸出作為下一個命令的標準輸入。
但是,如果你認為這樣寫命令可以得到預期的結果,那可能是因為你認為管道符連接的命令是串行執行的,這是一個常見的錯誤,實際上管道符連接的多個命令是并行執行的。
你可能以為,shell 會先執行cat file.txt命令,正常讀取file.txt中的所有內容,然后把這些內容通過管道傳遞給head -n 2 > file.txt命令。
雖然這時候file.txt中的內容會被清空,但是head并沒有從文件中讀取數據,而是從管道讀取數據,所以應該可以向file.txt正確寫入兩行數據。
但實際上,上述理解是錯誤的,shell 會并行執行管道符連接的命令,比如說執行如下命令:
$ sleep 5 | sleep 5
shell 會同時啟動兩個sleep進程,所以執行結果是睡眠 5 秒,而不是 10 秒。
這是有點違背直覺的,比如這種常見的命令:
$ cat filename | grep 'pattern'
直覺好像是先執行cat命令一次性讀取了filename中所有的內容,然后傳遞給grep命令進行搜索。
但實際上是cat和grep命令是同時執行的,之所以能得到預期的結果,是因為grep 'pattern'會阻塞等待標準輸入,而cat通過 Linux 管道向grep的標準輸入寫入數據。
執行下面這個命令能直觀感受到cat和grep是在同時執行的,grep在實時處理我們用鍵盤輸入的數據:
$ cat | grep 'pattern'
說了這么多,再回顧一開始的問題:
$ cat file.txt | head -n 2 > file.txt
cat命令和head會并行執行,誰先誰后不確定,執行結果也就不確定。
如果head命令先于cat執行,那么file.txt就會被先清空,cat也就讀取不到任何內容;反之,如果cat先把文件的內容讀取出來,那么可以得到預期的結果。
不過,通過我的實驗(將這種并發情況重復 1w 次)發現,file.txt被清空這種錯誤情況出現的概率遠大于預期結果出現的概率,這個暫時還不清楚是為什么,應該和 Linux 內核實現進程和管道的邏輯有關。
說了這么多管道符和重定向符的特點,如何才能避免這個文件被清空的坑呢?
最靠譜的辦法就是不要同時對同一個文件進行讀寫,而是通過臨時文件的方式做一個中轉。
比如說只保留file.txt文件中的頭兩行,可以這樣寫代碼:
# 先把數據寫入臨時文件,然后覆蓋原始文件 $ cat file.txt | head -n 2 > temp.txt && mv temp.txt file.txt
這是最簡單,最可靠,萬無一失的方法。
你如果嫌這段命令太長,也可以通過apt/brew/yum等包管理工具安裝moreutils包,就會多出一個sponge命令,像這樣使用:
# 先把數據傳給 sponge,然后由 sponge 寫入原始文件 $ cat file.txt | head -n 2 | sponge file.txt
sponge這個單詞的意思是海綿,挺形象的,它會先把輸入的數據「吸收」起來,最后再寫入file.txt,核心思路和我們使用臨時文件時類似的,這個「海綿」就好比一個臨時文件,就可以避免同時打開同一個文件進行讀寫的問題。
以上就是重定向和管道符的一些坑,希望能幫到你。
關于Linux實踐中使用重定向和管道符遇到的坑有哪些就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。