linux輸出錯誤重定向 總結_qingshanli1988-ChinaUnix博客

Linux Shell 環境中支持輸入輸出重定向,用符號 < 和 > 來表示。0、1和2分別表示標準輸入、標準輸出和標準錯誤信息輸出,可以用來指定需要重定向的標準輸 入或輸出,比如 2>a.txt 表示將錯誤信息輸出到文件a.txt中。

同時,還可以在這三個標準輸入輸出之間實現重定向,比如將 錯誤信息重定向到標準輸出,可以用 2>1來實現。

Linux下還有一個特殊的文件/dev/null,它就像一個無底洞,所有重定向到它的信息都會消失得無影無蹤。這一點非常有用,當我們不需要回顯程序的所有信息時,就可以將輸出重定向到/dev/null。

如 果想要正常輸出和錯誤信息都不顯示,則要把標準輸出和標準錯誤都重定向到/dev/null, 例如:

# ls 1>/dev/null 2>/dev/null

還有一種做法是將錯誤重定向到標準輸出,然後再重定向到 /dev/null,例如:

# ls >/dev/null 2>1

注意:此處的順序不能更改,否則 達不到想要的效果,此時先將標準輸出重定向到 /dev/null,然後將標準錯誤重定向到標準輸出,由於標準輸出已經重定向到了/dev/null,因此標準錯誤也會重定向到/dev/null,於是一切靜悄悄:-)

以上:http://blog.daviesliu.net/2005/08/31/200811/

以下轉自:http://hi.baidu.com/lb_hb/blog/item/bfe6a4659e8877f6f73654bd.html Chapter 16. I/O 重定向

默認情況下始終有3個"文件"處於打開狀態, stdin (鍵盤), stdout (屏幕), and stderr (錯誤消息輸出到屏幕上). 這3個文件和其他打開的文件都可以被重定向. 對於重定向簡單的解釋就是捕捉一個文件, 命令, 程序, 腳本, 或者甚至是腳本中的代碼塊(參見 Example 3-1 和 Example 3-2)的輸出, 然後將這些輸出作爲輸入發送到另一個文件, 命令, 程序, 或腳本中.

每個打開的文件都會被分配一個文件描述符.[1]stdin, stdout, 和stderr的文件描述符分別是0, 1, 和 2. 對於正在打開的額外文件, 保留了描述符3到9. 在某些時候將這些格外的文件描述符分配給stdin, stdout, 或者是stderr作爲臨時的副本鏈接是非常有用的. [2] 在經過複雜的重定向和刷新之後需要把它們恢復成正常的樣子 (參見 Example 16-1).

