Qui coupe le premier symbole du file substitut du process et pour quelle raison?

$ 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 

Je l'ai testé sur Debian ( php-5.4.14 , bash-4.1.5 ) et Arch Linux ( php-5.4.12 , bash-4.2.42 ).

C'est PHP, bien sûr. Les pipes ne mangent pas le premier caractère du file. PHP lit tous les caractères, mais il ne sort pas le premier. Jusqu'à présent, vous ne pouvez pas savoir si le problème est dans l'input ou dans la sortie: il se peut que PHP n'émette pas le premier caractère pour une raison quelconque.

Une petite expérience montre que le problème est en effet avec l'input.

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

PHP est en train de manger le premier caractère du script, seulement quand le script est donné par un nom de file (pas lorsqu'il n'y a pas d'argument de command line et que le script est lu depuis l'input standard), seulement quand le script est un pipe ou autre file non-cherchable (pas quand le file de script est recherché, par exemple quand c'est un file normal).

Ce qui se passe est que tout au début, avant que l'parsingur PHP normal n'intervienne, le processeur de command line vérifie si le script commence par une ligne shebang . Si le script commence avec les deux caractères #! , PHP saute la première ligne. De cette façon, vous pouvez écrire un script PHP comme celui-ci

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

et ce script va sortir

 first line second line 

et pas de faux #!/usr/bin/php au début.

Le détecteur de shebang fonctionne de cette façon:

  • Lisez le premier caractère.
  • Si le premier caractère est # , lisez un autre caractère.
  • Si les deux premiers caractères sont #! , continuez à lire jusqu'au premier caractère de nouvelle ligne.
  • Si les deux premiers caractères ne sont pas #! , rembobinez le début du file.
  • Démarrer l'parsing PHP normale.

Si le file de script n'est pas consultable, l'étape de rebobinage échoue, mais PHP n'essaie pas de le détecter. Le premier caractère du file est donc perdu (et le deuxième si le premier est un # ). Ceci est un bug dans l'interpréteur de command line PHP.

Vous pouvez voir le code vous-même (dans la fonction cli_seek_file_begin ).

Je peux reproduire ce que vous voyez sur 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 

C'est quelque chose avec php . Je ne pouvais pas le reproduire avec un cat (ce qui montrerait que le code fifo du kernel est sérieusement cassé).

Un journal strace sur php montre qu'il a lu datatables du pipe: deux numbers et une nouvelle ligne:

 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 

La prochaine chose qu'il essaye de descripteur de file 3 est un lseek:

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

C'est là que les choses vont peut-être mal. Supposons que cela soit appelé par le biais d'une bibliothèque d'E / S en memory tampon qui devient confuse par les tuyaux et gâcher son tampon.

À ce stade, de nombreuses autres opérations ont lieu.

Plus tard, il essaie quelques opérations supplémentaires, y compris une tentative d'get des parameters tty et une lecture de plus qui indique clairement 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 ne croit pas qu'un return nul sur une lecture bloquante d'un canal Unix signifie EOF et donc il essaie à nouveau:

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

Les deux lignes suivantes de la trace sont celles-ci. Le tampon même qui a été alloué avant cette lecture est libéré (peut-être le tampon de stream?) Suivi par la sortie hachée:

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

Cela répond à peu près à la question de savoir quel logiciel est responsable. Si vous voulez y creuser plus, une solution serait d'get une version debug de php et d'y entrer avec gdb .


Addenda:

Le comportement est assez différent quand php lit à partir de l'input standard, comme dans l' echo 12 | php echo 12 | php affaire. Par exemple, l'opération llseek n'est jamais essayée sur le descripteur de file 0. De plus, le stream n'est jamais fermé (bien sûr) et il n'y a pas d'opérations intermédiaires entre la lecture et l'écriture. Ce qui suit apparaît comme un bloc contigu:

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

On peut s'attendre à ce que l'ouverture d'un file nommé sur la command line passe par un stream différent par rapport à la lecture d'une input standard (probablement représentée par un object de stream d'input standard global).