エッジケース - PerlのSTDIN入力検出

エッジケース - PerlのSTDIN入力検出

このような質問をどのようにすべきかはよく分かりませんし、ここがそのような質問をするところなのかもしれません。これは非常に複雑に見え、何が起こっているのか完全には理解していません。正直に言うと、これが私がこの問題を解決するのに役立つように書いている理由です。私の究極の目標は学ぶことであり、全体的な問題を解決することではありません。私が説明しようとしている状況が発生したとき、そしてなぜそのようなことが起こったのか理解したいと思います。

私が開発したPerlモジュールがあります。これが行うことの1つは、標準入力(パイプまたはリダイレクト(たとえば<)など)に入力があるかどうかを検出することです。

リダイレクトをキャプチャするために、状況に応じていくつかの異なるチェックを使用します。その1つは、出力でファイル記述子を見つけることです0rlsofかなりうまくいきます。多くのスクリプトは問題なくモジュールを使用していますが、スクリプトがSTDINから入力を受けていると思いますが、そうでない1つのユースケースがあります。これは私がlsof出力に入れたものに関連しています。 。以下は、私がイベントの範囲を絞った条件です。しかし、これが要件のすべてではありません。何か抜けました。とにかく、これらの条件が必要なようですが、おもちゃの例で実装する方法がわからないので、私の直感を理解してください。試してみました。だから私が欠けていることを知っています。何:

  1. バックティックを介してPerlスクリプト内でPerlスクリプトを実行すると(内部スクリプトは意図的にSTDINへの入力を提供したと思いますが、実際にはそうではありません。指摘する必要がありますが、親であるかどうかはわかりません)、開いた場合)
  2. 入力ファイルは、サブディレクトリ内の内部スクリプト呼び出しに提供されます。

0rlsofが報告するファイル記述子を持つファイルは次のとおりです。

/Library/Perl/5.18/AppendToPath

他の条件では、このファイルはlsof出力には表示されません。呼び出しeof(STDIN)の前後に実行すると、lsof結果は毎回1です。 -t STDIN定義されていません。 fileno(STDIN)はい0

私はこの記事を読んだ。ここ私がそれをキャッチすると、それは次のようになります:

>cat /Library/Perl/5.18/AppendToPath
/System/Library/Perl/Extras/5.18

これはPerlパスに追加するように設計されたmacOS-perl専用ファイルのようですが、@INC他のオペレーティングシステムでも同様のメカニズムを提供するかどうかはわかりません。

このファイルがいつ存在するのか、開くのか、いつ閉じるのかを詳しく知りたいです。オフにできますか?インタプリタがファイルの内容を読み込んだようです。しかし、なぜ私のスクリプトで開かれたファイルハンドルでハングするのですか?なぜSTDINにいるのですか?この場合、実際にファイルを直接リダイレクトするとどうなりますか?場合によっては、私が知らない間に子プロセスは親プロセスからそれを継承しますか?

修正する:添字の実行中にSTDINでAppendToPathファイルハンドルを開いたままにするために必要な3番目の(おそらく最終的な)要件が見つかりました。 STDINをオフにする親スクリプトの上部にコード行があることがわかりました(おそらく同様の問題を解決するために追加されました。当時、STDINへの入力検出についてはこれよりはるかに少ないことがわかりました)。私はコメントを閉じて、奇妙なファイルを除外せずにすべてが機能し始めました(つまり、/Library/Perl/5.18/AppendToPathlsofのSTDINで開かれたものとして表示されなくなりました)。コメントアウトされたコードは次のとおりです。

close(STDIN) if(defined(fileno(STDIN)) && fileno(STDIN) ne '' &&
                fileno(STDIN) > -1);

そのコメントには次の内容があります。

#Prevent the passing of active standard in handles to the calls to the script
#being tested by closing STDIN.

それで、私は数年前にこの記事を書いたときに、おそらくstdin検出について学んでいたでしょう。私のモジュールは最終的になどを使用できますが、-t STDINこの-f STDINような問題にはlsofを使用するように切り替えることで、何が起こっているのかをよりよく見ることができます。したがって、-t/-f/-p親モジュールでSTDINを閉じないと、現在のモジュール(lsofまたはlsofを使用する新しい(/restored?)単純化バージョンを使用)が期待どおりに機能します。

