エッジケース - 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

おすすめ記事