ユーザーが入力した行をCtrl + Dまで1行ずつ読み、Ctrl + Dを入力した行を含める方法

ユーザーが入力した行をCtrl + Dまで1行ずつ読み、Ctrl + Dを入力した行を含める方法

このスクリプトはユーザー入力を 1 行ずつ取得し、各行myfunctionで実行されます。

#!/bin/bash
SENTENCE=""

while read word
do
    myfunction $word"
done
echo $SENTENCE

入力を停止するには、を押して[ENTER]からを押しますCtrl+D

Ctrl+D押された行だけを終了して処理するようにスクリプトを再構築するにはどうすればよいですかCtrl+D

ベストアンサー1

これを行うには、1行ずつ読む必要がなく、文字ごとに読み取る必要があります。

なぜ?シェルは、read() ユーザーが入力したデータを読み取るために標準のCライブラリ関数を使用する可能性が高く、関数は実際に読み取ったバイト数を返します。 0を返すと、EOFが発生したことを意味します(マニュアルread(2);を参照man 2 read)。 EOFは文字ではなく、条件、つまり「コンテンツを読み取れなくなった」という条件であることに注意してください。ファイルの終わり

Ctrl+D送る転送終了文字 (EOT、ASCII文字コード4、$'\04'in bash)を端末ドライバにコピーします。これはread()、シェルに送信する待機呼び出しを送信する効果があります。

1行にテキストを入力しながら半分を押すと、Ctrl+Dこれまでに入力したすべての内容がシェル1に送信されます。つまり Ctrl+D、1行に何かを2回入力すると、最初はデータを送信し、2番目はデータを送信します。何もない、呼び出しはread()0を返し、シェルはそれをEOFとして解釈します。同様にEnter、followを押すとCtrl+D送信するデータがないため、シェルはすぐにEOFを受け取ります。

それでは、2回入力するのをどのように防ぐことができますかCtrl+D

私が言ったように、単一の文字を読んでください。readシェル組み込みを使用している場合は、入力バッファーがある可能性があり、read()入力ストリームから最大文字数を読み取る必要があります(約16kb程度)。これは、シェルが16kbの入力チャンクを取得し、次に16kb未満のチャンク、0バイト(EOF)を取得します。入力の終わり(または改行文字または指定された区切り文字)が表示されると、制御はスクリプトに返されます。

単一文字を読み取るために使用する場合、read -n 1シェルは呼び出し時にシングルバイトバッファを使用します。read()つまり、緊密なループに入り、文字ごとに読み込み、各文字の後に制御をシェルスクリプトに返します。

唯一の問題read -nは、端末を「生モード」に設定することです。つまり、文字が解析なしでそのまま転送されるという意味です。たとえば、を押すと、Ctrl+D文字列にリテラルEOT文字が表示されます。だから私たちはそれを確認する必要があります。また、ユーザーがスクリプトに送信する前にを押すBackspaceCtrl+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)に対して機能します。$'\04'EOT()が検出されると、got_eot1に設定され、break内部ループから取り出されるステートメントが実行されます。改行文字()が検出されると、$'\n'内部ループから抜け出します。それ以外の場合は、line変数の末尾に文字を追加してください。

  • ループの後、行は標準出力として印刷されます。これがを使用して呼び出すことです"$line"。 EOTを検出してここに到達したら、最も外側のループを終了します。

1cat >file 1つの端末と別の端末で実行し、部分行を入力し、キーを押して出力で何が起こるかを確認してtail -f fileテストします。catCtrl+Dtail


ユーザーの場合ksh93:上のループはから行フィードの代わりにキャリッジリターンを読み取りますksh93。これは、テストをテスト$'\n'に変更する必要があることを意味します$'\r'。シェルもこれを^M

この問題を解決するには:

stty_saved="$( stty -g )"
stty-echoctl

#ここで繰り返してください。 $'\n' は $'\r' に置き換えられます。

stty "$stty_saved"

breakとまったく同じ動作を得る前に、明示的に改行文字を出力することもできますbash

おすすめ記事