シェル:読み取り:EOFと改行文字を区別します。

シェル:読み取り:EOFと改行文字を区別します。

単一文字を読む、null<EOF>\n?を区別する方法は?

たとえば、

f() { read -rn 1 -p "Enter a character: " char &&
      printf "\nYou entered '%s'\n" "$char"; }

印刷可能な文字を含む:

$ f
Enter a character: x
You entered 'x'

押すときEnter

$ f
Enter a character: 

You entered ''

Ctrl+を押すとD

$ f
Enter a character: ^D
You entered ''
$ 

最後の2つのケースの出力が同じ理由は何ですか?どのように区別できますか?

POSIXシェルと比較して他のアプローチはありますかbash

答え1

(POSIX機能ではありません)stdinread -n "$n"が端末装置の場合は、端末をそのモードreadから取り出します。それ以外の場合は、ターミナル行ルールの内部行エディタから返された行全体を確認し、一度に1バイトずつ読みます。文字または改行文字を読みました(間違った文字を入力すると、予期しない結果が表示されることがあります)。icanonread$n

$n1行から最大1文字を読みます。また、$IFS入力からIFS文字が削除されないようにするには、それを消去する必要があります。

icanon今私たちはパターンから外れたので、もはや^D特別ではありません。だから押すとCtrl+D文字が読み込まれます。^D

何らかの方法でターミナル接続が切断されない限り、ターミナルデバイスでeofを見ることはできません。 stdinが他の種類のファイルの場合は、eofが表示されることがあります(: | IFS= read -rn 1; echo "$?"stdinが空のパイプであるか、stdinがからリダイレクトされます/dev/null)。

read$n文字(有効な文字の一部を構成しないバイトは1文字としてカウントされます)、または行全体を読み取ると0が返されます。

したがって、1つの文字のみを要求する特別な場合は、次のようになります。

if IFS= read -rn 1 var; then
  if [ "${#var}" -eq 0 ]; then
    echo an empty line was read
  else
    printf %s "${#var} character "
    (export LC_ALL=C; printf '%s\n' "made of ${#var} byte(s) was read")
  fi
else
  echo "EOF found"
fi

POSIXlyは実行するのが非常に複雑です。

これは次のとおりです(EBCDICではなくASCIIベースのシステムを想定)。

readk() {
  REPLY= ret=1
  if [ -t 0 ]; then
    saved_settings=$(stty -g)
    stty -icanon min 1 time 0 icrnl
  fi
  while true; do
    code=$(dd bs=1 count=1 2> /dev/null | od -An -vto1 | tr -cd 0-7)
    [ -n "$code" ] || break
    case $code in
      000 | 012) ret=0; break;; # can't store NUL in variable anyway
      (*) REPLY=$REPLY$(printf "\\$code");;
    esac
    if expr " $REPLY" : ' .' > /dev/null; then
      ret=0
      break
    fi
  done
  if [ -t 0 ]; then
    stty "$saved_settings"
  fi
  return "$ret"
}

文字全体を読んだ後にのみ返されることに注意してください。誤ったエンコーディング(ロケールエンコーディングとは異なります)を入力した場合、たとえば、端末がéiso8859-1(0xe9)エンコーディングを送信し、UTF-8(0xc3 0xa9)を期待している場合は、任意の数値コンテンツを入力できますが、関数éは返品。bashread -n12番目の0xe9を返し、両方とも変数に保存しますが、これは少し良い動作です。

(スクリプトを終了する代わりに、、...でも動作します)または/ on(フロー制御の代わりに)^Cで文字を読みたい場合は、行にaを追加できます。でもこれをしないことに注意してください(ダウンすると再起動することがあります)。Ctrl+C^Z^\^S^QCtrl+S/Q-isig -ixonsttybashread -n1isig

スクリプトが終了すると(たとえば、押すと)tty設定は復元されませんCtrl+C。を追加できますが、これによりスクリプトの他の設定が上書きされる可能性trapがあります。trap

zsh代わりにbashwhere read -k(beforeksh93またはbash's)を使用することもできますread -n/-N。これは、端末から文字を読み取って^D直接処理し(文字が入力された場合はゼロ以外の値を返します)、改行文字は特に処理しません。

if read -k k; then
  printf '1 character entered: %q\n' $k
fi

答え2

次に変更f()してください。%s%q

f() { read -rn 1 -p "Enter a character: " char && \
      printf "\nYou entered '%q'\n" "$char"; }
f;f

ユーザーが入力すると出力新しいチーム、その後Ctrl-D':

Enter a character: 

You entered ''''
Enter a character: ^D
You entered '$'\004''

`man printfから:

 %q       ARGUMENT is printed in a format that can be reused as shell input, 
          escaping non-printable characters with the proposed POSIX $'' syntax.

答え3

実際にread -rn1Bashで実行してをクリックすると、^DEOF条件ではなくリテラル制御文字として扱われます。制御文字は印刷時に表示されないため、一緒には表示されませんprintf "'%s'"。出力を似たようなものにリンクすると、すでに述べた他の回答のようod -cに表示されます。printf "%q"

実際に入力がない場合、結果は異なります。以下を使用してもここでは空ですprintf "%q"

$ f()  { read -rn 1  x ; printf "%q\n" "$x"; }
$ printf "" | f
''

ここで改行文字が返されない理由は2つありますread。まず、読み取り用のデフォルトの行区切り文字なので、出力として返されます。第二に、これはデフォルト値の一部でもあり、そのIFS一部readである場合は先頭と末尾のスペースを削除しますIFS

read -dしたがって、デフォルトの区切り文字を変更する必要があります。そして明らかなIFS

$ g() { IFS= read -rn 1 -d '' x ; printf "%q\n" "$x"; }
$ printf "\n" | g
$'\n'

read -d ""区切り文字を効果的に NUL バイトにします。これは、まだ入力なしとNULバイト入力の違いが不明であることを意味します。

$ printf "" | g
''
$ printf "\000" | g
''

入力がなくてもreadfalseが返されるので、$?それを検出できることを確認できます。

答え4

read -r var
status=$?
echo "\$var='$var':\$?=$status"

改行と Ctrl-D ケースは状態変数で区別されます。

改行文字の場合、ステータスはtrue(0)、Ctrl-Dを押すとステータスはfalse(1)です。

関連情報