如何逐行讀取使用者輸入,直到 Ctrl+D 並包括輸入 Ctrl+D 的行
該腳本逐行獲取使用者輸入,並
myfunction
在每一行執行#!/bin/bash SENTENCE="" while read word do myfunction $word" done echo $SENTENCE
要停止輸入,使用者必須按
[ENTER]
,然後按Ctrl+D
。如何重建我的腳本以僅以
Ctrl+D
和處理Ctrl+D
按下的行結束。
為此,您必須逐個字元地閱讀,而不是逐行閱讀。
為什麼?shell 很可能使用標準 C 庫函式
read()
來讀取使用者正在輸入的數據,並且該函式返回實際讀取的字節數。如果它返回零,這意味著它遇到了 EOF(參見read(2)
手冊;man 2 read
)。請注意,EOF 不是字元而是條件,即條件“沒有什麼可讀取的”,文件結尾。
Ctrl+D
向終端驅動程序發送一個傳輸結束字元 (EOT,ASCII 字元程式碼 4,$'\04'
inbash
)。這具有將要發送的任何內容髮送到read()
shell 的等待呼叫的效果。當您
Ctrl+D
在一行中輸入文本的過程中按下一半時,到目前為止您輸入的任何內容都會發送到 shell 1。這意味著,如果您Ctrl+D
在一行上鍵入內容後輸入兩次,第一個將發送一些數據,第二個將不發送任何數據,read()
呼叫將返回零,shell 將其解釋為 EOF。同樣,如果您按Enter
後跟Ctrl+D
,shell 會立即獲得 EOF,因為沒有任何數據要發送。那麼如何避免輸入
Ctrl+D
兩次呢?正如我所說,閱讀單個字元。當您使用
read
shell 內置命令時,它可能有一個輸入緩衝區,並要求read()
從輸入流中讀取最多那麼多字元(可能 16 kb 左右)。這意味著 shell 將獲得一堆 16 kb 的輸入塊,然後是一個可能小於 16 kb 的塊,然後是零字節 (EOF)。一旦遇到輸入的結尾(或換行符,或指定的分隔符),控制權將返回給腳本。如果您使用
read -n 1
讀取單個字元,shell 將在其對 的呼叫中使用單個字節的緩衝區read()
,即它將坐在一個緊密的循環中逐個字元地讀取,在每個字元之後將控制權返回給 shell 腳本。唯一的問題
read -n
是它將終端設置為“原始模式”,這意味著字元按原樣發送而沒有任何解釋。例如,如果您按Ctrl+D
,您將在字元串中獲得文字 EOT 字元。所以我們必須檢查一下。這也有副作用,使用者將無法在將行送出到腳本之前對其進行編輯,例如按Backspace
,或使用Ctrl+W
(刪除前一個單詞)或Ctrl+U
(刪除到行首) .**長話短說:**以下是您的
bash
腳本讀取一行輸入所需執行的最後一個循環,同時允許使用者隨時按 中斷輸入Ctrl+D
:while true; do line='' while IFS= read -r -N 1 ch; do case "$ch" in $'\04') got_eot=1 ;& $'\n') break ;; *) line="$line$ch" ;; esac done printf 'line: "%s"\n' "$line" if (( got_eot )); then break fi done
無需過多詳細說明:
IFS=
清除IFS
變數。沒有這個,我們將無法讀取空格。我使用read -N
而不是read -n
,否則我們將無法檢測到換行符。使我們能夠正確讀取反斜杠的-r
選項。read
- 該
case
語句作用於每個讀取的字元 ($ch
)。如果檢測到 EOT ($'\04'
),它將設置got_eot
為 1,然後執行break
將其從內部循環中取出的語句。如果$'\n'
檢測到換行符 ( ),它就會跳出內部循環。否則,它將字元添加到line
變數的末尾。- 在循環之後,該行被列印到標準輸出。這將是您呼叫使用
"$line"
. 如果我們通過檢測到 EOT 到達這裡,我們將退出最外層循環。1
cat >file
您可以通過在一個終端和另一個終端中執行來測試這一點tail -f file
,然後在 中輸入部分行cat
並按Ctrl+D
以查看在輸出中會發生什麼tail
。對於
ksh93
使用者:上面的循環將讀取輸入符而不是換行符ksh93
,這意味著測試 for$'\n'
將需要更改為測試 for$'\r'
。shell 也會將這些顯示為^M
.要解決此問題:
stty_saved="$( stty -g )" stty -echoctl #*循環到這裡,$'\n' 替換為 $'\r'* stty "$stty_saved"
您可能還希望在 之前顯式輸出換行符
break
以獲得與bash
.