私はシェルスクリプトを書いています:
#!/bin/bash
i=1;
sp="/-\|";
no_config=true;
echo "type \"continue\" to exit this while loop";
echo "if you feel the conditions for continuing sucessfully have been met... ";
while $no_config;
do printf "\b${sp:i++%${#sp}:1}";
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false;
sleep 1;
done;
# do more stuff
これで、ユーザーが開始したループ終了も追加したいと思います。
複雑に見えますね。
当初、私はこれを見つけて希望を高めました。 https://unix.stackexchange.com/a/228152/228658 ただし、読み取りコマンドの実行が停止したことがわかりました。
私はこれをしたくありません。私はいつでも「q」を押すと終了しますが、上下にスクロールする機能を妨げないマニュアルページのようなものが欲しいです。バックグラウンドで入力を慎重に待っています。
シェルスクリプトでこれをどのように達成できますか?
答え1
より簡単な解決策は、ユーザーにCtrl-C
ループ終了を押してSIGINT
シグナルを送信し、スクリプトを終了するように指示することです。
スクリプトを終了する前に他のタスクを実行するには、次の信号をキャッチできます。
cleanup() {
# ...
exit 0
}
trap 'cleanup' INT
あるいは、サブシェルでシェルの 1 つを実行して、シェルの 1 つがシャットダウンした場合、他のシェルもシャットダウンすることもできます。これにより、入力の1つをブロックし、他の入力は異なるチェックを実行できます。
#!/bin/bash
i=1
sp="/-\|"
no_config=true
echo "type \"continue\" to exit this while loop if you feel the conditions for continuing sucessfully have been met... "
(
while $no_config
do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
kill $$
) &
child_pid=$!
while $no_config
do
read -r typed_continue
[[ "$typed_continue" = "continue" ]] && no_config=false
sleep 1
done
kill $child_pid
答え2
残念ながら、Bashはこのような制御には適していません。
しかし、うまくいくことを得る方法があります。
最も単純なものから最も複雑なものまでいくつか紹介します。
終了するには、Ctrl+C と入力します。
ループがCtrl + Cで終了するように、Ctrl + C(INT信号をキャッチして処理)をインストールしてtrap
値をno_config
設定できます。false
while
i=1
sp='/-\|'
no_config=true
echo "type Ctrl+C to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
trap 'no_config=false' INT
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
trap 'trap - INT; kill -INT $$' INT
echo "do more stuff"
(対話型シェルで上記のコードを実行する場合覚えるコードの機能によってシェルが汚染されないように、コード全体を先行(
および末尾にまとめます。)
)
while
ループ内ですべてのコマンドが完了した場合にのみ、ループは中断されます$no_config
。これはwhile
、ループ内で実行する操作によっては、この動作を望む場合もあれば望ましくない場合もあります。あなたの例では、実際の「遅い」操作は明示的であるため、突然中断するsleep 1
必要はありませんが、とにかくループを中断するには、最も簡単な付録はダイ全体を終了できるサブシェルを使用することです。このように:
trap 'kill $!' INT
(
i=1
sp='/-\|'
no_config=true
echo "type Ctrl+C to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
) &
wait
trap 'trap - INT; kill -INT $$' INT
echo "do more stuff"
ここでは、「より多くのタスクを実行する」部分を続行するtrap
ためにサブシェルを終了するようにINT(Ctrl + C)を設定しました。wait
ただし、この方法でループ内に設定された変数は、「より多くの作業」部分に伝播されません。この問題を解決するには、一時ファイルや名前付きFIFOなどの補助通信が必要です。
終了するには、「q」と入力してください。
read
任意の文字で終了できるようにする上記の方法の代替(したがってCtrl + Cも通常のタスクとして保持)は、ループに非ブロック機能を含めることです(Bash v4 +で利用可能)。これには、端末設定のためのいくつかの補助準備も必要です。全体の内容は次のとおりです。
term_settings="$(stty -g)"
trap 'stty "${term_settings}"' EXIT
i=1
sp='/-\|'
no_config=true
echo "type exactly \"q\" to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
stty -icanon -echo
while $no_config; do
while read -t 0 && read -rN 1 ; do [ "${REPLY}" = q ] && break 2 || true; done
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
stty "${term_settings}"
echo "do more stuff"
ここでは、後で終了時に復元できるように、まず端末の現在の設定を保存してから、入力された文字表示()と行ベースの-echo
キーボード入力(-icanon
)を無効にします。while
ループでは、read -t 0
(non-blocking)を使用して入力された文字があるかどうかを確認します。この場合、ループを終了したい文字があるかどうかをテストするためにread
1つずつ読みます()。-N 1
q
read -t 0
ループの各繰り返しが完了するので、わずかな計算オーバーヘッドが追加されますwhile
。あなたの例では、オーバーヘッドは無視できます。
を使用して突然中断可能なループを実行するには、type q to quit
別のアプローチを取る必要があり、フォアグラウンドコードでread
aを待っている間にバックグラウンドコードでコードの中断可能な部分を実行する必要があるため、状況はより複雑になります。read
バックグラウンドコードが定期的に完了したら、ロックを解除する方法が必要です。
これを達成するには、次のように Bash 機能の広範な拡張が必要です。
- コードの割り込み可能部分をバックグラウンドサブシェルに別々にカプセル化します。以前に行ったものと少し似ていますが、特別な注意が必要です。
- 背景サブシェルから前景にCtrl + Cをシミュレートします
read
。
while
これはまた、前述のように、ループ内の「より多くの作業」部分に変数を渡すことができないことを意味します(意図的に補助通信を確立する必要があります)。
例は次のとおりです。
#!/bin/bash
term_settings="$(stty -g)"
(
trap 'stty "${term_settings}"' EXIT
_keyboard_waiter=$BASHPID
(
i=1
sp='/-\|'
no_config=true
echo "type exactly \"q\" to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
kill -INT $_keyboard_waiter
) &
while read -rsN 1 && ! [ "${REPLY}" = q ] ; do :; done
{ kill $! && wait; } 2>/dev/null
)
echo "do more stuff"
ここには次のものがあります。
- シグナルを処理する必要があるため、スクリプトファイルを使用してすべてをよりよく使用できます。
read
停止時に必要であり、後で終了時に復元できるように、起動時に端末設定が保存されました。- サブシェルはフォアグラウンドで実行され、キーボード入力を待機する役割を果たします。
- サブサブシェルは、コードの中断可能な部分のためにバックグラウンド(&)で実行されます。
kill -INT
背景サブシェルread
割り込みに使用されるサブシェル。これは Bash が処理する対応するサブシェルに対して Ctrl+C をエミュレートします。- 改行( )に関係なく、1文字だけ待機/受け入れ中に
read
入力された文字()を表示しないオプションとバックスラッシュをエスケープ文字として解釈しない一般的なオプション-s
-N 1
-r
- 「q」が実際に入力されると、背景サブシェルを単に「kill&wait」します。