誰がファイルの最初の記号を置き換えるプロセスを中断し、その理由は何ですか?

誰がファイルの最初の記号を置き換えるプロセスを中断し、その理由は何ですか?
$ type 1.sh
#!/bin/bash -eu
php <(echo 12)
$ ./1.sh
2

$ type 2.sh
#!/bin/bash -eu
cat <(echo 12)
$ ./2.sh
12

$ type 3.sh
#!/bin/bash -eu
echo 12 | php
$ ./3.sh
12

$ type 4.sh
#!/bin/bash -eu
rm -f named_pipe
mknod named_pipe p
echo 12 > named_pipe
$ ./4.sh
$ php named_pipe   # from another window
2

Debianphp-5.4.14bash-4.1.5)、Arch Linuxphp-5.4.12、)bash-4.2.42についてテストしました。

答え1

もちろんPHPです。パイプはファイルの最初の文字を食べません。 PHPはすべての文字を読みますが、最初の文字を出力しません。これまで、問題が入力にあるのか出力にあるのかはわかりません。 PHPが何らかの理由で最初の文字を出力しない可能性があります。

いくつかの実験は実際に入力に問題があることを示しました。

$ php <(echo '<?php echo "hello" ?>') 
?php echo "hello" ?>
$ php <(echo ' <?php echo "hello" ?>')
hello$ 

PHPは、スクリプトがファイル名として提供されている場合にのみ(コマンドライン引数がなく、スクリプトがstdinから読み取られない場合)、スクリプトファイルがパイプまたは他のものである場合にのみ、スクリプトの最初の文字を食べます。スクリプトファイルは検索可能です(通常のファイルなど)。

何が起こるかは、最初に通常のPHPパーサーが起動する前に、コマンドラインハンドラがスクリプトが次に起動することを確認することです。シェルボーンワイヤー。スクリプトが2文字で始まる場合、#!PHPは最初の行をスキップします。これにより、次のようなPHPスクリプトを作成できます。

#!/usr/bin/php
first line
<?php echo "second line"?>

スクリプトが出力されます。

first line
second line

#!/usr/bin/phpそしてそもそも偽りはなかった。

Shebang検出器は次のように動作します。

  • 最初の文字を読みます。
  • 最初の文字がある場合は、#他の文字を読みます。
  • 最初の2文字がある場合は、#!最初の改行文字まで読み続けます。
  • 最初の2文字でない場合は、#!ファイルの先頭に巻き戻します。
  • 一般的なPHPの解析を開始します。

スクリプトファイルが見つからないと、巻き戻しステップは失敗しますが、PHPはそれを検出しないため、ファイルの最初の文字が失われます(最初の文字がaの場合、2番目の文字も失われます#)。 。これはPHPコマンドラインソルバーのバグです。

あなたは見ることができますパスワード自分のために(関数からcli_seek_file_begin)。

答え2

Ubuntuで見た内容を再現できます。

#!/bin/bash -eu
rm -rf named_pipe
mkfifo named_pipe
echo 12 >  named_pipe


$ ./namedpipe.sh &  # background it
$ php named_pipe    # same terminal; don't need another window
2

phpこれは.I can't presents it cat(カーネルのfifoコードが深刻に破損していることを示す)に関連しています。

ログは、パイプから2つの数字と改行でデータを読み取ったことをstrace示します。php

open("named_pipe", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFIFO|0664, st_size=0, ...}) = 0
mmap2(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6fdc000
read(3, "12\n", 65536)                  = 3

試す次のファイル記述子は3lseek です。

_llseek(3, 0, 0xbf93c640, SEEK_SET)     = -1 ESPIPE (Illegal seek)

ここで状況が間違っている可能性があります。これがバッファリングされたI / Oライブラリを介して呼び出されると仮定すると、ライブラリはパイプによって混乱し、バッファを混乱させます。

この時点で他の多くのタスクが発生します。

その後、tty設定を取得して再読み込みするなど、いくつかの追加操作を試しました。これは明らかにEOFを表します。

ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, 0xbf93a2e8) = -1 EINVAL (Invalid argument)
fstat64(3, {st_mode=S_IFIFO|0664, st_size=0, ...}) = 0
read(3, "", 65536)                      = 0

PHP は、Unix パイプのブロック読み出しでゼロリターンが EOF を意味しないと思うので、再試行します。

read(3, "", 65536)                      = 0
close(3)              

トレースの次の2行は次のとおりです。読み取り前に割り当てられたバッファが解放され(おそらくストリームバッファ?)、切り捨てられた出力が続きます。

munmap(0xb6fdc000, 65536)               = 0
write(1, "2\n", 2)                      = 2

これは基本的にどのソフトウェアが責任があるかについての質問に答えます。さらに詳しく知りたい場合は、デバッグバージョンを入手する方法が1つphpありますgdb


付録:

phpこの例のように、標準入力から読み取るときの動作はまったく異なりますecho 12 | php。たとえば、llseekファイル記述子 0 では操作は試行されません。さらに、ストリームは決して閉じられず(もちろん)読み取りと書き込みの間に中間操作はありません。以下は連続したブロックで表されます。

read(0, "12\n", 4096)                   = 3
read(0, "", 4096)                       = 0
read(0, "", 4096)                       = 0
write(1, "12\n", 3)                     = 3

コマンドラインで名前付きファイルを開くと、標準入力(おそらく特別に設定されたグローバル標準入力ストリームオブジェクトとして表されます)から読み取るのとは異なるプロセスが行われると予想できます。

関連情報