zshからGitブランチを削除するのに役立つユーティリティスクリプトを作成しています。現在は次のとおりです。
git for-each-ref --format="%(refname:short)" refs/heads/ |
while read -r line; do
printf "remove %s? (y/n)" $line;
read ans </dev/tty;
case "$ans" in
y|Y) echo "$line";;
esac;
done
これは、入力プロンプトに応答すると1つずつ機能し、分岐を印刷します。
ただし、スクリプトではなくコマンドラインから直接同じコマンドシーケンスを実行すると、最初のブランチがスキップされます。
git for-each-ref --format="%(refname:short)" refs/heads/ | while read -r line; do printf "remove %s? (y/n)" $line; read ans </dev/tty; case "$ans" in y|Y) echo "$line";; esac; done
これらの動作の違いが発生する理由と回避策をご存知ですか?
答え1
と zsh
同様に、ksh
パイプラインの最後のコンポーネントはサブシェルではなく現在のシェルで実行されます。
対話型シェルインスタンスのフォアグラウンドでタスクを実行すると、シェルは新しいプロセスグループを作成し、そのプロセスグループを端末のフォアグラウンドプロセスグループにします。次に、そのプロセスグループに入れるジョブのすべてのプロセスを作成します。
しかし、以下では
cmd | { some-builtin; some-builtin; }
を実行する主要なシェルプロセスですsome-builtin
。そして、そのプロセスはすでにプロセスグループのリーダーであるため、フォアグラウンドプロセスグループに含めることはできません。これは、実行中のプロセスが終了しない限りcmd
バックグラウンドに残ることを意味します。
フォアグラウンドプロセスグループに属していないプロセスは、制御端末からデータを読み取ることができない。これはSIGTTIN
シグナルによって一時停止し、これらのシグナルを無視すると(対話型シェルの基本プロセスのように)、read()
システムコールがEIOエラーで失敗します。
ksh93に同じ内容が表示されない唯一の理由は、組み込み機能がread
読み取り前に実行select()
/動作するためですpoll()
。したがって、返す前に何かを入力すると、EIOのみが表示されますgit
。
には、zsh
次のようなより深刻な問題があります。
cmd1 | { cmd2; cmd3; }
cmd1
cmd2
同じプロセスグループとフォアグラウンドに配置されますが、実行時にzshcmd3
はバックグラウンドプロセスグループに戻り、両方ともバックグラウンドになりますcmd1
。cmd3
~$ (sleep 1; cat) | { ps -opid,pgid,tpgid,stat,args; ps -opid,pgid,tpgid,stat,args; sleep 2; echo done; }
PID PGID TPGID STAT COMMAND
571038 571038 653553 Ss zsh
653553 653553 653553 S+ zsh
653554 653553 653553 S+ sleep 1
653555 653553 653553 R+ ps -opid,pgid,tpgid,stat,args
PID PGID TPGID STAT COMMAND
571038 571038 571038 Ss+ zsh
653553 653553 571038 S zsh
653554 653553 571038 S sleep 1
653556 653553 571038 R ps -opid,pgid,tpgid,stat,args
zsh: suspended (tty input) sleep 2
$ ps -opid,pgid,tpgid,stat,args
PID PGID TPGID STAT COMMAND
571038 571038 654415 Ss zsh
653553 653553 654415 T cat
653557 653553 654415 T sleep 2
653558 653558 654415 T zsh
654415 654415 654415 R+ ps -opid,pgid,tpgid,stat,args
(+
フォアグラウンドは、TPGIDがフォアグラウンドプロセスグループであることを意味します。)
ここでは、パイプライン全体またはサブシェルの右端の要素を実行して問題を解決できます。
(git | while ...; done)
または:
git | (while ...; done)
シェルが非対話型の場合はジョブ制御を実行しないため、スクリプトではこの問題は発生しません。スクリプト内のすべてのプロセスは同じプロセスグループで実行され、それを起動したシェルが呼び出された方法に応じて前景または背景(ターミナルで実行されると仮定)になります。