標準輸入/輸出(standard I/O)可能是軟件設計原則里最重要的概念了。這個概念就是:程序應該有數據的來源端、數據的目的端(輸出結果的地方)已經報告問題的地方,它們分別被稱為標準輸入(standard input)、標準輸出(standard output)以及標準錯誤輸出(standard error)。程序不必知道也不用關心它的輸入與輸出背后是什么設備,當程序運行時,這些標準 IO 就已經打開并準備就緒了。
運行時的程序稱為進程,在 Linux 系統中,對于每個進程來說,始終有 3 個”文件”處于打開狀態:
- stdin
- stdout
- stderr
這三個文件就是進程默認的標準輸入、標準輸出和標準錯誤輸出。每個打開的文件都會被分配一個文件描述符。stdin,stdout 和 stderr 的文件描述符分別是 0,1 和 2(一個文件描述符說白了就是文件系統為了跟蹤這個打開的文件而分配給它的一個數字)。對于 Bash 進程來說,stdin 默認就是鍵盤,stdout 和 stderr 默認都是屏幕。
我們可以把 IO 重定向簡單理解為:
系統捕捉一個文件、命令、程序或腳本的輸出,然后將這些輸出作為輸入發送到另一個文件、命令、程序或腳本中。
IO 重定向的基本概念
把標準輸出重定向到文件
> 和 >> 符號把標準輸出重定向到文件中
> 會覆蓋掉已經存在的文件中的內容
>> 則把新的內容追加到已經存在的文件中的內容的尾部
比如下面的命令將會把文件 log.txt 變為一個空文件(就是 size 為 0):
$ : > log.txt
下面的命令則把 ls 命令輸出的結果追加到 log.txt 文件的尾部:
$ ls >> log.txt
其實這是省略了標準輸出的文件描述符 1 的寫法,完整的寫法為:
$ ls 1> log.txt
把標準錯誤輸出重定向到文件
標準錯誤的文件描述符為 2,所以重定向標準錯誤到文件的寫法為:
$ ls 2> error.txt
如果想同時把標準輸出和標準錯誤輸出重定向到同一個文件中,可以使用下面的方法:
$ ls &> log.txt
還有另外一種寫法為:
$ ls >log.txt 2>&1
重定向到另一個文件描述符
比如通過這種方式把標準輸出和標準錯誤輸出重定向到同一個文件中:
$ ls >log.txt 2>&1
注意:& 符號后面跟的是文件描述符而不是文件。
總結一下,我們大概可以得到下面兩條規則:
M>N
# “M” 是一個文件描述符,如果沒有明確指定的話默認為 1。
# “N” 是一個文件名。
# 文件描述符 “M” 被重定向到文件 “N”。
M>&N
# “M” 是一個文件描述符,如果沒有明確指定的話默認為 1。
# “N” 是另一個文件描述符。
關閉文件描述符
文件描述符是可以關閉的,典型的寫法有下面幾種:
n<&- # 關閉輸入文件描述符 n
0<&-, <&- # 關閉 stdin
n>&- # 關閉輸出文件描述符 n
1>&-, >&- # 關閉 stdout
>/dev/null 2>&1 和 2>&1 >/dev/null
Linux 系統中有一個特殊的設備文件 /dev/null,發送給它的任何內容都會被丟棄,因此如果需要丟棄標準輸出和標準錯誤輸出的內容時可以使用下面的方法:
1>/dev/null 2>/dev/null
下面的寫法與上面的寫法是等同的,接下來我們重點來解釋下面的寫法:
>/dev/null 2>&1
>/dev/null
>/dev/null 的作用是將標準輸出 1 重定向到 /dev/null 中,因此標準輸出中的內容被完全丟棄了。
2>&1
2>&1 用到了重定向綁定,采用 & 可以將兩個輸出綁定在一起,也就是說錯誤輸出將會和標準輸出輸出到同一個地方。
其實 Linux 在執行 shell 命令之前,就會確定好所有的輸入輸出位置,解釋順序為從左到右依次執行重定向操作。所以 >/dev/null 2>&1 的作用就是讓標準輸出重定向到 /dev/null 中,接下來因為錯誤輸出的文件描述符 2 被重定向綁定到了標準輸出的描述符 1,所以錯誤輸出也被定向到了 /dev/null 中,錯誤輸出同樣也被丟棄了。
2>&1 >/dev/null
2>&1 >/dev/null 執行的結果和 >/dev/null 2>&1 是不一樣的!它的執行過程為:
- 2>&1,將錯誤輸出綁定到標準輸出上。由于此時的標準輸出是默認值,也就是輸出到屏幕,所以錯誤輸出會輸出到屏幕。
- >/dev/null,將標準輸出1重定向到 /dev/null 中。
exec 與重定向
exec <filename 命令會將 stdin 重定向到文件中。 從這句開始, 所有的 stdin 就都來自于這個文件了, 而不是標準輸入(通常都是鍵盤輸入)。 這樣就提供了一種按行讀取文件的方法。當然,也可以用這種方法來重定向標準輸出和標準錯誤輸出。下面的 upper.sh 程序把輸入文件中的字母轉換為大寫并輸出到另外一個文件中,腳本中使用 exec 同時重定向了 stdin 和 stdout:
#!/bin/bash
E_FILE_ACCESS=70
E_WRONG_ARGS=71
if [ ! -r "$1" ] # 判斷指定的輸入文件是否可讀
then
echo "Can't read from input file!"
echo "Usage: $0 input-file output-file"
exit $E_FILE_ACCESS
fi # 即使輸入文件($1)沒被指定
if [ -z "$2" ]
then
echo "Need to specify output file."
echo "Usage: $0 input-file output-file"
exit $E_WRONG_ARGS
fi
exec 4<&0 # 保存默認 stdin
exec < $1 # 將會從輸入文件中讀取.
exec 7>&1 # 保存默認 stdout
exec > $2 # 將寫到輸出文件中.
# 假設輸出文件是可寫的
# -----------------------------------------------
cat - | tr a-z A-Z # 轉換為大寫
# 從 stdin 中讀取
# 寫到 stdout 上
# 然而,stdin 和 stdout 都被重定向了
# -----------------------------------------------
exec 1>&7 7>&- # 恢復 stout
exec 0<&4 4<&- # 恢復 stdin
# 恢復之后,下邊這行代碼將會如預期的一樣打印到 stdout 上
echo "File \"$1\" written to \"$2\" as uppercase conversion."
exit 0
在 upper.sh 程序所在的目錄下創建 in.txt 和 out.txt 文件,編輯 in.txt 文件的內容為:
abcd
xxyy
然后運行 upper.sh 程序:
$ ./upper.sh in.txt out.txt

