新聞中心
簡(jiǎn)而言之,輸入重定向就是把文件導(dǎo)入到命令中, 輸出重定向就是把原本要輸出到屏幕的數(shù)據(jù)信息寫入到指定文件中。在日常的使用中,我們使用輸出重定向頻率更高,所以又將輸出重定向分為了標(biāo)準(zhǔn)輸出重定向和錯(cuò)誤輸出重定向兩種不同的技術(shù),以及清空寫入與追加的寫入的兩種模式。

專注于為中小企業(yè)提供做網(wǎng)站、成都網(wǎng)站建設(shè)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)大東免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了上千余家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。
本文就分享一下我在實(shí)踐中使用重定向和管道符遇到的一些坑,搞明白一些底層原理,寫腳本的效率能提升不少。
> 和 >> 重定向符的坑
先說第一個(gè)問題,執(zhí)行如下命令會(huì)發(fā)生什么?
$ cat file.txt > file.txt
讀取再寫入同一個(gè)文件,感覺什么也不會(huì)發(fā)生對(duì)吧?
實(shí)際上,上述命令運(yùn)行的結(jié)果是清空file.txt文件中的內(nèi)容。
PS:有的 Linux 發(fā)行版可能會(huì)直接報(bào)錯(cuò),可以執(zhí)行cat file.txt繞開這個(gè)檢測(cè)。
前文 Linux 進(jìn)程和文件描述符 說過,程序本身沒有必要關(guān)心自己的標(biāo)準(zhǔn)輸入/輸出指向哪里,是 shell 通過管道符和重定向符號(hào)修改了程序的標(biāo)準(zhǔn)輸入/輸出的位置。
所以執(zhí)行cat file.txt > file.txt這個(gè)命令時(shí),shell 會(huì)先打開file.txt,由于重定向符號(hào)是>,所以文件中的內(nèi)容會(huì)被清空,然后 shell 將cat命令的標(biāo)準(zhǔn)輸出設(shè)置為file.txt,這時(shí)候cat命令才開始執(zhí)行。
也就是如下過程:
1、shell 打開file.txt并清空其內(nèi)容。 2、shell 將cat命令的標(biāo)準(zhǔn)輸出指向file.txt文件。 3、shell 執(zhí)行cat命令,讀了一個(gè)空文件。 4、cat命令將空字符串寫入標(biāo)準(zhǔn)輸出(file.txt文件)。
所以,最后的結(jié)果就是file.txt變成了空文件。
我們知道,>會(huì)清空目標(biāo)文件,>>會(huì)在目標(biāo)文件尾部追加內(nèi)容,那么如果將重定向符>改成>>會(huì)怎樣呢?
$ echo hello world > file.txt # 文件中只有一行內(nèi)容
$ cat file.txt >> file.txt # 這個(gè)命令會(huì)死循環(huán)
file.txt中首先被寫入一行內(nèi)容,執(zhí)行cat file.txt >> file.txt后預(yù)期的結(jié)果應(yīng)該是兩行內(nèi)容。
但是很遺憾,運(yùn)行結(jié)果并不符合預(yù)期,而是會(huì)死循環(huán)不斷向file.txt中寫入 hello world,文件很快就會(huì)變得很大,只能用 Control+C 停止命令。
這就有意思了,為什么會(huì)死循環(huán)呢?其實(shí)稍加分析就可以想到原因:
首先要回憶cat命令的行為,如果只執(zhí)行cat命令,就會(huì)從命令行讀取鍵盤輸入的內(nèi)容,每次按下回車,cat命令就會(huì)回顯輸入,也就是說,cat命令是逐行讀取數(shù)據(jù)然后輸出數(shù)據(jù)的。
那么,cat file.txt >> file.txt命令的執(zhí)行過程如下:
1、打開file.txt,準(zhǔn)備在文件尾部追加內(nèi)容。 2、將cat命令的標(biāo)準(zhǔn)輸出指向file.txt文件。 3、cat命令讀取file.txt中的一行內(nèi)容并寫入標(biāo)準(zhǔn)輸出(追加到file.txt文件中)。 4、由于剛寫入了一行數(shù)據(jù),cat命令發(fā)現(xiàn)file.txt中還有可以讀取的內(nèi)容,就會(huì)重復(fù)步驟 3。
以上過程,就好比一邊遍歷列表,一遍往列表里追加元素一樣,永遠(yuǎn)遍歷不完,所以導(dǎo)致我們的命令死循環(huán)。
> 重定向符和 | 管道符配合
我們經(jīng)常會(huì)遇到這樣的需求:截取文件的前 XX 行,其余的都刪除。
在 Linux 中,head命令可以完成截取文件前幾行的功能:
$ cat file.txt # file.txt 中有五行內(nèi)容
1
2
3
4
5
$ head -n 2 file.txt # head 命令讀取前兩行
1
2
$ cat file.txt | head -n 2 # head 也可以讀取標(biāo)準(zhǔn)輸入
1
2
如果我們想保留文件的前 2 行,其他的都刪除,可能會(huì)用如下命令:
$ head -n 2 file.txt > file.txt
但是這就犯了前文說的錯(cuò)誤,最后file.txt會(huì)被清空,不能實(shí)現(xiàn)我們的需求。
那我們是這樣寫命令是否可以避坑呢:
$ cat file.txt | head -n 2 > file.txt
結(jié)論是不行,文件內(nèi)容依然會(huì)被清空。
What?是不是管道漏了,把數(shù)據(jù)全漏掉了?
前文 Linux 進(jìn)程和文件描述符 也說過管道符的實(shí)現(xiàn)原理,本質(zhì)上就是將兩個(gè)命令的標(biāo)準(zhǔn)輸入和輸出連接起來,讓前一個(gè)命令的標(biāo)準(zhǔn)輸出作為下一個(gè)命令的標(biāo)準(zhǔn)輸入。
但是,如果你認(rèn)為這樣寫命令可以得到預(yù)期的結(jié)果,那可能是因?yàn)槟阏J(rèn)為管道符連接的命令是串行執(zhí)行的,這是一個(gè)常見的錯(cuò)誤,實(shí)際上管道符連接的多個(gè)命令是并行執(zhí)行的。
你可能以為,shell 會(huì)先執(zhí)行cat file.txt命令,正常讀取file.txt中的所有內(nèi)容,然后把這些內(nèi)容通過管道傳遞給head -n 2 > file.txt命令。
雖然這時(shí)候file.txt中的內(nèi)容會(huì)被清空,但是head并沒有從文件中讀取數(shù)據(jù),而是從管道讀取數(shù)據(jù),所以應(yīng)該可以向file.txt正確寫入兩行數(shù)據(jù)。
但實(shí)際上,上述理解是錯(cuò)誤的,shell 會(huì)并行執(zhí)行管道符連接的命令,比如說執(zhí)行如下命令:
$ sleep 5 | sleep 5
shell 會(huì)同時(shí)啟動(dòng)兩個(gè)sleep進(jìn)程,所以執(zhí)行結(jié)果是睡眠 5 秒,而不是 10 秒。
這是有點(diǎn)違背直覺的,比如這種常見的命令:
$ cat filename | grep 'pattern'
直覺好像是先執(zhí)行cat命令一次性讀取了filename中所有的內(nèi)容,然后傳遞給grep命令進(jìn)行搜索。
但實(shí)際上是cat和grep命令是同時(shí)執(zhí)行的,之所以能得到預(yù)期的結(jié)果,是因?yàn)間rep ‘pattern’會(huì)阻塞等待標(biāo)準(zhǔn)輸入,而cat通過 Linux 管道向grep的標(biāo)準(zhǔn)輸入寫入數(shù)據(jù)。
執(zhí)行下面這個(gè)命令能直觀感受到cat和grep是在同時(shí)執(zhí)行的,grep在實(shí)時(shí)處理我們用鍵盤輸入的數(shù)據(jù):
$ cat | grep 'pattern'
說了這么多,再回顧一開始的問題:
$ cat file.txt | head -n 2 > file.txt
cat命令和head會(huì)并行執(zhí)行,誰先誰后不確定,執(zhí)行結(jié)果也就不確定。
如果head命令先于cat執(zhí)行,那么file.txt就會(huì)被先清空,cat也就讀取不到任何內(nèi)容;反之,如果cat先把文件的內(nèi)容讀取出來,那么可以得到預(yù)期的結(jié)果。
不過,通過我的實(shí)驗(yàn)(將這種并發(fā)情況重復(fù) 1w 次)發(fā)現(xiàn),file.txt被清空這種錯(cuò)誤情況出現(xiàn)的概率遠(yuǎn)大于預(yù)期結(jié)果出現(xiàn)的概率,這個(gè)暫時(shí)還不清楚是為什么,應(yīng)該和 Linux 內(nèi)核實(shí)現(xiàn)進(jìn)程和管道的邏輯有關(guān)。
解決方案
說了這么多管道符和重定向符的特點(diǎn),如何才能避免這個(gè)文件被清空的坑呢?
最靠譜的辦法就是不要同時(shí)對(duì)同一個(gè)文件進(jìn)行讀寫,而是通過臨時(shí)文件的方式做一個(gè)中轉(zhuǎn)。
比如說只保留file.txt文件中的頭兩行,可以這樣寫代碼:
# 先把數(shù)據(jù)寫入臨時(shí)文件,然后覆蓋原始文件
$ cat file.txt | head -n 2 > temp.txt && mv temp.txt file.txt
這是最簡(jiǎn)單,最可靠,萬無一失的方法。
你如果嫌這段命令太長(zhǎng),也可以通過apt/brew/yum等包管理工具安裝moreutils包,就會(huì)多出一個(gè)sponge命令,像這樣使用:
# 先把數(shù)據(jù)傳給 sponge,然后由 sponge 寫入原始文件
$ cat file.txt | head -n 2 | sponge file.txt
sponge這個(gè)單詞的意思是海綿,挺形象的,它會(huì)先把輸入的數(shù)據(jù)「吸收」起來,最后再寫入file.txt,核心思路和我們使用臨時(shí)文件時(shí)類似的,這個(gè)「海綿」就好比一個(gè)臨時(shí)文件,就可以避免同時(shí)打開同一個(gè)文件進(jìn)行讀寫的問題。
以上就是重定向和管道符的一些坑,希望能幫到你。
新聞名稱:Linux重定向和管道符注意事項(xiàng)
轉(zhuǎn)載來于:http://www.fisionsoft.com.cn/article/djgejeo.html


咨詢
建站咨詢
