1

Suppose I have the following PHP script being run on the PHP CLI in a shell script:

<?php
# foo.php

$a = rand(1,5);

if ($a == 1) {
    fwrite(STDOUT, 'normal condition');
} else {
    fwrite(STDERR, 'error condition');
}

The script is run in the shell like so:

php foo.php

How do I handle the output from the PHP script in the shell based on whether the output is to STDERR or STDOUT? For instance, if the output from the PHP script is STDOUT, write it to a CSV file, but if the output is to STDERR, write the output to an error log file.

nhuff717
  • 369
  • 2
  • 3
  • 17
  • You may need to clarify 'handle'. Is it ok to create both files each time? I so, the answer below should work. Can you add post-processing to remove an empty file? Also, how large is the expected output? If it's relatively small, in could be captured in a variable, and then written to a file. – Gairfowl Mar 22 '23 at 04:45
  • Your question is tagged _bash_, _zsh_ and _sh_. We don't know which shell you are going to use. – user1934428 Mar 22 '23 at 09:08
  • @Gairfowl Ideally only one file would be created/updated depending on the source of the output. If post-processing can remove the empty file as part of handling either outcome, I'd like to know that. Output would be relatively small, about a kilobyte. – nhuff717 Mar 22 '23 at 13:37
  • @user1934428 I am using zsh, though I can also use bash. Ideally this would work in either shell, but I'd also like to know shell-specific differences. – nhuff717 Mar 22 '23 at 13:39

2 Answers2

1

You redirect each stream to separate locations.

php file.php > outfile.csv 2> errorlog.txt

See https://askubuntu.com/questions/625224/how-to-redirect-stderr-to-a-file for more details

DaveyBoy
  • 2,928
  • 2
  • 17
  • 27
  • An empty file would be created based on if the output is to STDERR or STDOUT (at least in zsh). See Gairfowl's reply to my OP. What's the best way to remove that empty file? – nhuff717 Mar 22 '23 at 13:41
  • You could test which file is empty and remove this one. Are you having a [XY Problem](https://xyproblem.info/)? Looks like you're trying something complicated. – lolesque Mar 22 '23 at 13:58
1

Some possibilities that produce a single output file.


Option 1 - redirect stdout and stderr to separate files, then remove empty files:

php file.php > ./data.csv 2> ./error.txt
[[ ! -s ./data.csv ]] && rm ./data.csv
[[ ! -s ./error.txt ]] && rm ./error.txt

The ! -s test will be true if a file is zero-length. This version uses some fairly common shell idioms, and should work in both zsh and bash. The operations can be combined into a function:

phpPost() {
    php ${1:?script required} > ${2:?csv required} 2> ${3:?err required}
    [[ ! -s $2 ]] && rm $2
    [[ ! -s $3 ]] && rm $3
}
phpPost file.php data.csv error.txt

Option 2 - save stdout and stderr in shell variables, and only create the files if there is output:

php file.php \
    >  >(d=$(<&0) && [[ -n $d ]] && <<<$d >data.csv) \
    2> >(e=$(<&0) && [[ -n $e ]] && <<<$e >error.txt)

Some of the pieces:

  • >, 2> - redirect stdout and stderr.
  • >(...) - process substitution. The shell creates a subprocess (sort of - see below) and sends the output that we've redirected to that process.
  • t=$(<&0) - store the stdin of the subprocess in the variable t.
  • [[ -n ... ]] - test if the string is non-empty.
  • <<<... > ... - use a here-string to write the variable contents to a file.

This version comes with some caveats:

  • It is specific to zsh (or more accurately, it did not work in my very brief tests with bash).
  • This is not a common shell idiom.
  • The files are not overwritten each time.
  • Process substitutions can be run asynchronously by the shell, which means in some cases the initial command might finish before the output files are completely written. In this instance, the commands within the process substitution are all zsh built-ins, so the shell runs them synchronously within a single process. Other variations of this pattern might behave differently - the zsh documentation describes a workaround if that is an issue.

This can also be written as a function:

phpVars() {
    rm ${2:?} ${3:?} 2>/dev/null
    php ${1:?} \
        >  >(d=$(<&0) && [[ -n $d ]] && <<<$d >${2}) \
        2> >(e=$(<&0) && [[ -n $e ]] && <<<$e >${3})
}
phpVars file.php data.csv error.txt

Alternative solutions:

  • It may be easier to detect whether the php command succeeded by testing the shell return code instead of looking at the output files.
  • If updating the php code is a possibility, that script could write directly to the data and error files instead of passing the information through the shell.
Gairfowl
  • 2,226
  • 6
  • 9
  • Option 1 worked for my needs, though credit to DaveyBoy's first replace for the starting point. Thank you. – nhuff717 Mar 27 '23 at 15:04