其實還有一種稍微簡單一些的方式把標準輸入和標準輸出重定向到文件:
[j]<>myfile
# 為了讀寫 myfile,把文件 myfile 打開, 并且將文件描述符 j 分配給它。
# 如果文件 myfile 不存在, 那么就創建它。
# 如果文件描述符 j 沒指定,那默認是 fd 0,即標準輸入。
這種應用通常是為了把內容寫到一個文件中指定的地方,比如下面的 demo:
echo 1234567890 > myfile # 寫字符串到 myfileexec 6<> myfile # 打開 myfile 并且將 fd 6 分配給它read -n 4 <&6 # 只讀取4個字符echo -n . >&6 # 寫一個小數點exec 6>&- # 關閉 fd 6cat myfile # ==> 1234.67890
避免子 shell
默認情況下,不能在子 shell 中改變父 shell 中變量的值。下面的 demo 通過 exec 重定向 IO 有效的規避了子 shell 問題:
#!/bin/bash
E_WRONG_ARGS=71
if [ -z "$1" ]
then
echo "Usage: $0 input-file"
exit $E_WRONG_ARGS
fi
Lines=0
cat "$1" | while read line; # 管道會產生子 shell
do {
echo $line
(( Lines++ )); # 增加這個變量的值
# 但是外部循環卻不能訪問
}
done
echo "Number of lines read = $Lines" # 0
# 錯誤!
echo "------------------------"
exec 3<> "$1"
while read line <&3
do {
echo "$line"
(( Lines++ )); # 增加這個變量的值
# 現在外部循環就可以訪問了
# 沒有子shell, 現在就沒問題了
}
done
exec 3>&-
echo "Number of lines read = $Lines"
exit 0
把上面的代碼保存到 avoid-subshell.sh 文件中,然后運行下面的命令:
$ ./avoid-subshell.sh in.txt

使用重定向 IO 的方式獲得了正確的結果。
代碼塊重定向
像 while、until、for 和 if/then 等代碼塊也是可以進行 IO 重定向的,下面的 demo 把 while 循環的標準輸入重定向到一個文件:
#!/bin/bash
E_WRONG_ARGS=71
if [ -z "$1" ]
then
echo "Usage: $0 input-file"
exit $E_WRONG_ARGS
fi
count=0
while [ "$name" != xxyy ]
do
read name
if [ -z "$name" ]; then
break
fi
echo $name
let "count += 1"
done <"$1"
echo "$count names read"
exit 0
把上面的代碼保存到文件 whileblock.sh 中,然后執行下面的命令,可以看到標準輸入重定向后的結果:
$ ./whileblock.sh in.txt

下面的 demo 則同時重定向了 for 循環的標準輸入和標準輸出:
#!/bin/bash
E_WRONG_ARGS=71
if [ -z "$1" ]
then
echo "Usage: $0 input-file output-file"
exit $E_WRONG_ARGS
fi
if [ -z "$2" ]
then
echo "Usage: $0 input-file output-file"
exit $E_WRONG_ARGS
fi
FinalName="xxyy"
line_count=$(wc "$1" | awk '{ print $1 }')
for name in $(seq $line_count)
do
read name
echo "$name"
if [ "$name" = "$FinalName" ]
then
break
fi
done < "$1" > "$2"
exit 0
把上面的代碼保存到文件 forblock.sh 中,然后執行下面的命令,終端上沒有任何輸出,因為輸出都被重定向到了 out.txt 文件:
$ ./forblock.sh in.txt out.txt
總結
在眾多的場景中,IO 重定向的結果是顯而易見的。但在一些特殊的用例中結果就不是那么明顯了,需要我們理解重定向的基本規則,才能理解哪些特殊寫法的含義或是寫出我們自己滿意的腳本。
鏈接:https://www.cnblogs.com/sparkdev/p/10247187.html
(版權歸原作者所有,侵刪)

需要完整資料的看這里
掃碼回復666 根據提示 添加小助理領取

本文鏈接:http://www.royaladd.com/46024.html
網友評論comments