しかし、親プロセスがSTDINを閉じたときにファイルが子プロセスのSTDINにある理由をまだ理解したいと思います。

答え1

ユーザーが次のようにリダイレクトせずにインタラクティブシェルからスクリプトを呼び出す場合:

your-script with args

あなたのスクリプトはttyデバイスになるシェルの標準入力を継承し、読み取り+書き込みモードで開く可能性が高いです。

ユーザーが次のように呼び出す場合:

your-script with args < some-file

fd 0 は読み取り専用モードで開きますsome-file(すべてのタイプ)。そうであれば< /dev/pts/0ttyデバイスでもあります。 fifoファイルの場合、stdinはパイプから出てくるようです。そうであれば< /dev/null別のデバイスになります。キャラクターデバイスなど)。

そして:

your-script with args <> some-file

これは、ファイルが読み取り+書き込みモードで開くことを除いて、上記と同じです。この場合、<> /dev/pts/0端末の対話型シェルからリダイレクトされていないスクリプトを呼び出すのとまったく同じです。

そして:

your-script <&-

標準入力が閉じます。

そして:

other-cmd | your-script

stdinはほとんどのシェルではパイプですが(< named-pipeorを実行するのと同じ< <(cmd))、ksh93ではソケットペアにすることができます。

you-script &非対話型シェルでは、stdinは/dev/null

output=$(your-script)or output=`your-scrip`、orでは、cmd <(your-script)stdinは同じままですが、stdoutはパイプになります。

your-script |&(ksh)または(zsh、bash)では、coproc your-scriptstdinとstdoutの両方がパイプです。

スクリプトが次から実行される場合:

ssh host your-script

つまり、sshdonを使用するhostとstdinとstdoutもパイプになります(onを使用するとrsh読み取り+書き込みから直接ネットワークソケットになります)。

cronOR操作によって開始された場合、atstdinはパイプであり、/dev/nullstdoutはパイプである可能性があります(出力がある場合は最終的に電子メールで送信されます)。

など。

スクリプトでこれらすべてを検出するにはlsof

発覚:

  • stdinが開いているかどうか:fcntl(STDIN, F_GETFL, 0)stdinが開いていないと、実行は失敗します。
  • どのモードで開くか(r、w、rw):fcntl()上記の戻り値にO_RDONLY、O_WRONLY、O_RDWRがあることを確認してください。
  • 標準入力(通常、パイプ、デバイス)で開かれたファイルタイプ:fstat()システムコール(stat STDINperl)を実行し、そのフィールドからタイプを取得しますmode。または、Perlの-f/ -d...を使用して、-p可能なすべてのファイルタイプをテストできます。
  • デバイスファイルの場合は、ttyデバイスであるかどうかにかかわらず、POSIX::isatty(STDIN)またはを使用します-t

しかし、これらは質問に答えることとほとんど関係がありません。標準入力で読む方法はありますか?またはread()ブロックに失敗するか、EOFを返します。これには次のものが必要ですpoll()

あなたの最終目標が何であるかはわかりませんが、スクリプトに対話モード(ユーザーが対話する場所)と自動化モードがあり、stdinおよび/またはstdoutに基づいて2つを切り替えたいと思います。

したがって、これはstdinおよび/またはstdoutがttyであること(ttyデバイスを介して対話するユーザーがいるかどうか)を確認し-t STDINて確認する必要があります。-t STDOUT

答え2

バックティックを介してPerlスクリプト内でPerlスクリプトを実行すると(内部スクリプトはSTDINに入力があると誤って考えます)

内部スクリプトは input があると正しく考えます。他の開かれたファイルがファイル記述子0(perlでは常にファイルハンドルである)をSTDIN取得します。STDINご存知のように、stdinファイル記述子は他のサブプロセスと同様に、Perlqx{...}またはPerlで実行されるプログラムを介して外部スクリプトから継承されます。`...`

内部スクリプトはソースを継承するためファイル記述子0、Perl STDINではないファイルハンドルこれは、内部スクリプトまたは外部スクリプトが他のスクリプトに対して何も残らないまで、必要な入力をさらに読み取ることができるため、バッファリングの問題を引き起こします。次の例を考えてみましょう。

$ echo text | perl -e '$junk=`perl -e "eof(STDIN)"`; print while <>'
$ # nothing!

「EOFテスト」だけでは、内部スクリプトは外部スクリプトへの入力を残しません。

