web-dev-qa-db-ja.com

バッシュ:キーを押したまま無限ループを終了するには?

任意のキーが押されたときに停止する無限ループを作成する必要があります。

残念ながら、これはキーが押されたときにのみループします。

アイデアはどうですか?

#!/bin/bash

count=0
while : ; do

    # dummy action
    echo -n "$a "
    let "a+=1"

    # detect any key  press
    read -n 1 keypress
    echo $keypress

done
echo "Thanks for using this script."
exit 0
26
janmartin

標準入力を非ブロッキングモードにする必要があります。これが機能する例です:

#!/bin/bash

if [ -t 0 ]; then
  SAVED_STTY="`stty --save`"
  stty -echo -icanon -icrnl time 0 min 0
fi

count=0
keypress=''
while [ "x$keypress" = "x" ]; do
  let count+=1
  echo -ne $count'\r'
  keypress="`cat -v`"
done

if [ -t 0 ]; then stty "$SAVED_STTY"; fi

echo "You pressed '$keypress' after $count loop iterations"
echo "Thanks for using this script."
exit 0

2014/12/09を編集:stty-icrnlフラグを追加してReturnキーを正しくキャッチし、readの代わりにcat -vを使用しますスペースをキャッチするために。

catは、十分な速度でデータが供給されている場合、複数の文字を読み取る可能性があります。望ましい動作でない場合は、cat -vdd bs=1 count=1 status=none | cat -vに置き換えます。

2019/09/05を編集:stty --saveを使用してTTY設定を復元します。

33
sam hocevar

readには文字数パラメーター-nとタイムアウトパラメータ-t使用できます。

から bash manual

-nnchars readは、入力の完全な行を待つのではなく、nchars文字を読み取った後に戻りますが、ncharsより少ない文字が読み取られる前に、区切り文字を優先します。デリミタ。

-ttimeout

入力(または指定された数の文字)の完全な行がタイムアウト秒以内に読み取られない場合、readがタイムアウトして失敗を返します。タイムアウトは、小数点に続く小数部分のある10進数です。このオプションは、readが端末、パイプ、またはその他の特殊ファイルから入力を読み取る場合にのみ有効です。通常のファイルから読み取る場合は効果がありません。読み取りがタイムアウトした場合、readは、読み取られた部分的な入力を指定された変数名に保存します。タイムアウトが0の場合、readはデータを読み取ろうとせずに、すぐに戻ります。指定されたファイル記述子で入力が利用可能な場合、終了ステータスは0です。それ以外の場合はゼロ以外です。タイムアウトを超えると、終了ステータスは128を超えます。

ただし、組み込みの読み取りは、独自の設定を持つ端末を使用します。他の回答が指摘したように、sttyを使用して端末のフラグを設定する必要があります。

#!/bin/bash
old_tty=$(stty --save)

# Minimum required changes to terminal.  Add -echo to avoid output to screen.
stty -icanon min 0;

while true ; do
    if read -t 0; then # Input ready
        read -n 1 char
        echo -e "\nRead: ${char}\n"
        break
    else # No input
        echo -n '.'
        sleep 1
    fi       
done

stty $old_tty
7
Paul

通常、私は単純なCTRL-Cでbashの無限ループを壊してもかまいません。これは、たとえばtail -fを終了するための従来の方法です。

2
mouviciel

これが別の解決策です。スペース、エンター、矢印など、どのキーを押しても機能します。

Bashでテストされた元のソリューション:

IFS=''
if [ -t 0 ]; then stty -echo -icanon raw time 0 min 0; fi
while [ -z "$key" ]; do
    read key
done
if [ -t 0 ]; then stty sane; fi

Bashとdashでテストされた改善されたソリューション:

if [ -t 0 ]; then
   old_tty=$(stty --save)
   stty raw -echo min 0
fi
while
   IFS= read -r REPLY
   [ -z "$REPLY" ]
do :; done
if [ -t 0 ]; then stty "$old_tty"; fi

Bashでは、REPLYコマンドのread変数を省略することもできます。これは、この変数がデフォルトの変数だからです。

0
YSN

私は this forum post を見つけ、eraの投稿をこのかなり一般的な使用形式に書き直しました:

# stuff before main function
printf "INIT\n\n"; sleep 2

INIT(){
  starting="MAIN loop starting"; ending="MAIN loop success"
  runMAIN=1; i=1; echo "0"
}; INIT

# exit script when MAIN is done, if ever (in this case counting out 4 seconds)
exitScript(){
    trap - SIGINT SIGTERM SIGTERM # clear the trap
    kill -- -$$ # Send SIGTERM to child/sub processes
    kill $( jobs -p ) # kill any remaining processes
}; trap exitScript SIGINT SIGTERM # set trap

MAIN(){
  echo "$starting"
  sleep 1

  echo "$i"; let "i++"
  if (($i > 4)); then printf "\nexiting\n"; exitScript; fi

  echo "$ending"; echo
}

# main loop running in subshell due to the '&'' after 'done'
{ while ((runMAIN)); do
  if ! MAIN; then runMain=0; fi
done; } &

# --------------------------------------------------
tput smso
# echo "Press any key to return \c"
tput rmso
oldstty=`stty -g`
stty -icanon -echo min 1 time 0
dd bs=1 count=1 >/dev/null 2>&1
stty "$oldstty"
# --------------------------------------------------

# everything after this point will occur after user inputs any key
printf "\nYou pressed a key!\n\nGoodbye!\n"

実行 このスクリプト

0
DogeCode