パイププロセスでシェルを開くことができないのはなぜですか?

パイププロセスでシェルを開くことができないのはなぜですか?

私の問題を次のコードに圧縮しました。

#include <stdio.h>

int main(){
    char buffer[256];

    printf("Enter input: ");
    scanf("%s", buffer);

    system("/bin/sh");
    return 0;
}

ユーザー入力でプログラムを実行すると、次のような結果が得られます。

user@ubuntu:~/testing/temp$ ./main
Enter input: Test
$ 

最後の行はプログラムが起動されるシェルです。

ただし、パイプの入力としてプログラムを実行すると、次のようになります。

user@ubuntu:~/testing/temp$ echo 'test' | ./main
Enter input: user@ubuntu:~/testing/temp$

プログラムがシェルを開いていないようです。


少し悩んだ後、私は次の事実に気づきました。

user@ubuntu:~/testing/temp$ (python -c "print 'test'" ; echo 'ls') | ./main
a.out  main  main.c
Enter input: user@ubuntu:~/testing/temp

ls開いたシェルからコマンドを実行できます。

したがって、2つの質問があります。

  1. 最初のケースのようにシェルが開かないのはなぜですか?
  2. この問題にどのように対処する必要がありますか?実行するコマンドを決定する必要があるのは非常に不便です。今後プログラムの実行:私は実行するコマンドを動的に選択できるシェルが欲しいです。

答え1

  1. 最初のケースのようにシェルが開かないのはなぜですか?

最初のケースstdinは、端末とシェルが対話型であることです。それはあなたの命令などを待ちます。

2番目のケースstdinはパイプで、シェルは非対話型です。プログラムは最初の行stdin(文字列test\n)を消費し、シェルがそれを読み取ろうとし、stdinそれを確認しますEOF。これは、入力を受け取るプログラムがEOF実行する必要がある操作であるためです。

3番目の場合でも、同じ理由でシェルは再び非対話型になります。つまり、その行の最初の行を消費し、シェルがそれをscanf()読み取ります。シェルが実行され、より多くのコマンドを読み取ろうとし、確認して終了します。stdintest\nlslsEOF

  1. この問題にどのように対処する必要がありますか?

「処理」とは、stdinパイプに接続している間に対話型シェルを実行することを意味する場合、解決策はpty(7)

答え2

Cプログラムは(read x; /bin/sh)

コマンドラインにこのコードスニペットを入力すると、標準入力がターミナルキーボードに接続され、shファイルの終了条件が発生するまで1行を読み、さらに行を読みます。 Ctrl-D を押すとファイル終了条件が発生します)

プログラムに入力をパイピングする例では、入力サイズが制限されています。入力サイズは制限されており、それ以上はありません。最初の行(存在する場合)以降の行は解釈され終了shします。

catを使用してキーボード入力を次に渡すことで、パイプレスのケースをシミュレートできますsh

(echo test; cat) | (read x; /bin/sh)

おそらく:

(echo test; cat) | ./main

しかし、これはシェルを直接実行するのと同じくらい良くないかもしれません。入力が端末ではないことを検出すると、行編集機能が無効になることがあります。

答え3

パイプに接続された対話型シェルを実行するには、対話型にするだけです。

input | /bin/sh -i

シェルには対話用の端末は必要ありません。つまり、インタラクティブな入力が必要です。一般に、対話式に実行される場合、シェルの動作は端末とほとんど関係がありません。(少なくともによると仕様)- 通常、エラー処理が何よりも重要な非対話型シェルの動作とは異なります。対話型シェルは、非対話型シェルを終了させるエラー条件では終了しない傾向があります。シェルは基本ただし、入力が端末から来る場合は対話モードです。

一部のシェルは対話型の使用に端末入力が必要なようですが、これは勘違いです。実際には、これらのシェルは例のケースと非常によく似ています。bashたとえば、設定はreadlineターミナルI / Oの詳細を処理し、zshZLEラインエディタを呼び出します。dash (ビルドタイムオプションなしでコンパイルした場合小さい)BSDライブラリlibeditへのリンク。これらのラインエディタはターミナル入力を読み取り、それをライン固有のシェルスクリプトに似たものとして扱い、必要に応じてシェルが実行します。

しかし、これらのエディタには問題はありません。プロンプトとexecve通話で判断すると、dashユーザーに電話をかけています。小さいビルド時間オプション(Debian デフォルト)この場合はうまくいきます。ただし、この機能で得られる唯一の行編集機能は、端末の行規則でデフォルトで提供する機能だけです。(望むよりstty)

問題は、シェルに入力がないことです。他の場所で指摘したように、シェルがEOFを検出すると死にます。あなたもできます...

input | /bin/sh -i -o ignoreeof

しかし、あなたはおそらくいいえ結果が好きです。dash一部のシェルのように、10回連続した空の読み取りは終了しません。印刷だけです...

Type 'exit' to exit the shell

...標準エラーで永遠に。少し良かったかもしれません...

cat input - | /bin/sh -i

...ファイルのシェルコマンドをinputstdinからstdoutにcatリンクし、-結果を対話的に実行します/bin/sh。これはうまくいくでしょう。ただし、少なくとも機能するバックスペースキーを取得できるように、回線ルールをstty標準状態のように設定し、適切なキーを使用したい場合があります。eraseすでに正しく設定されている可能性がありますが、とにかく確認する価値があります。

他の方法...

echo ": some command; exec <$(tty)" | /bin/sh -i

上記の方法は、パイプが制御端子の現在の前景操作であるために機能します。パイプは現在端子入力を所有し、cat実際に読み取っています。対照的に、python -cそれをecho読まないでください。コマンドライン引数によって生成された出力のみを渡します。したがって、出力が終わったら、シェルの入力も同様です。echo2番目の例で行ったように、シェルが他の場所で入力を見つけるように指示しない限り、これは正しいです。

端末はシェルに入力できるさまざまなソースの1つにすぎず、さまざまな方法で処理できます。ターミナルのセッションリーダーは、bashパイプのターミナル制御が終了して制御権を再取得し、次の手順が実行されるのを待ちます。情報は非同期信号として伝達される。これが端末の用途です。端末は読み取り/印刷プロセス中に表示/入力ペアを多重化します。彼らはこの仕事を見事にしています。

たとえば、

$ PS1='bgsh1: ' sh -i +m & PS1='bgsh2: ' sh -i +m &
$ bgsh1: bgsh2: 
[2] + Stopped (tty input)        sh -i +m
[1] - Stopped (tty input)        sh -i +m
$ i=0; while [ "$((i+=1))" -lt 5 ]; do fg "%$(((i%2)+1))"; done
sh -i +m

bgsh2: var=something
bgsh2: kill -TSTP $$
sh -i +m

bgsh1: var=else
bgsh1: kill -TSTP $$
sh -i +m
bgsh2: echo $var; kill -TSTP $$
something
sh -i +m
bgsh1: echo $var; kill -TSTP $$
else
[1] + Stopped                    sh -i +m
[2] - Stopped                    sh -i +m
$ 

関連情報