Shell 標準輸入和輸出
無論是要交給程式處理的資料,還是控制指令碼的簡單命令,都少不了輸入和輸出。程式要做的第一件事就是處理如同一陰一陽的“輸入與輸出”。
1 、從檔案獲取輸入
當我們希望向檔案輸出內容時,我們可以通過符號 > 或 >> 實現。而用代表輸入重定向的符號 < 可以從檔案中讀取資料,如下:
$ wc < my.file
之所以選擇這種形狀的操作符號,原因在於它們可以從視覺上提示重定向的方向。
很多 shell 命令可以接受一個或多個檔名作為引數,但如果沒有給出檔名,命令就會從標準輸入讀取。使用這種命令時,可以採用command filename 或者 command < filename,這兩種形式的結果沒什麼區別。在這個例子中,wc 是這樣,換作 cat 或其他命令,也是如此。
2、將資料與指令碼存放在一起
< 可以從檔案讀取資料,當你需要獲得指令碼輸入,但又不想用單獨的檔案時,使用 <<(here-document)從命令列而非檔案重定向輸入文字。如果放在 shell 指令碼中,則指令碼檔案可以同時包含資料與程式碼。
以下是名為 ext.sh 的 shell 指令碼示例:
# 下面是here-document
grep $1 <<EOF
mike x.123
joe x.234
sue x.555
pete x.818
sara x.822
bill x.919
EOF
當我們執行此指令碼,可以傳入一個引數,如下呼叫:
$ ./ext.sh bill
# 輸出以下內容
bill x.919
grep 命令查詢第一個引數是否在指定檔案中出現,如果沒有指定檔案,那麼它會在標準輸入中查詢。通過設定 here document,告訴 shell 將標準輸入重定向(臨時)到此處。<< 語法表示我們想建立一個臨時輸入源,EOF 是一個任意的字串(你想用什麼都行),用作臨時輸入的終止符。它並不屬於輸入的一部分,只是作為標記告訴輸入在哪裡結束。
3、避免here-document中的怪異行為
here-document 在使用時可能會出現一些怪異的行為。你想用上一節介紹的方法來儲存一份簡單的捐贈人列表,因此建立了一個名為donors.sh 的檔案,如下所示:
# 簡單地查詢慷慨的捐贈人
grep $1 <<EOF
pete $100
joe $200
sam $ 25
bill $ 9
EOF
但是執行時出現了奇怪的輸出:
$ ./donors.sh bill
pete bill00
bill $ 9
$ ./donors.sh pete
pete pete00
正常情況下(除非使用了轉義語法),bash 手冊頁中是這樣說的:“……here-document 的每一行都要執行引數擴充套件、命令替換以及算術擴充套件”。因此,最初的 donors 指令碼中所發生的事情是捐贈額被當作 shell 變量了。例如,$100 被視為 shell 變數 $1,隨後跟著兩個 0。這就是為什麼我們在搜尋“pete”時,得到的是 pete00;搜尋“bill”時,得到的是 bill00。
解決辦法:
通過轉義結尾標記中的任意或所有字元,修改指令碼內容,關閉 here-document 內部的 shell 特性(注意觀察EOF位置的變化):
# 簡單地查詢慷慨的捐贈人
grep $1 <<'EOF'
pete $100
joe $200
sam $ 25
bill $ 9
EOF
儘管其中存在非常微妙的區別,但也可以將 <<EOF 替換成 <<\EOF或 <<'EOF',甚至是 <<E\OF,都沒問題。儘管這並不是最優雅的語法,但足以告訴 bash 你希望區別處理 here-document 中的內容。如果我們轉義了 EOF 的部分或全部字元,那麼 bash 就知道不用執行擴充套件,這樣就符合我們的預期行為了。
$ ./donors.sh pete
pete $100
4、獲取使用者輸入
輸入不止從檔案中獲取,有時我們還需要獲取使用者輸入的內容。此時,我們需要用到read命令,如下:
$ read
或者
$ read -p "answer me this " ANSWER
不帶引數的 read 語句會讀取使用者輸入並將其儲存在 shell 變數REPLY 中,這是 read 的最簡形式。如果希望 bash 在讀取使用者輸入前先輸出提示資訊,可以使用 -p 選項。-p 之後的單詞就是提示資訊,如果想提供多個單詞,可以將其引用起來。記住,要在提示資訊結尾處加上標點符號或空格,因為游標會停在那裡等待輸入。-t 選項可以設定超時值。指定秒數達到後,不管使用者是否輸入,read 語句都會返回。我們的示例同時用到了 -t 和 -p 選項,但你也可以單獨使用 -t 選項。
上面的方式獲取使用者輸入時會以明文回顯,那適用密碼輸入麼?
當我們需要使用者輸入敏感資訊時,需要禁止使用者輸入內容的回顯。此時用 read 命令讀取使用者輸入,需要加上一個特殊選項來關閉回顯:
read -s -p "password: " PASSWD
printf "%b" "\n"
-s 選項告訴 read 命令不要回顯輸入的字元(s 代表 silent),-p 選項指明下一個引數是提示資訊,會在讀取使用者輸入之前顯示。從使用者那裡讀取到的輸入行儲存在變數 $PASSWD 中。在 read 之後,我們用 printf 輸出了一個換行符。這裡的printf 不能少,因為 read -s 會關閉字元回顯。如果禁止了回顯功能,當用戶按下回車鍵時,就不會回顯換行符,後續輸出就會和提示資訊出現在同一行。輸出換行符會將游標帶到下一行。
當然,我們也可以選擇一行,如下:
read -s -p "password: " PASSWD ; printf "%b" "\n"
Shell標準輸出
如果無法產生輸出,那麼軟體也就沒什麼價值了,但長久以來,I/O一直是難纏的計算領域之一。問題是有太多型別的輸出,向螢幕寫入不同於向檔案寫入,向檔案寫入也不同於向磁帶或快閃記憶體寫入。所以,對於輸出會產生一些問題,如下:
- 軟體開發人員是否要針對各種輸出裝置編寫程式碼,甚至包括尚未發明的裝置?
- 寫到哪個檔案?程式怎麼知道是該寫入代表終端視窗的檔案、磁碟檔案還是其他種類的檔案?
顯然,如果把這些事情都交給每個程式設計師是不合理的,所以這種事情留給shell 就行了。
1、輸出到終端/終端視窗
想要用 shell 命令產生一些簡單的輸出,使用內建命令 echo。命令列中的所有引數都會列印到螢幕上。
echo Please wait.
輸出:
Please wait.
結果和在 bash 提示符(字元 $)後輸入該命令相同:
echo 是最簡單的 bash 命令之一。該命令可以將引數輸出到螢幕上。但是有幾點需要記住:
- 首先,shell 負責解析 echo 的命令列引數。將引數交給 echo前,shell 會完成所有的替換、萬用字元匹配等操作。
- 其次,在解析引數時,引數之間的空白字元會被忽略,如下圖:
shell 對引數間的空白字元沒有太多限制,這通常是一種不錯的特性。但對於 echo 來說,就有點煩人了。
- 保留輸出中的空白字元。將字元放入引號中就可以保留空白字元,如下圖:
引號中的單片語成了 echo 命令的單個引數。該引數是一個字串,shell 不會干涉字串的內容。實際上可以用單引號('')明確告訴shell 不要干涉字串。
2、在輸出中加入更多格式控制
使用內建命令 printf。例如:
printf '%s = %d\n' Lines $LINES
Lines = 24
或者:
$ printf '%-10.10s = %4.2f\n' 'Gigahertz' 1.92735
Gigahertz = 1.93
內建命令 printf 的行為和 C 語言中的同名庫函式相似,其中第一個引數是格式控制字串,之後的引數都根據格式規範(%)進行格式化。
% 和格式型別(本例為 s 或 f)之間的數字提供了額外的格式化細節。
對於浮點型別(f),第一個數字(指示符 4.2 中的 4)是整個欄位的寬度。第二個數字(2)是應該在小數點右側打印出的數位量。注意,結果會按照四捨五入處理。
對於字串,第一個數字是欄位的最大寬度,第二個數字是要輸出的字元數量。根據需要,字串會被截斷(長於 max)或用空白填充(不足 min)。如果指示符 max 和 min 相同,那麼就可以確保字串按照該長度輸出。指示符左側的負號表示字串向左對齊(在欄位寬度內)。如果不使用負號,則字串向右對齊
3、消除輸出中的換行符
希望輸出中不包含 echo 預設生成的換行符。使用 printf,做法很簡單,去掉格式化字串末尾的 \n 即可,如下圖:
printf "%s %s" next prompt
如果是 echo,則使用 -n 選項:
$ echo -n prompt
因為 printf 的格式字串(第一個引數)末尾並沒有換行符,所以命令列提示符($)出現在了 printf 的輸出之後。該特性在shell 指令碼中用處更大,你可能希望在形成一整行前由多條語句逐部分輸出,或者在讀取輸入前顯示使用者提示。
換作 echo 命令(參見 15.6 節),消除換行符的方法有兩種。
首先,-n 選項能夠抑制輸出行尾的換行符。
另外,echo 命令還可以處理多種具有特殊含義的轉義序列(如表示換行符的 \n),這些轉移序列與 C 語言字串中的類似。呼叫 echo 命令時加上 -e 選項。其中一種轉義序列是 \C,它並不會輸出什麼字元,而是禁止在行尾輸出換行符。如下圖:
$ echo -e 'hi\c'
4、儲存命令輸出
如過想把命令輸出儲存在檔案中,用 > 符號告訴 shell 將輸出重定向至檔案,例如:
$ echo fill it up
fill it up
$ echo fill it up > file.txt
我們來檢視一下檔案 file.txt 的內容,看看其中是否包含了命令的輸出:
$ cat file.txt
fill it up
示例第一部分的第一行中出現的 echo 命令包含了 3 個要輸出的引數。第二行用 > 將這些輸出儲存到檔案 file.txt 中,這就是看不
到 echo 輸出的原因。
示例第二部分用 cat 命令顯示檔案內容。我們可以看出,檔案中包含的正是 echo 本該輸出的內容。
cat 命令得名自一個較長的單詞 concatenation(拼接)。該命令會將出現在命令列上的檔案的輸出拼接在一起,如果你輸入 cat
file1 file2 file3,那麼這些檔案的內容會逐個傳送到終端視窗。如果一個大檔案被分成了兩半,你也可以用 cat 將其恢復原樣(也就是將兩部分拼接起來),這隻需將輸出儲存到另一個檔案中:
cat first.half second.half > mergeFile.txt
5、將輸出儲存到其他檔案
如想要用重定向將輸出儲存到當前目錄之外的其他位置,重定向輸出時加上路徑,如下:
echo some more data > /tmp/echo.out
或者:
echo some more data > ../../over.here
出現在重定向符號(>)後的檔名其實就是路徑名。如果沒有任何限定部分,那麼檔案就會放置在當前目錄中。
如果檔名以斜線(/)起始是絕對路徑名,此時檔案會被放置在檔案系統層次結構(目錄樹)中以根目錄起始的指定位置。
第二個例子中,我們使用了相對路徑名 ../../over.here,其中的.. 是一個指向父目錄的特殊目錄,存在於每個目錄中。
6、將輸出和錯誤訊息傳送到不同檔案
希望獲得程式的輸出,但不想輸出被出現的錯誤訊息弄亂。要儲存的錯誤訊息混雜在程式輸出中不容易找出,可將輸出和錯誤訊息重定向到不同檔案,如下:
$ myprogram 1> messages.out 2> message.err
或者採用更常見的方法:
$ myprogram > messages.out 2> message.err
shell 會建立兩個輸出檔案。
第一個是messages.out,程式 myprogram 的所有輸出都會重定向到該檔案。
第二個是message.err,程式myprogram 的所有錯誤訊息都會重定向到 message.err。
在 1> 和 2> 中,數字表示檔案 描述符。
- 1 代表標準輸出(STDOUT),
- 2 代表標準錯誤(STDERR)。
- 0 代表標準輸入(STDIN)。
如果不指定數字,則假定為 STDOUT。
7、將輸出和錯誤訊息傳送到同一檔案
利用重定向,我們可以將輸出或錯誤訊息儲存到單獨的檔案中,但如何將兩者送往同一檔案呢?用 shell 語法將標準錯誤訊息重定向到和標準輸出相同的地方。
首選:
$ myprogram >& outfile
或者:
$ myprogram &> outfile
又或者老式且略煩瑣的寫法:
$ myprogram > outfile 2>&1
其中,myprogram是準備向 STDERR 和 STDOUT 生成輸出的程式。
&> 和 >& 只是將 STDOUT 和 STDERR 傳送到相同地方的便捷寫法。
8、追加輸出
每次重定向輸出,都會產生一個全新的輸出檔案。如果想要兩次(或三次、四次……)重定向輸出,同時又不想破壞之前的輸出,該怎麼辦呢?
在 bash 的重定向符號中,雙大於號(>>)表示追加輸出:
$ ls > /tmp/ls.out
$ cd ../elsewhere
$ ls >> /tmp/ls.out
$ cd ../anotherdir
$ ls >> /tmp/ls.out
如果存在同名檔案,第一行中的重定向會將其截斷,並將 ls 命令的輸出儲存在這個已被清空的檔案中。
後兩次呼叫 ls 時使用了雙大於號(>>),表示向輸出檔案中追加內容,而不是覆蓋其原有內容。
如果想要同時重定向錯誤訊息(STDERR),可以將 STDERR 的重定向放在後面,如下所示:
ls >> /tmp/ls.out 2>&1
在 bash 4 中,你可以將這兩個重定向合二為一:
$ ls &>> /tmp/ls.out
該命令會重定向 STDERR 和 STDOUT,並將兩者追加到指定檔案中。& 符號必須先出現,且這 3 個字元之間不能有空格
9、丟棄輸出
你有時不想將輸出儲存到檔案中或者有時甚至不想看到輸出。如我們在查詢某個檔案時,忽略那些沒有許可權的提示,如下圖:
此時,我們可以將輸出重定向到 /dev/null,如下所示:
$ find / -name myfile 2> /dev/null
其實,你可以將不想要的輸出重定向到檔案,然後再將其刪除。但還有一個更簡單的方法。Unix 和 Linux 系統都存在一個特殊裝置,該裝置並非真實的硬體,而僅僅是一個位桶(bit bucket),我們可以將不需要的資料都扔進去。它就是 /dev/null,非常適用於此類場景。寫入其中的資料會被直接丟棄並不會佔用磁碟空間,重定向很容易做到這一點。示例中,只有發往標準錯誤的輸出被丟棄了
本文由
傳智教育博學谷
教研團隊釋出。如果本文對您有幫助,歡迎
關注
和點贊
;如果您有任何建議也可留言評論
或私信
,您的支援是我堅持創作的動力。轉載請註明出處!
- ElasticSearch還能效能調優,漲見識、漲見識了!!!
- 【必須收藏】別再亂找TiDB 叢集部署教程了,這篇保姆級教程來幫你!!| 博學谷狂野架構師
- 【建議收藏】7000 字的TIDB保姆級簡介,你見過嗎
- Tomcat架構設計剖析 | 博學谷狂野架構師
- 你可能不那麼知道的Tomcat生命週期管理 | 博學谷狂野架構師
- 大哥,這是併發不是並行,Are You Ok?
- 為啥要重學Tomcat?| 博學谷狂野架構師
- 這是一篇純講SQL語句優化的文章!!!| 博學谷狂野架構師
- 捲起來!!!看了這篇文章我才知道MySQL事務&MVCC到底是啥?
- 為什麼99%的程式設計師都做不好SQL優化?
- 如何搞定MySQL鎖(全域性鎖、表級鎖、行級鎖)?這篇文章告訴你答案!太TMD詳細了!!!
- 【建議收藏】超詳細的Canal入門,看這篇就夠了!!!
- 從菜鳥程式設計師到高階架構師,竟然是因為這個字final
- 為什麼95%的Java程式設計師,都是用不好Synchronized?
- 99%的Java程式設計師者,都敗給這一個字!
- 8000 字,就說一個字Volatile
- 98%的程式設計師,都沒有研究過JVM重排序和順序一致性
- 來一波騷操作,Java記憶體模型
- 時隔多年,這次我終於把動態代理的原始碼翻了個地兒朝天
- 再有人問你分散式事務,把這篇文章砸過去給他