Quem corta o primeiro símbolo do arquivo substituído pelo processo e por qual motivo?

4
$ 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

Eu testei em Debian ( php-5.4.14 , bash-4.1.5 ) e Arch Linux ( php-5.4.12 , bash-4.2.42 ).

    
por x-yuri 03.05.2013 / 20:44

2 respostas

5

É PHP, claro. Pipes não comeria o primeiro caractere do arquivo. O PHP está lendo todos os caracteres, mas não está exibindo o primeiro. Até agora você não pode dizer se o problema está na entrada ou na saída: pode ser que o PHP não esteja exibindo o primeiro caractere por algum motivo.

Um pequeno experimento mostra que o problema é de fato com a entrada.

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

O PHP está consumindo o primeiro caractere do script, somente quando o script é dado por um nome de arquivo (não quando não há nenhum argumento de linha de comando e o script é lido a partir da entrada padrão), somente quando o arquivo de script é um pipe ou outro arquivo não pesquisável (não quando o arquivo de script é procurado, por exemplo, quando é um arquivo normal).

O que está acontecendo é que no início, antes que o analisador PHP normal entra em ação, o processador da linha de comando verifica se o script começa com um shebang linha. Se o script começar com os dois caracteres #! , o PHP pula a primeira linha. Desta forma você pode escrever um script PHP como este

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

e esse script será exibido

first line
second line

e nenhum espúrio #!/usr/bin/php no começo.

O detector de shebang funciona assim:

  • Leia o primeiro caractere.
  • Se o primeiro caractere for # , leia outro caractere.
  • Se os dois primeiros caracteres forem #! , continue lendo até o primeiro caractere de nova linha.
  • Se os dois primeiros caracteres não forem #! , retroceda até o início do arquivo.
  • Inicie a análise normal do PHP.

Se o arquivo de script não for pesquisável, a etapa de retrocesso falhará, mas o PHP não tentará detectar isso, então o primeiro caractere do arquivo será perdido (e também o segundo caractere se o primeiro for um # ). Este é um bug no interpretador de linha de comando do PHP.

Você pode ver o código para você mesmo (na função cli_seek_file_begin ).

    
por 04.05.2013 / 02:56
-1

Eu posso reproduzir o que você está vendo no 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

É algo com php . Eu não consegui reproduzi-lo com cat (o que mostraria que o código fifo no kernel está seriamente quebrado).

Um strace log sobre php mostra que leu os dados do canal: dois dígitos e uma nova linha:

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

A próxima coisa que tenta do descritor de arquivo 3 é um lseek:

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

É aqui que as coisas podem estar erradas. Suponha que isso seja chamado por meio de alguma biblioteca de E / S armazenada em buffer, que fica confusa com pipes e bagunça seu buffer.

Neste ponto, várias outras operações ocorrem.

Mais tarde, ele tenta mais algumas operações, incluindo uma tentativa de obter configurações tty e mais uma leitura, o que indica claramente 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

O PHP não acredita que um retorno zero em uma leitura de bloqueio de um pipe Unix significa EOF e, portanto, ele tenta novamente:

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

As próximas duas linhas no traço são estas. O buffer muito que foi alocado antes dessa leitura é liberado (talvez o buffer de fluxo?) Seguido pela saída interrompida:

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

Então, isso praticamente responde à questão de qual software é responsável. Se você quiser aprofundar mais, um caminho a seguir seria obter uma compilação de depuração de php e entrar nele com gdb .

Adendo:

O comportamento é bem diferente quando php lê a entrada padrão, como no caso echo 12 | php . Por exemplo, a operação llseek nunca é tentada no descritor de arquivo 0. Além disso, o fluxo nunca é fechado (é claro) e não há operações intermediárias entre a leitura e a gravação. O seguinte aparece como um bloco contíguo:

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

Seria de se esperar que a abertura de um arquivo chamado na linha de comando passasse por um fluxo diferente comparado à leitura da entrada padrão (provavelmente representada por algum objeto de fluxo de entrada padrão global criado especialmente).

    
por 04.05.2013 / 01:14

Tags