1

I'm piping lines into a PHP script (see contrived example below). Alas the pipe unintentionally flows into the shell command in the script, thus nano doesn't run because it chokes on STDIN.

I want the shell command to run completely unrelated to the STDIN piped to the main script. So the PHP script should in a way "eat" the STDIN, so it doesn't reach the sub-shell. How do I fix this?

Note that exec(), system() and passthru() all give the same result.

$ echo -e "World\nEverybody" | php script.php
Hello World
Received SIGHUP or SIGTERM
Hello Everybody
Received SIGHUP or SIGTERM

script.php:

<?php

foreach(file("php://stdin") as $name) {
  echo "Hello $name";
  passthru("nano");
}

?>

Environment:

  • PHP 7.1.14 / PHP 5.6.30
  • GNU bash, version 3.2.57
  • Why did you type the characters p,a,s,s,t,h,r and u in your script? – Gerard H. Pille Mar 19 '18 at 14:04
  • It's a PHP way of doing sub-shells. None of the supported methods work as expected. – forthrin Mar 19 '18 at 14:39
  • @forthrin did you see that: stackoverflow.com/q/11968244/1195001 They are not using the same code as you. I think your problem is more about php than pipe – Kiwy Mar 19 '18 at 14:42
  • @forthrin True, but if you read the passthru manual page, what does it say about the output? – Gerard H. Pille Mar 19 '18 at 14:46
  • Well, yes processes inherit the file descriptors of their parent. What would you want its stdin to be? – Stéphane Chazelas Mar 19 '18 at 15:01
1

Yes, processes inherit the file descriptors of their parent, when you do:

In

php -r 'passthru("nano");'

php will inherit the stdin of the shell (a tty device if invoked at the prompt of an interactive shell) and nano will inherit it as well (while stdout is a pipe used by php to retrieve the output of nano and pass it through, nano seems to be happy with it, not all editors would, you may want to use system() instead here).

In:

something | php -r 'passthru("nano");'

You're calling php with it stdin now a pipe with something's stdout at the other end. And nano will inherit it.

If you want php's stdin to be the pipe, while nano stdin whatever the shell's stdin is, you'd need to somehow pass that resource to php, and have php (or the shell run by passthru) make it the stdin of nano. It could be done with for instance:

{ something 3<&- | php -r 'passthru("nano <&3 3<&-");'; } 3<&0

Where we make the resource on fd 0 (stdin) also available on fd 3 within a command group ({...;}), close it for something, which doesn't need it (3<&-), and tell the shell run by php's passthru to restore stdin from that fd 3.

Example:

$ php -r 'passthru("ls -l /proc/self/fd");'
total 0
lrwx------ 1 stephane stephane 64 Mar 19 15:12 0 -> /dev/pts/38
l-wx------ 1 stephane stephane 64 Mar 19 15:12 1 -> pipe:[22538485]
lrwx------ 1 stephane stephane 64 Mar 19 15:12 2 -> /dev/pts/38

fd 0 is a tty device for terminal interaction.

$ echo hello | php -r 'passthru("ls -l /proc/self/fd");'
total 0
lr-x------ 1 stephane stephane 64 Mar 19 15:12 0 -> pipe:[22539326]
l-wx------ 1 stephane stephane 64 Mar 19 15:12 1 -> pipe:[22530020]
lrwx------ 1 stephane stephane 64 Mar 19 15:12 2 -> /dev/pts/38

Now ls's stdin is a pipe (the one echo is feeding).

$ { echo hello 3<&- | php -r 'passthru("ls -l /proc/\$PPID/fd /proc/self/fd <&3 3<&-");';} 3<&0
/proc/9202/fd:
total 0
lr-x------ 1 stephane stephane 64 Mar 19 15:17 0 -> pipe:[22544619]
lrwx------ 1 stephane stephane 64 Mar 19 15:17 1 -> /dev/pts/38
lrwx------ 1 stephane stephane 64 Mar 19 15:17 2 -> /dev/pts/38
lrwx------ 1 stephane stephane 64 Mar 19 15:17 3 -> /dev/pts/38
lr-x------ 1 stephane stephane 64 Mar 19 15:17 4 -> pipe:[22544623]

/proc/self/fd:
total 0
lrwx------ 1 stephane stephane 64 Mar 19 15:17 0 -> /dev/pts/38
l-wx------ 1 stephane stephane 64 Mar 19 15:17 1 -> pipe:[22544623]
lrwx------ 1 stephane stephane 64 Mar 19 15:17 2 -> /dev/pts/38

ls's stdin has been made the tty device again, while its parent (php) still has the pipe on stdin (see also the tty on fd 3 and another pipe on fd 4, probably the one it is reading the output of ls with).

So here, you need to change your php script to:

<?php
foreach(file("php://stdin") as $name) {
  echo "Hello $name";
  passthru("nano <&3 3<&-");
}
?>

And call it as:

{ printf '%s\n' World Everybody | php script.php; } 3<&0

To pass both resources (the pipe from printf and the original stdin) to php.

If you expect that php script to always be called from within a terminal and that nano should then always interact with the terminal (but then again, note that php makes its stdout not the terminal), you could change it instead to:

<?php
foreach(file("php://stdin") as $name) {
  echo "Hello $name";
  passthru("nano < /dev/tty");
}
?>

Where we hard-code nano's stdin to be the controlling terminal.

  • echo -e "World\nEverybody" 3<&- | php script.php and passthru("nano <&3 3<&-"); gives sh: 3: Bad file descriptor. Maybe I'm getting you wrong. Could you provide a changed version of my script and command line that works? – forthrin Mar 19 '18 at 15:22
  • @forthrin, you need to open that fd 3 to something (the original stdin) first with the { ...; } 3<&0. See edit. – Stéphane Chazelas Mar 19 '18 at 15:38
  • nano < /dev/tty works perfectly! I'll try to read up on the low level stuff on pipes. Perfect excuse to dig deeper into the magic realms of UNIX. Great explanation! – forthrin Mar 19 '18 at 15:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.