単一の文字を読み取ると、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
でこれを行う別の方法はありますか?
read -n "$n"
(POSIX機能ではありません)を使用し、stdinが端末デバイスの場合、read
は端末をicanon
モードから外します。それ以外の場合、read
は端末回線の規律の内部回線エディターと次に、$n
文字または改行が読み取られるまで、一度に1バイトを読み取ります(無効な文字を入力すると、予期しない結果が表示される場合があります)。
1行から最大$n
文字を読み取ります。また、入力からIFS文字を削除しないように、$IFS
を空にする必要があります。
icanon
モードを終了したため、^D
は特別ではなくなりました。だから押すと Ctrl+D、^D
文字が読み取られます。
端末が何らかの方法で切断されていない限り、端末デバイスからeofは表示されません。 stdinが別のタイプのファイルの場合、eofが表示される場合があります(: | IFS= read -rn 1; echo "$?"
の場合、stdinは空のパイプ、または/dev/null
からstdinをリダイレクトする場合など)
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で行うのはかなり複雑です。
これは次のようなものになります(ASCIIベースの(たとえば、EBCDICではなく)システムを想定しています)。
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"
}
完全な文字が読み取られた場合にのみ戻ることに注意してください。入力が間違ったエンコーディング(ロケールのエンコーディングとは異なる)である場合、たとえば、UTF-8(0xc3 0xa9)が予期されているときに、端末がiso8859-1(0xe9)でエンコードされたé
を送信する場合、次のように入力できます。 é
をいくつでも使用すると、関数は戻りません。 bash
のread -n1
は、2番目の0xe9を返します(両方を変数に格納します)。これはやや優れた動作です。
^C
文字も読みたい場合 Ctrl+C (スクリプトを強制終了させる代わりに、^Z
、^\
...の場合も)、または^S
/^Q
Ctrl+S/Q (フロー制御の代わりに)stty
行に-isig -ixon
を追加できます。 bash
のread -n1
もそれを行わないことに注意してください(オフの場合、isig
も復元されます)。
スクリプトが強制終了された場合など、tty設定は復元されません。 Ctrl+C。 trap
を追加することもできますが、スクリプト内の他のtrap
sを上書きする可能性があります。
zsh
の代わりにbash
を使用することもできます。ここで、read -k
(ksh93
またはbash
のread -n/-N
よりも前の日付)は端末から1文字を読み取り、^D
を単独で処理します(非その文字が入力された場合はゼロ)、改行を特別に扱いません。
if read -k k; then
printf '1 character entered: %q\n' $k
fi
f()
で%s
から%q
:
f() { read -rn 1 -p "Enter a character: " char && \
printf "\nYou entered '%q'\n" "$char"; }
f;f
出力、ユーザーがnewlineと入力した場合、 '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.
実際、Bashでread -rn1
を実行し、^D
を押すと、EOF条件ではなく、リテラル制御文字として扱われます。制御文字は、印刷時に表示されるため、printf "'%s'"
と一緒に表示されません。出力をod -c
などにパイプすると、他の回答ですでに述べたprintf "%q"
と同様に表示されます。
入力として実際には何もないため、結果は異なります。ここでは、printf "%q"
でも空です。
$ f() { read -rn 1 x ; printf "%q\n" "$x"; }
$ printf "" | f
''
ここでread
が改行を返さない理由は2つあります。まず、これは読み取りのデフォルトの行区切り文字であるため、出力として返されます。第2に、これはデフォルトのIFS
の一部でもあり、read
は、先頭と末尾の空白がIFS
の一部である場合、それらを削除します。
したがって、区切り文字をデフォルトから変更するには、read -d
が必要ですandIFS
を空にします。
$ g() { IFS= read -rn 1 -d '' x ; printf "%q\n" "$x"; }
$ printf "\n" | g
$'\n'
read -d ""
は、区切り文字を事実上NULバイトにします。これは、何も入力しない場合とNULバイトを入力する場合の違いをまだ認識しないことを意味します。
$ printf "" | g
''
$ printf "\000" | g
''
入力として何もない場合でも、read
はfalseを返すため、$?
をチェックしてそれを検出できます。
read -r var
status=$?
echo "\$var='$var':\$?=$status"
改行とCtrl-Dの場合は、ステータス変数によって区別されます。
改行の場合、ステータスはtrue(0)ですが、Ctrl-Dが指定されると、ステータスはfalse(1)になります。