Bash

如何逐行讀取使用者輸入,直到 Ctrl+D 並包括輸入 Ctrl+D 的行

  • November 17, 2021

該腳本逐行獲取使用者輸入,並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'in bash)。這具有將要發送的任何內容髮送到read()shell 的等待呼叫的效果。

當您Ctrl+D在一行中輸入文本的過程中按下一半時,到目前為止您輸入的任何內容都會發送到 shell 1。這意味著,如果您 Ctrl+D在一行上鍵入內容後輸入兩次,第一個將發送一些數據,第二個將不發送任何數據,read()呼叫將返回零,shell 將其解釋為 EOF。同樣,如果您按Enter後跟Ctrl+D,shell 會立即獲得 EOF,因為沒有任何數據要發送。

那麼如何避免輸入Ctrl+D兩次呢?

正如我所說,閱讀單個字元。當您使用readshell 內置命令時,它可能有一個輸入緩衝區,並要求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 到達這裡,我們將退出最外層循環。

1cat >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.

引用自:https://unix.stackexchange.com/questions/316257