1 COMMAND_OUTPUT >
2 # 重定向stdout到一個文件.
3 # 如果沒有這個文件就創建, 否則就覆蓋.
4
5 ls -lR > dir-tree.list
6 # 創建一個包含目錄樹列表的文件.
7
8 : > filename
9 # > 會把文件"filename"截斷爲0長度.
10 # 如果文件不存在, 那麼就創建一個0長度的文件(與'touch'的效果相同).
11 # : 是一個佔位符, 不產生任何輸出.
12
13 > filename
14 # > 會把文件"filename"截斷爲0長度.
15 # 如果文件不存在, 那麼就創建一個0長度的文件(與'touch'的效果相同).
16 # (與上邊的": >"效果相同, 但是在某些shell下可能不能工作.)
17
18 COMMAND_OUTPUT >>
19 # 重定向stdout到一個文件.
20 # 如果文件不存在, 那麼就創建它, 如果存在, 那麼就追加到文件後邊.
21
22
23 # 單行重定向命令(只會影響它們所在的行):
24 # --------------------------------------------------------------------
25
26 1>filename
27 # 重定向stdout到文件"filename".
28 1>>filename
29 # 重定向並追加stdout到文件"filename".
30 2>filename
31 # 重定向stderr到文件"filename".
32 2>>filename
33 # 重定向並追加stderr到文件"filename".
34 >filename
35 # 將stdout和stderr都重定向到文件"filename".
36
37 #==============================================================================
38 # 重定向stdout, 一次一行.
39 LOGFILE=script.log
40
41 echo "This statement is sent to the log file, "$LOGFILE"." 1>$LOGFILE
42 echo "This statement is appended to "$LOGFILE"." 1>>$LOGFILE
43 echo "This statement is also appended to "$LOGFILE"." 1>>$LOGFILE
44 echo "This statement is echoed to stdout, and will not appear in "$LOGFILE"."
45 # 每行過後, 這些重定向命令會自動"reset".
46
47
48
49 # 重定向stderr, 一次一行.
50 ERRORFILE=script.errors
51
52 bad_command1 2>$ERRORFILE # 錯誤消息發到$ERRORFILE中.
53 bad_command2 2>>$ERRORFILE # 錯誤消息添加到$ERRORFILE中.
54 bad_command3 # 錯誤消息echo到stderr,
55 # 並且不出現在$ERRORFILE中.
56 # 每行過後, 這些重定向命令也會自動"reset".
57 #==============================================================================
58
59
60
61 2>1
62 # 重定向stderr到stdout.
63 # 得到的錯誤消息與stdout一樣, 發送到一個地方.
64
65 i>j
66 # 重定向文件描述符ij.
67 # 指向i文件的所有輸出都發送到j中去.
68
69 >j
70 # 默認的, 重定向文件描述符1(stdout)到 j.
71 # 所有傳遞到stdout的輸出都送到j中去.
72
73 0< FILENAME
74 < FILENAME
75 # 從文件中接受輸入.
76 # 與">"是成對命令, 並且通常都是結合使用.
77 #
78 # grep search-word 79
80
81 [j]<>filename
82 # 爲了讀寫"filename", 把文件"filename"打開, 並且分配文件描述符"j"給它.
83 # 如果文件"filename"不存在, 那麼就創建它.
84 # 如果文件描述符"j"沒指定, 那默認是fd 0, stdin.
85 #
86 # 這種應用通常是爲了寫到一個文件中指定的地方.
87 echo 1234567890 > File # 寫字符串到"File".
88 exec 3<> File # 打開"File"並且給它分配fd 3.
89 read -n 4 <3 # 只讀4個字符.
90 echo -n . >3 # 寫一個小數點.
91 exec 3>- # 關閉fd 3.
92 cat File # ==> 1234.67890
93 # 隨機存儲.
94
95
96
97 |
98 # 管道.
99 # 通用目的的處理和命令鏈工具.
100 # 與">"很相似, 但是實際上更通用.
101 # 對於想將命令, 腳本, 文件和程序串連起來的時候很有用.
102 cat *.txt | sort | uniq > result-file
103 # 對所有的.txt文件的輸出進行排序, 並且刪除重複行,
104 # 最後將結果保存到"result-file"中.

可以將輸入輸出重定向和(或)管道的多個實例結合到一起寫在一行上.1 command < input-file > output-file
2
3 command1 | command2 | command3 > output-file

參見 Example 12-28 和 Example A-15.

可以將多個輸出流重定向到一個文件上.1 ls -yz >> command.log 2>1
2 # 將錯誤選項"yz"的結果放到文件"command.log"中.
3 # 因爲stderr被重定向到這個文件中,
4 # 所有的錯誤消息也就都指向那裏了.
5
6 # 注意, 下邊這個例子就不會給出相同的結果.
7 ls -yz 2>1 >> command.log
8 # 輸出一個錯誤消息, 但是並不寫到文件中.
9
10 # 如果將stdout和stderr都重定向,
11 # 命令的順序會有些不同.

關閉文件描述符n關閉輸入文件描述符n.

0關閉stdin.

n>-

關閉輸出文件描述符n.

1>-, >-

關閉stdout.

子進程繼承了打開的文件描述符. 這就是爲什麼管道可以工作. 如果想阻止fd被繼承, 那麼可以關掉它.1 # 只重定向stderr到一個管道.
2
3 exec 3>1 # 保存當前stdout的"值".
4 ls -l 2>1 >3 3>- | grep bad 3>- # 對'grep'關閉fd 3(但不關閉'ls').
5 # ^^^^ ^^^^
6 exec 3>- # 現在對於剩餘的腳本關閉它.
7
8 # Thanks, S.C.

如果想了解關於I/O重定向更多的細節參見 附 錄 E.16.1. 使用exec

