シェルスクリプト:大文字と小文字を入力しながら「qを入力して終了」を追加します。

シェルスクリプト:大文字と小文字を入力しながら「qを入力して終了」を追加します。

私はシェルスクリプトを書いています:

#!/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設定できます。falsewhile

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)を使用して入力された文字があるかどうかを確認します。この場合、ループを終了したい文字があるかどうかをテストするためにread1つずつ読みます()。-N 1q

read -t 0 ループの各繰り返しが完了するので、わずかな計算オーバーヘッドが追加されますwhile。あなたの例では、オーバーヘッドは無視できます。


を使用して突然中断可能なループを実行するには、type q to quit別のアプローチを取る必要があり、フォアグラウンドコードでreadaを待っている間にバックグラウンドコードでコードの中断可能な部分を実行する必要があるため、状況はより複雑になります。readバックグラウンドコードが定期的に完了したら、ロックを解除する方法が必要です。

これを達成するには、次のように Bash 機能の広範な拡張が必要です。

  1. コードの割り込み可能部分をバックグラウンドサブシェルに別々にカプセル化します。以前に行ったものと少し似ていますが、特別な注意が必要です。
  2. 背景サブシェルから前景に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」します。

関連情報