鼻悪魔

鼻悪魔

ファイル入力があります。

$ cat input
1echo 12345

次のプログラムがあります

初版

#include <stdio.h>
#include <stdlib.h>

int main() {
  system("/bin/bash -i");
  return 0;
}

今これを実行すると

$ gcc -o program program.c
$ ./program < input
bash: line 1: 1echo: command not found
$ exit

すべてが期待どおりに動作します。

getchar()これで、ファイル入力の最初の文字を無視したいのでsystem()

2番目のバージョン:

#include <stdio.h>
#include <stdlib.h>

int main() {
  getchar();
  system("/bin/bash -i");
  return 0;
}

驚くべきことに、bashは入力がないかのようにすぐに終了します。

$ gcc -o program program.c
$ ./program < input
$ exit

質問Bashが入力を受け取らないのはなぜですか?

ノート いくつか試してみたところ、基本プロセスで新しいサブプロセスをフォークすると問題が解決することがわかりました。

3番目のバージョン

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
  getchar();
  if (fork() > 0) {
    system("/bin/bash -i");
    wait(NULL);
  }
  return 0;
}

$ gcc -o program program.c
$ ./program < input
$ 12345
$ exit

オペレーティングシステムUbuntu 16.04 64ビット、gcc 5.4

答え1

ファイルストリーム次のように定義されます。:

参照された対話型デバイスがないことが確認できる場合にのみ完全にバッファリングされます。

標準入力にリダイレクトするので、stdinは非対話型なのでバッファリングされます。

getcharバッファをストリームから埋め、そのバイトを消費してからバイトを返すストリーム関数。systemただfork-execを実行してくださいそのため、子プロセスは開いているすべてのファイル記述子をそのまま継承します。標準入力から読み取ろうとすると、親bashプロセスですべての内容を読み取ったため、すでにファイルの末尾にあることがわかります。


あなたの場合、その単一バイトを子プロセスに渡す前にのみ消費しようとしているので、次のようになります。

このsetvbuf()関数は、ストリームが指すストリームが開かれたファイルに関連付けられた後、ストリームで他の操作が実行される前に使用できます。

したがって、以前に適切な呼び出しを追加してくださいgetchar()

#include <stdio.h>
#include <stdlib.h>

int main() {
  setvbuf(stdin, NULL, _IONBF, 0 );
  getchar();
  system("/bin/bash -i");
  return 0;
}

stdinバッファなし()に設定して目的の_IONBF操作を実行します。getchar単一バイトのみが読み取られ、残りの入力は子プロセスで使用できます。使用する方が良いかもしれませんread代わりに、この場合はフルストリームインターフェイスを使用しないでください。


POSIXは、フォーク後の両方のプロセスがハンドルにアクセスできるときに特定の動作を強制します。が、明らかに明示されている

いずれかのプロセスによって実行された唯一の操作が次のいずれかである場合exec関数 [...]、このプロセスではハンドルにアクセスできません。

これはsystem()特別な措置を講じる必要がないことを意味します。なぜならこれはただフォーク実行(fork-exec)です。

回避策に問題がある可能性がありますfork。ハンドルが両側からアクセスできる場合、では初めて:

読み取りを許可するモードでストリームが開き、デフォルトの開かれたファイル記述が見つかるデバイスを参照する場合、アプリケーションは以下を実行する必要があります。fflush()、そうでなければストリームは閉じます。

呼ぶfflush()読み取りストリームでは、次のことを意味します。

デフォルトのオープンファイル記述のファイルオフセットは、ストリーム内のファイルの場所に設定する必要があります。

したがって、記述子の位置はストリームと同じ位置の1バイトにリセットされなければならず、後続の子プロセスはその時点で始まる標準入力を受け取ります。

また、2番目(子)ハンドルの場合:

上記の最初のハンドルの要件に加えて、ファイルオフセットを明示的に変更する関数で以前にアクティブなハンドルを使用した場合、アプリケーションは次のことを行う必要があります。lseek()またはfseek()(ハンドルの種類に応じて)適切な位置に移動します。

私は「適切な場所」がおそらく同じであると思います(もう指定されていませんが)。getchar()「ファイルオフセットを明示的に変更しました」という呼び出しがあるため、この場合に適用する必要があります。この詩の目的は、両方のフォークで作業することが同じ効果を持つ必要があるため、および両方ともfork() > 0同じfork() == 0ように機能する必要があることです。しかし、その四半期では実際には何も起こらないので、これらの規則を親や子にまったく使用してはならないと言うのが妥当です。

