Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running at from PHP gives no output

Tags:

php

exec

I've been wrestling with exec(), trying to capture the output from it when I add a task using the at Unix system command. My problem is that it is giving no output when run from my script, however running it from the terminal and PHP in interactive mode prints out a couple of lines.

The command I want to execute is this:

echo exec("echo 'php -f /path/to/file.php foo=1' | at now + 1 minutes", $result);

var_dump() gives string(0) "", and print_r() spits out Array (). I've tried using shell_exec(), which outputs NULL, however the following outputs hi when run in a web page context:

echo exec("echo 'hi'");

This also outputs stuff:

echo exec("atq");

However, as soon as I use at, nothing is output. How can I get the output of:

exec("echo 'php -f /path/to/file.php foo=1' | at now + 1 minutes", $result);

Because at present it outputs nothing when run as "normal" by PHP through Apache, however running the command in the terminal as well as in PHP's interactive console gives me the expected result of something like:

php > echo exec("echo 'php -f /path/to/file.php foo=1' | at now + 1 minutes", $result);
warning: commands will be executed using /bin/sh
job 1219 at Sun Jun 10 12:43:00 2012

safe_mode is off, and I cannot work out why I don't get any output from at with a piped-in echo statement, when executing atq or any other commend with exec() gives me output. I've searched and read this question, all to no avail.

How can I get exec() to return the output from at to either a string, or an array if using a second argument with exec()?

like image 558
Bojangles Avatar asked Jun 10 '12 12:06

Bojangles


3 Answers

Working, one line solution

I didn't realise it could be this simple. All that is required is to reroute stderr to stdout by putting 2>&1 at the end of the command to execute. Now any output from at is printed to stdout, therefore captured by exec():

echo exec("echo 'php -f /path/to/file.php foo=1' | at now + 1 minutes 2>&1", $result);

My old solution:

I was trying to keep to a one/two line solution, however the only thing that worked in the end was using proc_open() because at logs to stderr, which exec() doesn't read! I'd like to thank @Tourniquet for pointing this out, however he has deleted his answer. To quote:

As far as i can see, at outputs to stderr, which isn't captured by exec. I'm not really confident in it, but consider using http://php.net/manual/en/function.proc-open.php, which allows you to direct stderr to its own pipe.

This is actually the correct way of doing things. My solution (because I only want stderr) was to do this:

// Open process to run `at` command
$process = proc_open("echo 'php -f /path/to/file.php foo=1' | at now + 1 minutes", array(2 => array("pipe", "w")), $pipes);

// Get stuff from stderr, because `at` prints out there for some odd reason
if(is_resource($process)) {
    $output = stream_get_contents($pipes[2], 100);
    fclose($pipes[2]);

    $return_value = proc_close($process);
}

$output now contains whatever at printed to stderr (which should really go to stdout because it's not an error), and $return_value contains 0 on success.

like image 67
Bojangles Avatar answered Oct 12 '22 23:10

Bojangles


Here a more complex solution with proc_open. I'm writing this answer because, in my case, the '2>&1' workaround doesn't work.

function runCommand($bin, $command = '', $force = true)
{
    $stream = null;
    $bin .= $force ? ' 2>&1' : '';

    $descriptorSpec = array
    (
        0 => array('pipe', 'r'),
        1 => array('pipe', 'w')
    );

    $process = proc_open($bin, $descriptorSpec, $pipes);

    if (is_resource($process))
    {
        fwrite($pipes[0], $command);
        fclose($pipes[0]);

        $stream = stream_get_contents($pipes[1]);
        fclose($pipes[1]);

        proc_close($process);
    }

    return $stream;
}

Usage examples:

// getting the mime type of a supplied file
echo runCommand('file -bi ' . escapeshellarg($file));

Another example using the command parameter:

// executing php code on the fly
echo runCommand('/usr/bin/php', '<?php echo "hello world!"; ?>');

Another example using the force parameter (this can be useful for commands that will change the output during the execution process):

// converting an mp3 to a wav file
echo runCommand('lame --decode ' . escapeshellarg($source) . ' ' . escapeshellarg($dest), '', true);

I hope this helps :-)

like image 43
Francesco Casula Avatar answered Oct 13 '22 00:10

Francesco Casula


Try to create a minimum (non-)working example. Break everything down, and test only one thing at a time.

Here is one error in your bash:

hpek@melda:~/temp$ echo 'php -f /path/to/file.php foo=1' | at now + 1 minutes
at: pluralization is wrong
job 22 at Sun Jun 10 14:48:00 2012
hpek@melda:~/temp$ 

Write minute instead og minutes.

My output from at is send to me by mail!!

like image 42
hpekristiansen Avatar answered Oct 12 '22 23:10

hpekristiansen