ただし、スクリプト内でバッファリングされていない読み取りを実行すると、sysread期待どおりに機能します。

$ cat inner.pl
sysread STDIN, $d, 2
$ echo text | perl -e '$junk = `perl inner.pl`; print while <>'
xt

[他の回答から]
以下を使用すると、your-script <&-stdinが閉じられます。

stdinのようなファイル記述子を閉じることは決して良い考えではありません。 (デーモンはファイル記述子をからリダイレクトしますが、/dev/null決して閉鎖)しかし、PerlやPythonのような言語で書かれたスクリプトを実行すると特に悪いです。これはstdinが終了する可能性があるためです。開いている(そしてスクリプトを参照)の代わりに閉鎖:

$ cat script.pl
seek STDIN, 0, 0;
print while <STDIN>;
$ perl script.pl <&-
seek STDIN, 0, 0;
print while <STDIN>;

open(2)これは、最初の無料のファイル記述子を返すか、同じシステムコールが発生するために発生しますsocket(2)。 stdinが閉じられると、返されたfdはstdinになります。

答え3

これまでの答えは質問に対する答えですが、これまでの子Perlプロセスがファイル記述子0(STDIN)で読み取るためにAppendToPathというファイルを開く時期と理由に関する具体的な質問は直接解決されていません。以下の@zevzekのコメントは何が起こっているのか教えてくれます。どのように機能し、STDINが標準入力以外のファイルハンドルになるかを説明するメカニズムを提供するので、選択した回答を維持します。しかし、AppendToPathできるだけファイルのコンテキストに入れます。関連する再現可能な(macOSでシステムPerlを使用する)例があります。

ファイル記述子がどのように「生成」されたかを知る方法はありませんが、システムはその記録を保持しません。デバッグする場合は、strace -f ./your_script のようにプログラムを追跡するのが便利です。

上記の引用によると、親プロセスまたは子プロセスによって開かれたかどうかはわかりませんが、AppendToPathこれが最初に必要なAppendToPathperlを更新するために使用されるファイルであることを考慮する@INCと、子プロセスのperlによって開かれた可能性が高くなります。インタプリタの準備中に提供されたスクリプトを実行します。

以下は、親がSTDINを閉じて子のSTDIN(fd 0)があることを証明するおもちゃの例ですAppendToPath

bash-3.2$ perl -e 'close(STDIN); \
                   $c=q{perl -e } . \
                      chr(39) . \
                      print(fileno(STDIN),"\n"); \
                      q{print qx{lsof -w -b -p $$}} . \
                      chr(39); \
                   print `$c`'
0
COMMAND   PID     USER   FD   TYPE             DEVICE SIZE/OFF                NODE NAME
perl5.18 8901 robleach  cwd    DIR                1,8      832            35897246 /Users/robleach/GoogleDrive/WORK/RPST
perl5.18 8901 robleach  txt    REG                1,8    37552 1152921500311880916 /usr/bin/perl5.18
perl5.18 8901 robleach  txt    REG                1,8  1305808 1152921500312070866 /System/Library/Perl/5.18/darwin-thread-multi-2level/CORE/libperl.dylib
perl5.18 8901 robleach  txt    REG                1,8  1568368 1152921500312405021 /usr/lib/dyld
perl5.18 8901 robleach    0r   REG                1,8       33            90514450 /Library/Perl/5.18/AppendToPath
perl5.18 8901 robleach    1   PIPE 0xd289f4ba11f1bbb8    16384                     ->0x4d76dba4a1ac82fd
perl5.18 8901 robleach    2u   CHR               16,0  0t13390                 723 /dev/ttys000
perl5.18 8901 robleach    3   PIPE 0x9f2f7b3ec7eb66ba    16384                     ->0xc303b3e01efc707c

この例はSTDINを閉じず、fd 0がtty(つまりSTDIN)であることを示しています。

bash-3.2$ perl -e '$c=q{perl -e } . \
                      chr(39) . \
                      print(fileno(STDIN),"\n"); \
                      q{print qx{lsof -w -b -p $$}} . \
                      chr(39); \
                   print `$c`'
