web-dev-qa-db-ja.com

POSIXシェルでstdinから単一の文字を読み取ることはできますか?

read -rのみ POSIXで指定 ; NUM文字の読み取りに使用されるread -n NUMは、そうではありません。 stdinから指定された数の文字を読み取った後に自動的に戻るポータブルな方法はありますか?

私のユースケースは、次のようなプロンプトを出力しています:

Do the thing? [y/n]

可能であれば、yまたはnを入力した後、ユーザーが後でEnterキーを押す必要なく、プログラムを自動的に続行させたいと思います。

4
Ash

1文字を読み取るとは、完全な文字を取得するまで、一度に1バイトを読み取ることです。

POSIXツールチェストで1バイトを読み取るために、dd bs=1 count=1があります。

ただし、端末デバイスがicanonモードの場合(通常はデフォルト)、端末デバイスからの読み取りは、 Return (別名 Enter)それまでは、端末のデバイスドライバーは、使用できるラインエディターの形式を実装しているため Backspace または入力した内容を修正するためのその他の編集文字。入力した内容は、編集中の行を送信したときにのみ、読み取りアプリケーションで使用できるようになります( Return または Ctrl+D)。

そのため、kshread -n/Nまたはzshread -kは、stdinが端末デバイスであることを検出したら、そのデバイスをicanonモードから外します。 、端末から送信されるとすぐにバイトを読み取ることができるようにします。

ここで、kshread -n npからn文字1行からのみを読み取りますが、改行すると停止します。文字が読み取られます(-N nを使用してn文字を読み取ります)。 bashは、ksh93とは異なり、-n-Nの両方に対してIFSおよびバックスラッシュ処理を実行します。

zshread -kまたはksh93read -N1またはbashIFS= read -rN 1を模倣する、つまり、POSIXlyのstdinから1文字だけを読み取る:

readc() { # arg: <variable-name>
  if [ -t 0 ]; then
    # if stdin is a tty device, put it out of icanon, set min and
    # time to sane value, but don't otherwise touch other input or
    # or local settings (echo, isig, icrnl...). Take a backup of the
    # previous settings beforehand.
    saved_tty_settings=$(stty -g)
    stty -icanon min 1 time 0
  fi
  eval "$1="
  while
    # read one byte, using a work around for the fact that command
    # substitution strips the last character.
    c=$(dd bs=1 count=1 2> /dev/null; echo .)
    c=${c%.}

    # break out of the loop on empty input (eof) or if a full character
    # has been accumulated in the output variable (using "wc -m" to count
    # the number of characters).
    [ -n "$c" ] &&
      eval "$1=\${$1}"'$c
        [ "$(($(printf %s "${'"$1"'}" | wc -m)))" -eq 0 ]'; do
    continue
  done
  if [ -t 0 ]; then
    # restore settings saved earlier if stdin is a tty device.
    stty "$saved_tty_settings"
  fi
}
8

this answer ...から引用すると、これはbashで私にとってうまくいきます:

echo -n "Is this a good question (y/n)? "
old_stty_cfg=$(stty -g)
stty raw -echo ; answer=$(head -c 1) ; stty $old_stty_cfg # Careful playing with stty
if echo "$answer" | grep -iq "^y" ;then
    echo Yes
else
    echo No
fi
2
flaysomerages