正確な結果はおそらくプラットフォームによって異なります。少なくとも「アクセス可能」と見なされる項目、または第1のハンドルと第2のハンドルに対する直接的な指定はない。親プロセスの以前の最も重要なケースがあります。

この開かれたファイル記述子に対するハンドルに対して実行される唯一の追加操作がハンドルを閉じることであれば、操作は必要ありません。

これはプログラムが終了するため、おそらくプログラムに適用されます。そうであれば、を含む残りのすべてのケースをfflush()スキップする必要があり、表示される動作は仕様から外れます。確かに、呼び出しはfork()ハンドルに対してアクションを実行すると見なされますが、明確でも明確でもないので、そうは信じません。要件にも「どちらか」と「または」が十分なので、多くのバリエーションが許可されているようです。

多くの理由により私の考えにあなたが見ている行動はバグかもしれません。、または少なくとも規範の寛大な解釈。私の全体的な解釈は、各場合に1つのブランチが何もしないため、これらのfork規則を適用しないでください。記述子の場所は無視する必要があります。私はこれについてはわかりませんが、最も簡単な読書のようです。


私はfork働くために技術に頼らないでしょう。あなたの3番目のバージョンはここでは動作しません。使用setbuf/setvbuf代わりに。可能であれば私も使用しますpopenあるいは、ストリームとファイル記述子の相互作用のあいまいさに依存するのではなく、必要なフィルタリングを使用してプロセスを明示的に設定することと同じです。

答え2

私は拍手を送るマイケル・ホーマーの答え、POSIX参照を見つけてください。しかし、私はこの内容を少なくとも70%は理解したと思い、彼の答えを完全に理解していないので、このように準備しました。長い話を短く私は彼のバージョンを信じています。

  • getchar(別名getc)、ファイルから読み取るときに実際にファイルから1つのブロック(またはファイル全体のうち小さい方)を読み込みます。データをバッファに読み込みます。その後、バッファの最初の文字を提供します。

    • 後続の呼び出しは、バッファが使い果たされるまでバッファから後続の文字を返します。その後、ファイルから別のブロックを読み取ろうとします。この「バッファリングされたI / O」は、プログラムがファイルを読み込み、一度に少しずつ処理することをより効率的にします。


    バッファリングされたファイルストリームの論理I / Oポインタがファイル内の文字であっても、ファイル記述のI / Oポインタがファイルのブロックに移動されます(または小さなファイルの場合は最後)。ファイルの)。分岐すると、system子プロセスはファイル記述を継承しますが、ファイルストリームは継承しないため、bashはファイルの末尾にあるファイルI / Oポインタを継承します。したがって、ファイルを読み込むとEOFが得られます。

  • プログラムの3番目のバージョンでは直接呼び出されmainます(単に呼び出されるforkわけではありません)。もちろん、プログラムの2番目のバージョンと同様に。  systemforkmaingetcharMichaelが見つけたPOSIXドキュメントISO C規格の拡張を説明していることを明確にしてください。その秘密の言語で分かる限り、main上記の問題に気づいたのはCコンパイラが責任があると言います。 fork関数を呼び出すルーチンによって直接呼び出される場合stdio そしてそれを解決してください。したがって、明らかにfork(または他のメカニズム?)stdioファミリと対話して、ファイル記述の物理I / Oポインタが再び移動し、ファイルポインタの論理I / Oポインタと同期します。つまり、文字をファイルに入れます。 。 2番目のものfork(隠されたもの)systemは、シェルが2番目のバイトから始まるファイルを読み取ることができるので、偶然これから利益を得ます。

関連マニュアルページを大まかに検索しましたが、この動作を説明する情報が見つかりませんでした。私もあなたの結果を再現できません。したがって、これはPOSIXで指定された動作かもしれませんが、まだ普遍的には実装されていないようです。

答え3

鼻悪魔

man 3 getchar説明する:

The getchar() function shall be equivalent to getc(stdin).

そしてman 3 getc説明する:

It is not advisable to mix calls to  input  functions  from  the  stdio
library  with  low-level  calls  to  read(2)  for  the  file descriptor
associated with the input stream; the results  will  be  undefined  and
very probably not what you want.

インタラクティブモードのBashはread(readline経由で)などを使用して入力に直接アクセスできます。だから私たちは鼻悪魔

関連情報