Linux中標準輸出和標準錯誤的重導向

如果一個命令需要長時間在服務器上運行,那麼很多時候會用到nohup命令,這時即便遠程登錄ssh中斷了與服務器的聯繫,那麼在服務器上運行的命令也不會因此而被迫停止。

通常情況下,nohup與&連用,&的意思是將該命令放在後臺執行。如下:

nohup example.sh &

將exmaple.sh通過&放在服務器後臺運行,nohup確保了即便當前ssh遠程連接中斷,example.sh仍然能夠不受影響,繼續在遠程服務器中運行。

最近有兩個配對測序文件,需要比對到參考基因組上,通過bwa可以完成,同時由於該文件比較大,運行時間長,爲了避免網絡連接不穩定造成ssh中斷,使用nohup

nohup bwa mem ref.fa read1.fq.gz read2.fq.gz > read12.sam &

將兩個測序文件合併並生成sam文件。
寫好命令,一個回車鍵按下去,“啪”一聲,那就一個爽。然後不用管它,十餘個小時之後,果然生成了一個很大的文件read12.sam文件。
但是,當用該sam文件生成bam文件時,提示錯誤sam文件存在錯誤!!!【十餘個小時的計算白費了】
仔細檢查了一下sam文件,發現程序運行的結果和程序運行過程中的說明輸出到了同一個文件中!


1、標準輸出和標準錯誤

標準輸出(standard output)即結果默認的輸出地方,比如在bash中,

$ echo 'hello'
hello

在默認狀態下,’hello’時輸出到你的終端(terminal)上顯示。
再如,通過cat命令顯示一個文本文件,

$ cat hello.txt
Hello!
This is a test!

但是,如果這個文本文件在當前路徑下不存在,在會輸出錯誤:

$ cat No_exist.txt
cat: No_exist.txt: No such file or directory
這時的輸出內容“cat: No_exist.txt: No such file or directory”就是標準錯誤(standard error)。

2、重導向輸出

默認情況下,標準輸出和標準錯誤都會在終端顯示。如果要將標準輸出不是輸出在終端,而是輸出到一個其他文件中,這個時候就是重導向輸出,可以通過“>“符號來完成。

echo 'hello' > hello.txt

將“hello”輸出到hello.txt文件中,系統會新創建該文件,如果路徑中存在該文件,舊的文件將會被覆蓋。
還可以使用”>>”符號,這樣不會覆蓋舊文件,會將”hello”添加到舊文件中。
那麼,這兒有一個問題,是不是所有輸出到終端的內容都可以重導向輸出到一個文件中?比如,如果是標準錯誤,能否通過”>”輸入到文件中?

$ cat No_exist.txt > output.txt
cat: No_exist.txt: No such file or directory

結果證明:不能!output.txt依舊是一個空文件,而錯誤內容並沒有出現在該文件中,依舊在終端顯示!所以不能直接通過”>”將標準錯誤輸出到文件中!
那麼應該怎樣才能將標準錯誤輸出到文件中呢?

3、文件描述符

在bash中,通常使用3個整數來表示標準輸入(0)、標準輸出(1)和標準錯誤(2)。
如果要把標準錯誤輸出到文件中,可以使用

cat No_exist.txt 2> tt.txt

這時在tt.txt文件中就會出現標準錯誤“cat: No_exist.txt: No such file or directory”。
同樣的道理,我們可以將標準錯誤重導向輸出爲標準輸出,2>&1
比如

$ cat No_exist.txt 
cat: No_exist.txt: No such file or directory
$ cat No_exist.txt 2>&1
cat: No_exist.txt: No such file or directory

雖然它們在終端上輸出的內容看起來沒有什麼區別,但是它們的身份是不一樣的,第一個是以標準錯誤的形式輸出的,而第二個是標準輸出。我們可以通過管道符號驗證一下它們的不同。

$ cat No_exist.txt | sed 's/or/and/'
cat: No_exist.txt: No such file or directory
$ cat No_exist.txt 2>&1| sed 's/or/and/'
cat: No_exist.txt: No such file and directory

現在可以看出區別了,第一個標準錯誤無法通過管道符號把“or”替換成“and”,而第二個是標準輸出,可以通過管道符號,把其中的“or”替換成“and”.
同樣的道理,也可以將標準輸出重導向爲標準錯誤“1 >2&“

那麼回過頭來,看最開始的那個問題,爲什麼nohup同時會運算結果和運算過程的描述輸出到同一個sam文件中呢?
爲了簡便,用下面的代碼(example.sh)重現了nohup中的錯誤。

#!/bin/bash

echo "this is outcome!"
sleep 1
echo "sleep for 1s" >&2
echo "this is outcome, too!"
sleep 2
echo "second sleep for 2s" >&2

其中sleep的過程描述通過 >&2以標準錯誤的形式出現,而outcome則以標準輸出的形式輸出。
正常情況下,運行:

$ ./example.sh > outcome.txt
sleep for 1s
second sleep for 2s

$ cat outcome.txt 
this is outcome!
this is outcome, too!

標準錯誤直接輸出到了終端中,運行結果輸出到了outcome.txt中,沒有任何問題。但是在nohup的情況下,這種情況就變了。
在nohup的說明中,提到“如果標準輸出是在終端,那麼輸出的內容將會被添加到‘nohup.out’文件中;如果標準錯誤的輸出是在終端,那麼內容將會被重導向到標準輸出“。這就意味着,在沒有特殊說明的情況下,標準輸出和標準錯誤將會被重導向輸出到同一個地方。如下,

$ nohup ./example.sh 
appending output to nohup.out

$ cat nohup.out 
this is outcome!
sleep for 1s
this is outcome, too!
second sleep for 2s

在nohup.txt文件中不僅出現了我想要的運行結果,還出現了我不想要的運行過程!這也就解釋了爲什麼在我的sam文件中會出現很多本不應該屬於該文件的內容。
既然知道了原因,那麼解決問題就不難了。我們可以通過重導向輸出,把運行結果和運行過程分別輸出到不同的文件中。

$ nohup ./example.sh 2>stderr.log 1>outcome.txt

$ cat stderr.log 
sleep for 1s
second sleep for 2s

$ cat outcome.txt 
this is outcome!
this is outcome, too!

這樣,將過程以標準錯誤形式輸出到stderr.log中,將結果以標準輸出形式輸出到outcome.txt中。
所以本文最開頭提到的命令可以改爲:

nohup bwa mem ref.fa read1.fq.gz read2.fq.gz 1> read12.sam 2>read12.log &

總結

nohup在默認條件下,標準錯誤和標準輸出會重導向到同一個文件中;通過文件描述符(0,1,2)來對控制輸出內容;養成良好的輸出控制習慣,對標準輸出和標準錯誤要區別對待。

===== THE END =====

參考資料:

https://robots.thoughtbot.com/input-output-redirection-in-the-shell#file-descriptors
https://www.brianstorti.com/understanding-shell-script-idiom-redirect/

Linux中標準輸出和標準錯誤的重導向

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章