$ 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
我在Debian
( php-5.4.14
, bash-4.1.5
) 和Arch Linux
( php-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 才会吃掉脚本的第一个字符不可查找的文件(当脚本文件可查找时,例如,当它是常规文件时)。
发生的事情是,在一开始,在正常的 PHP 解析器启动之前,命令行处理器会检查脚本是否以舍邦线。如果脚本以两个字符 开头#!
,PHP 会跳过第一行。这样你就可以写一个像这样的PHP脚本
#!/usr/bin/php
first line
<?php echo "second line"?>
该脚本将输出
first line
second line
#!/usr/bin/php
并且一开始就没有虚假。
shebang 探测器的工作原理如下:
- 读出第一个字符。
- 如果第一个字符是
#
,则读取另一个字符。 - 如果前两个字符是
#!
,则继续阅读直到第一个换行符。 - 如果前两个字符不是
#!
,则倒回到文件的开头。 - 开始正常的PHP解析。
如果脚本文件是不可查找的,则倒带步骤会失败,但 PHP 不会尝试检测到这一点,因此文件的第一个字符会丢失(如果第一个字符是 a,则还会丢失第二个字符#
)。这是 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
.我无法重现它cat
(这表明内核中的 fifo 代码已严重损坏)。
日志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
它尝试的下一个文件描述符3
是 lseek:
_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)
跟踪中接下来的两行是这些。在读取之前分配的缓冲区被释放(可能是流缓冲区?),然后是切碎的输出:
munmap(0xb6fdc000, 65536) = 0
write(1, "2\n", 2) = 2
这样就基本回答了哪个软件负责的问题。如果您想深入研究它,一种前进的方法是获得 的调试版本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
人们会期望打开在命令行上命名的文件与从标准输入(可能由某些专门设置的全局标准输入流对象表示)读取相比会经历不同的流程。