0
COMMAND   PID     USER   FD   TYPE             DEVICE SIZE/OFF                NODE NAME
perl5.18 8904 robleach  cwd    DIR                1,8      832            35897246 /Users/robleach/GoogleDrive/WORK/RPST
perl5.18 8904 robleach  txt    REG                1,8    37552 1152921500311880916 /usr/bin/perl5.18
perl5.18 8904 robleach  txt    REG                1,8  1305808 1152921500312070866 /System/Library/Perl/5.18/darwin-thread-multi-2level/CORE/libperl.dylib
perl5.18 8904 robleach  txt    REG                1,8  1568368 1152921500312405021 /usr/lib/dyld
perl5.18 8904 robleach    0u   CHR               16,0  0t14630                 723 /dev/ttys000
perl5.18 8904 robleach    1   PIPE 0xd289f4ba11f1bbb8    16384                     ->0x4d76dba4a1ac82fd
perl5.18 8904 robleach    2u   CHR               16,0  0t14630                 723 /dev/ttys000
perl5.18 8904 robleach    3   PIPE 0x9f2f7b3ec7eb66ba    16384                     ->0xc303b3e01efc707c

STDIN がオフにならないと、ファイルは/Library/Perl/5.18/AppendToPathlsof 出力に含まれません。fileno(STDIN)子からクエリするときにSTDINファイル記述子が定義されることにも注意する価値があります。

以下は@zenzek(引用符の編集)を私が直接解釈したものです。

open(2) マンページ引用: "成功した呼び出しによって返されたファイル記述子は、現在プロセスに対して開かれていない最も低い番号のファイル記述子になります。" STDIN(fd 0)を閉じた後、次のオープン呼び出しはそのファイル記述子(0)を取得し、子プロセス-t STDINの内容は新しく開かれたファイルになります。例は次のとおりです。

perl -le 'close(STDIN); \
          open PW, "/etc/passwd"; \
          print fileno(PW); \
          print `perl -e "print fileno(STDIN),qq(\n)";lsof  -w -b -p $$`'
0
0
COMMAND    PID     USER   FD   TYPE             DEVICE SIZE/OFF                NODE NAME
perl5.18 13320 robleach  cwd    DIR                1,8      832            35897246 /Users/robleach/GoogleDrive/WORK/RPST
perl5.18 13320 robleach  txt    REG                1,8    37552 1152921500311880916 /usr/bin/perl5.18
perl5.18 13320 robleach  txt    REG                1,8  1305808 1152921500312070866 /System/Library/Perl/5.18/darwin-thread-multi-2level/CORE/libperl.dylib
perl5.18 13320 robleach  txt    REG                1,8  1568368 1152921500312405021 /usr/lib/dyld
perl5.18 13320 robleach    0r   REG                1,8     6946            90523670 /private/etc/passwd
perl5.18 13320 robleach    1u   CHR               16,0  0t23169                 723 /dev/ttys000
perl5.18 13320 robleach    2u   CHR               16,0  0t23169                 723 /dev/ttys000
perl5.18 13320 robleach    3   PIPE 0x5898fd9b21b123d7    16384                     ->0xc4c5f2aecc8eaaf7

より関連する引用:

ファイル記述子0は標準入力です。これはstdioのstdinストリームオブジェクトでもPerlのSTDINファイルハンドルオブジェクトでもないかもしれません(どちらも実際のファイルやファイル記述子をまったく参照しないかもしれない高レベルラッパーです)。ただし、これは常により高いレベルのラッパーではなく、execを継承するファイル記述子です。これは、逆引用符でlsofや他のプログラムを実行したときに起こります(標準fdがあってはならないcloexecとしてfdに表示されていない場合)。すべてのファイル記述子(0を含む)は開いたときは常に継承されますが、閉じるときは継承されません。 fd 0が閉じられた場合、fdを返すすべての関数(open()、accept()、ソケット()、epoll_create()など)は、成功すると0を返します。なぜなら、ゼロは現在使用されていない最も低い数値のfdであるからです。

STDINをオフにすると、指定されたfd 0の親で開かれたランダムファイルを再生できますが、fd 0の子で開かれた任意のファイルは再現できません(AppendToPathを開くことがサブスクリプトでも発生するかどうか疑わしいため)。私できる上記のファイルを再生すると、AppendToPathfd 0が与えられます。親が開いているのか、子が開いているのかはっきりわかりません。しかし、私はサブプロセスでPerlインタプリタによって開かれることが合理的な推測だと思います。

関連情報