同じコマンド順序でコマンドラインとスクリプトで異なる結果が発生する

同じコマンド順序でコマンドラインとスクリプトで異なる結果が発生する

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; }

cmd1cmd2同じプロセスグループとフォアグラウンドに配置されますが、実行時にzshcmd3はバックグラウンドプロセスグループに戻り、両方ともバックグラウンドになりますcmd1cmd3

~$ (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)

シェルが非対話型の場合はジョブ制御を実行しないため、スクリプトではこの問題は発生しません。スクリプト内のすべてのプロセスは同じプロセスグループで実行され、それを起動したシェルが呼び出された方法に応じて前景または背景(ターミナルで実行されると仮定)になります。

関連情報