exec Example 16-1. 使用exec重定向標準輸入1 #!/bin/bash
2 # 使用'exec'重定向標準輸入.
3
4
5 exec 6<0 # 將文件描述符#6與stdin鏈接起來.
6 # 保存了stdin.
7
8 exec < data-file # stdin被文件"data-file"所代替.
9
10 read a1 # 讀取文件"data-file"的第一行.
11 read a2 # 讀取文件"data-file"的第二行.
12
13 echo
14 echo "Following lines read from file."
15 echo "-------------------------------"
16 echo $a1
17 echo $a2
18
19 echo; echo; echo
20
21 exec 0<6 6 22 # 現在將stdin從fd #6中恢復, 因爲剛纔我們把stdin重定向到#6了,
23 # 然後關閉fd #6 ( 6 24 #
25 # <6 6 26
27 echo -n "Enter data "
28 read b1 # 現在"read"已經恢復正常了, 就是從stdin中讀取.
29 echo "Input read from stdin."
30 echo "----------------------"
31 echo "b1 = $b1"
32
33 echo
34
35 exit 0

同樣的, exec >filename 命令將會把stdout重定向到一個指定的文件中. 這樣所有的命令輸出就都會發向那個指定的文件, 而不是stdout.

Example 16-2. 使用exec來重定向stdout1 #!/bin/bash
2 # reassign-stdout.sh
3
4 LOGFILE=logfile.txt
5
6 exec 6>1 # 將fd #6與stdout相連接.
7 # 保存stdout.
8
9 exec > $LOGFILE # stdout就被文件"logfile.txt"所代替了.
10
11 # ----------------------------------------------------------- #
12 # 在這塊中所有命令的輸出就都發向文件 $LOGFILE.
13
14 echo -n "Logfile: "
15 date
16 echo "-------------------------------------"
17 echo
18
19 echo "Output of "ls -al" command"
20 echo
21 ls -al
22 echo; echo
23 echo "Output of "df" command"
24 echo
25 df
26
27 # ----------------------------------------------------------- #
28
29 exec 1>6 6>- # 恢復stdout, 然後關閉文件描述符#6.
30
31 echo
32 echo "== stdout now restored to default == "
33 echo
34 ls -al
35 echo
36
37 exit 0

Example 16-3. 使用exec在同一腳本中重定向stdin和stdout1 #!/bin/bash
2 # upperconv.sh
3 # 將一個指定的輸入文件轉換爲大寫.
4
5 E_FILE_ACCESS=70
6 E_WRONG_ARGS=71
7
8 if [ ! -r "$1" ] # 判斷指定的輸入文件是否可讀?
9 then
10 echo "Can't read from input file!"
11 echo "Usage: $0 input-file output-file"
12 exit $E_FILE_ACCESS
13 fi # 即使輸入文件($1)沒被指定
14 # 也還是會以相同的錯誤退出(爲什麼?).
15
16 if [ -z "$2" ]
17 then
18 echo "Need to specify output file."
19 echo "Usage: $0 input-file output-file"
20 exit $E_WRONG_ARGS
21 fi
22
23
24 exec 4<0
25 exec < $1 # 將會從輸入文件中讀取.
26
27 exec 7>1
28 exec > $2 # 將寫到輸出文件中.
29 # 假設輸出文件是可寫的(添加檢查?).
30
31 # -----------------------------------------------
32 cat - | tr a-z A-Z # 轉換爲大寫.
33 # ^^^^^ # 從stdin中讀取.Reads from stdin.
34 # ^^^^^^^^^^ # 寫到stdout上.
35 # 然而, stdin和stdout都被重定向了.
36 # -----------------------------------------------
37
38 exec 1>7 7>- # 恢復 stout.
39 exec 0<4 4 40
41 # 恢復之後, 下邊這行代碼將會如期望的一樣打印到stdout上.
42 echo "File "$1" written to "$2" as uppercase conversion."
43
44 exit 0

I/O重定向是一種避免可怕的子 shell中不可存取變量問題的方法.

Example 16-4. 避免子shell1 #!/bin/bash
2 # avoid-subshell.sh
3 # Matthew Walker提出的建議.
4
5 Lines=0
6
7 echo
8
9 cat myfile.txt | while read line; # (譯者注: 管道會產生子shell)
10 do {
11 echo $line
12 (( Lines )); # 增加這個變量的值
13 # 但是外部循環卻不能存取.
14 # 子shell問題.
15 }
16 done
17
18 echo "Number of lines read = $Lines" # 0
19 # 錯誤!
20
21 echo "------------------------"
22
23
24 exec 3<> myfile.txt
25 while read line <3
26 do {
27 echo "$line"
28 (( Lines )); # 增加這個變量的值
29 # 現在外部循環就可以存取了.
30 # 沒有子shell, 現在就沒問題了.
31 }
32 done
33 exec 3>-
34
35 echo "Number of lines read = $Lines" # 8
36
37 echo
38
39 exit 0
40
41 # 下邊這些行是腳本的結果, 腳本是不會走到這裏的.
42
43 $ cat myfile.txt
44
45 Line 1.
46 Line 2.
47 Line 3.
48 Line 4.
49 Line 5.
50 Line 6.
51 Line 7.
52 Line 8.

工作中需要構造一個數據傳輸工具,用來抓取ftp前端機的web日誌並直接寫入HDFS,該工具直接作爲hadoop mapreduce任務定時執行,所以最好不使用文件腳本以免去分發文件的繁瑣,能一句命令搞定最好。

如何抓取ftp前端機的web日誌並寫入HDFS? 管道(pipe),命令如下:
wget $SRC_URL --limit-rate=%s --tries=1 -O - | hadoop fs -put - $DESTPATH
一行命令,可以直接當成-mapper的參數,不需要額外寫文件腳本
可惜這個方法有問題,使用管道的話,整個命令的返回值是後半部分(hadoop fs -put - $DESTPATH)的返回值
當wget沒有得到抓到任何內容返回失敗時,整個命令返回0,錯誤沒有捕捉到,
如何解決?
1. PIPESTATUS
PIPESTATUS是一個環境變量,數組類型,保存了PIPE中所有命令的返回值
PIPE命令成功結束後,判斷PIPESTATUS[0]是否爲零即可
2. 那怎麼將這些邏輯整合到一條shell命令中?
正確解法(之一)需要使用到() exit,先看看這些(), , ||的標準行爲:
( list )
Placing a list of commands between parentheses causes a subshell to be created, and each of the commands in list to be executed in that subshell. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.
expression1 expression2 True if both expression1 and expression2 are true.
expression1 || expression2 True if either expression1 or expression2 is true.
The and || commands do not execute expression2 if the value of expression1 is sufficient to determine the return value of the entire conditional expression.

最後我的解決方案:
wget $SRC_URL --limit-rate=%s --tries=1 -O - | hadoop fs -put - $DESTPATH ( exit ${PIPESTATUS[0]} )
其實在找到這個正確方法之前,還是走了不少彎路:
wget $SRC_URL --limit-rate=%s --tries=1 -O - | hadoop fs -put - $DESTPATH exit ${PIPESTATUS[0]}
exit命令會直接退出當前shell,慎用!
wget $SRC_URL --limit-rate=%s --tries=1 -O - | hadoop fs -put - $DESTPATH ( exit ${PIPESTATUS[0]} )
()的功能是新啓動一個shell來運行括號裏面的內容,這樣exit的是()產生的shell,同時捕捉到了PIPESTATUS[0]
最後爲了代碼可讀性(C 傳染過來的毛病-_-!!), 擅自加了個括號:
( wget $SRC_URL --limit-rate=%s --tries=1 -O - | hadoop fs -put - $DESTPATH ) ( exit ${PIPESTATUS[0]} )
這樣反而又錯了,由於左邊的管道命令被封在一個新的shell裏面,PIPESTATUS環境變量已經追蹤不到裏面的管道命令了!!標準的畫蛇添足!
問題幾經曲折之後終於解決,對shell編程知識的積累也多了一點!
PS:
感覺shell確實是一種嚴重依賴"技巧",並把取巧當成其設計理念的工具,而不是語言
shell不像一種完備的現代程序語言,擁有嚴格的語法和語義,嚴格直接意味着不容易出錯,也不容易讓用戶誤用,這其實是很重要的,也是我傾向使用的腳本環境
反觀shell,很多本來應該屬於語法的東西, 是通過某種方式取巧模擬出來的,比如很多關鍵字其實是個程序
所以在使用shell時,就需要額外小心,隨時可能踩到地雷,當然不排除有高手將它用的出神入化,到了寫出的腳本一般人都看不懂的地步。
開發實際的項目時,shell編程的使用最好限制在很小的範圍內,比如僅僅是用來包裝一下主程序和工具程序,最好不要用它來做功能實現
PS2:
也許Python加上強大的系統管理工具庫可以創造一個比較完美的可編程shell來,比如ipython,不過初試之後感覺還沒達到我的期望,也可能用的太少還沒上手

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