Get exit status of piped commands in PHP - php

I've got a PHP script that calls the system shell with a piped command. In this case we're talking about a backup script (but it could be anything, I'm asking specifically about the exit status!):
exec(
"mysqldump --user=$u --password=$p --host=$h --port=$p $db | gzip -9 > backup.sql.gz",
$out,
$status
);
Now I want to know if the mysqldump command yielded an error, but the $status variable always seems to contain 0, even if I force an error. It appears to be the exit code of the second command (gzip in this case). I want to be able to see the exit status of the first command in PHP.

You'll need a little help from the Bash internal array PIPESTATUS. This holds the exit status of each command in the pipe. Since you're looking for the first command's exit status you would be addressing PIPESTATUS[0]. So you're code would look like:
exec(
"bash -c 'mysqldump --user=$u --password=$p --host=$h --port=$p $db | gzip -9 > backup.sql.gz; exit \${PIPESTATUS[0]}'",
$out,
$status
);
Note, this changes the overall exit status of the exec() call and you'll need additional code if you want to catch a failure in a longer chain of commands.

I've managed to think of a more generic solution that makes the exit status of each command in the pipe available to PHP. Of course it requires a shell with $PIPESTATUS (this excludes plain sh).
// The command with pipes.
$command = 'command1 | command2 | echo Test | gzip -9 -f';
// Execute the command. The overall exit code is in $exitStatus.
exec(
$command . '; echo -e "\n"${PIPESTATUS[*]}',
$out,
$exitStatus
);
// Get the exit statuses and remove them from the output.
$pipeStatus = explode(' ', array_pop($out));
print_r([$pipeStatus, $out]);
// [
// [
// "127",
// "127",
// "0",
// "0",
// ],
// [
// b"\x1F‹\x089fÙW\x02I-.á\x02Â\x1Axú\x05",
// ],
// ]
Slightly simpler variant if you're sure the piped commands will end with a newline (notice the echo part in the command is different):
// The command with pipes.
$command = 'command1 | command2 | echo Testing things | sed s/things/stuff/';
// Execute the command. The overall exit code is in $exitStatus.
exec(
$command . '; echo ${PIPESTATUS[*]}',
$out,
$exitStatus
);
// Get the exit statuses and remove them from the output.
$pipeStatus = explode(' ', array_pop($out));
print_r([$pipeStatus, $out]);
// [
// [
// "127",
// "127",
// "0",
// "0",
// ],
// [
// "Testing stuff",
// ],
// ]

Related

Passing variables with additional parameters from PHP to Python

I need to pass a multi-word argument from PHP to Python.
Passing only a single word poses no problem, however, how do I add the additional parameter that will result in a successful solution?
I have tried several ways to pass the additional parameter without any success, it result in either a 500 Internal Server Error or no response from the python script. Using 2>&1 also do not return any error messages.
There are six instances where additional parameter are optional or required for a successful response.
I can send the single word argument:
$command = escapeshellcmd("python /home/nova_api.py 'getwithdrawalhistory' 2>&1");
This will return the proper response of:
{"items":[],"message":"Your trade history with recent first","page":1,"pages":0,"perpage":100,"status":"success","total_items":0}
My attempts to pass the additional parameter from PHP to Python:
$command = escapeshellcmd("python /home/nova_api.py 'getwithdrawalhistory, { \'page\': 1 }' 2>&1");
$command = escapeshellcmd("python /home/nova_api.py 'getwithdrawalhistory, { \'page\': \'1\' }' 2>&1");
$command = escapeshellcmd("python /home/nova_api.py 'getwithdrawalhistory, { \'page\': \"1\" }' 2>&1");
All three result in a blank page - no response and no error returned.
You're doing the right thing by escaping, but using escapeshellcmd isn't what you want. It has no concept of arguments, so isn't working the way you expect. PHP provides a method to quote and escape individual arguments to shell commands:
<?php
$cmd = "python";
$args = [
"/home/nova_api.py",
"getwithdrawalhistory, { 'page': 1 }",
];
// just a fancy way of avoiding a foreach loop
$escaped_args = implode(" ", array_map("escapeshellarg", $args));
$command = "$cmd $escaped_args 2>&1";

PHP running multiple scripts concurrently

I have an array with object server like this:
Array
(
[0](
(
[id] => 1
[version] => 1
[server_addr] => 192.168.5.210
[server_name] => server1
)
)
[1](
(
[id] => 2
[server_addr] => 192.168.5.211
[server_name] => server2
)
)
)
By running the code below, I'm able to get the desired output
foreach ($model as $server) {
$cpu_usage = shell_exec('sudo path/to/total_cpu_usage.sh '.$server->server_addr);
$memory_usage = shell_exec('sudo path/to/total_memory_usage.sh '.$server->server_addr);
$disk_space = shell_exec('sudo path/to/disk_space.sh '.$server->server_addr);
$inode_space = shell_exec('sudo path/to/inode_space.sh '.$server->server_addr);
$network = shell_exec('sudo path/to/network.sh '.$server->server_addr);
exec('sudo path/to/process.sh '.$server->server_addr, $processString);
$processArray = array();
foreach ($processString as $i) {
$row = explode(" ", preg_replace('/\s+/', ' ', $i));
array_push($processArray,$row);
}
$datetime = shell_exec('sudo path/to/datetime.sh '.$server->server_addr);
echo $cpu_usage;
echo $mem_usage;
echo $disk_space;
......
}
My scripts are similar like:
#!/bin/bash
if [ "$1" == "" ]
then
echo "To start monitor, please provide the server ip:"
read IP
else
IP=$1
fi
ssh root#$IP "date"
But the whole process took like 10 sec for 5 servers compared to 1 server for less than 2 sec. Why is that? Is there anyway to lessen the time? My guess is that the exec command was waiting for the output to be assign to the variable before going to next loop? I tried to google a little bit but most of the answer are for without returning any output at all... I need the output though
You can run your scripts simultaneously with popen() and grab the output later with fread().
//execute
foreach ($model as $server) {
$server->handles = [
popen('sudo path/to/total_cpu_usage.sh '.$server->server_addr, 'r'),
popen('sudo path/to/total_memory_usage.sh '.$server->server_addr, 'r'),
popen('sudo path/to/disk_space.sh '.$server->server_addr, 'r'),
popen('sudo path/to/inode_space.sh '.$server->server_addr, 'r'),
popen('sudo path/to/network.sh '.$server->server_addr, 'r'),
];
}
//grab and store the output, then close the handles
foreach ($model as $server) {
$server->cpu_usage = fread($server->handles[0], 4096);
$server->mem_usage = fread($server->handles[1], 4096);
$server->disk_space = fread($server->handles[2], 4096);
$server->inode_space = fread($server->handles[3], 4096);
$server->network = fread($server->handles[4], 4096);
foreach($server->handles as $h) pclose($h);
}
//print everything
print_r($model);
I tested a similar code to execute 5 scripts that sleep for 2 seconds and the whole thing took only 2.12 seconds
instead of 10.49 seconds with shell_exec().
Update 1: Big thanks to Markus AO for pointing out an optimization potential.
Update 2: Modified the code to remove the possibility of overwrite.
The results are now inside $model.
This can also show which server refused the connection, in case that issue about sshd is affecting you.
All you need to do is add an > /dev/null & at the end on Linux, you wont get the output though, but it will run as a background ( async ) process.
shell_exec('sudo path/to/datetime.sh '.$server->server_addr.' > /dev/null &');
see also this Background process script from my GitHub, ( it has windows compatible background processes )
https://github.com/ArtisticPhoenix/MISC/blob/master/BgProcess.php
Cheers!
I don't know how to make your logic faster but I can tell you how I use to track time of running when I have scripts. At the begin of the script put some var $start = date('c'); and at the end just simple echo ' start='.$start; echo ' end='.date(c);
Yes you're correct: your PHP script is waiting for each response before moving onward.
I presume you're hoping to run the requests to all servers simultaneously, instead of waiting for each server to respond. In that case, assuming you're running a thread-safe version of PHP, look into pthreads. One option is to use cURL multi-exec for making asynchronous requests. Then there's also pcntl_fork that may help you out. Also see this & this thread for possible thread/async approaches.
Aside that, do test and benchmark the shell scripts individually to see where the bottlenecks are, and whether you can speed them up. That may be easier than thread/async setups in PHP. If you have issues with network latency, then write an aggregator shell script that executes the other scripts and returns the results in one request, and only call that in your PHP script.

Taking postgresql backup in php using pg_dumb

I am trying to take the backup of postgresql database using php. If I try it in the command prompt using the following then it is working. But if I try it using exec() then an empty file is generated.Also the php script gets executed infinitely.
pg_dump -U postgres test > D:/backup.sql
The following code works.
putenv("PGPASSWORD=postgres");
$dumpcmd = array("pg_dump", "-i", "-U", escapeshellarg("postgres"), "-F", "c", "-b", "-v", "-f", escapeshellarg("D:/backup4.sql"), escapeshellarg("test"));
exec( join(' ', $dumpcmd), $cmdout, $cmdresult );
putenv("PGPASSWORD");
if ($cmdresult != 0)
{
# Handle error here...
echo "error";
}

PHP - How to check if process is running with multiple parameters

I know how to check if one instance of process is running but how do I check a particular process running with different parameters for example
/usr/local/bin/foo --config /home/config1.txt
/usr/local/bin/foo --config /home/config2.txt
Following code checks only process name, how do I check if a process is running with a particular parameter?
function is_process_running ($process_name) {
$result = array();
exec("/sbin/pidof {$process_name}", $result);
if(is_array($result) && isset($result[0]) && $result[0] >= 1) {
return true;
}
return false;
}
is_process_running('/usr/local/bin/foo --config /home/config1.txt') returns true
is_process_running('/usr/local/bin/foo --config /home/config3.txt') returns false
function is_process_running ($process_name) {
$result = array();
exec("ps -Af | grep {$process_name}", $result);
// A loop that checks for your result and also checks
// that the result isn't the grep command called
// ps -ax | grep firefox asdfasd
// returns grep --color=auto firefox asdfasd
return false;
}
Give it a try. The flag 'f' modifies the output so includes the full call.
Try this bash command to get more details about the processes:
ps ax | grep YourProcesName
I know that at least java processes would should its Paramerers

mysqldump via PHP

I have a PHP script that gets passed the MySQL connection details of a remote server and I want it to execute a mysqldump command. To do this I'm using the php exec() function:
<?php
exec("/usr/bin/mysqldump -u mysql-user -h 123.145.167.189 -pmysql-pass database_name > /path-to-export/file.sql", $output);
?>
When the right login details are passed to it, it'll work absolutely fine.
However, I'm having trouble checking if it executes as expected and if it doesn't finding out why not.
The $output array returns as empty, whereas if I run the command directly on the command line a message is printed out telling me the login failed. I want to capture such error messages and display them. Any ideas on how to do that?
You should check the third parameter of exec function: &$return_var.
$return_var = NULL;
$output = NULL;
$command = "/usr/bin/mysqldump -u mysql-user -h 123.145.167.189 -pmysql-pass database_name > /path-to-export/file.sql";
exec($command, $output, $return_var);
By convention in Unix a process returns anything other than 0 when something goes wrong.
And so you can:
if($return_var) { /* there was an error code: $return_var, see the $output */ }
The solution I found is to run the command in a sub-shell and then output the stderr to stdout (2>&1). This way, the $output variable is populated with the error message (if any).
i.e. :
exec("(mysqldump -uroot -p123456 my_database table_name > /path/to/dump.sql) 2>&1", $output, $exit_status);
var_dump($exit_status); // (int) The exit status of the command (0 for success, > 0 for errors)
echo "<br />";
var_dump($output); // (array) If exit status != 0 this will handle the error message.
Results :
int(6)
array(1) { [0]=> string(46) "mysqldump: Couldn't find table: "table_name"" }
Hope it helps !
Because this line redirect the stdout output > /path-to-export/file.sql
try this,
<?php
exec("/usr/bin/mysqldump -u mysql-user -h 123.145.167.189 -pmysql-pass database_name", $output);
/* $output will have sql backup, then save file with these codes */
$h=fopen("/path-to-export/file.sql", "w+");
fputs($h, $output);
fclose($h);
?>
I was looking for the exact same solution, and I remembered I'd already solved this a couple of years ago, but forgotten about it.
As this page is high in Google for the question, here's how I did it:
<?php
define("BACKUP_PATH", "/full/path/to/backup/folder/with/trailing/slash/");
$server_name = "your.server.here";
$username = "your_username";
$password = "your_password";
$database_name = "your_database_name";
$date_string = date("Ymd");
$cmd = "mysqldump --hex-blob --routines --skip-lock-tables --log-error=mysqldump_error.log -h {$server_name} -u {$username} -p{$password} {$database_name} > " . BACKUP_PATH . "{$date_string}_{$database_name}.sql";
$arr_out = array();
unset($return);
exec($cmd, $arr_out, $return);
if($return !== 0) {
echo "mysqldump for {$server_name} : {$database_name} failed with a return code of {$return}\n\n";
echo "Error message was:\n";
$file = escapeshellarg("mysqldump_error.log");
$message = `tail -n 1 $file`;
echo "- $message\n\n";
}
?>
It's the --log-error=[/path/to/error/log/file] part of mysqldump that I always forget about!
As exec() is fetching just stdout which is redirected to the file, we have partial or missing result in the file and we don't know why. We have to get message from stderr and exec() can't do that. There are several solutions, all has been already found so this is just a summary.
Solution from Jon: log errors from mysqldump and handle them separately (can't apply for every command).
Redirect outputs to separate files, i.e. mysqldump ... 2> error.log 1> dump.sql and read the error log separately as in previous solution.
Solution from JazZ: write the dump as a subshell and redirect stderr of the subshell to stdout which can php exec() put in the $output variable.
Solution from Pascal: better be using proc_open() instead of exec() because we can get stdout and stderr separately (directly from pipes).
write below code to get the database export in .sql file.
<?php exec('mysqldump --user=name_user --password=password_enter --host=localhost database_name > filenameofsql.sql'); ?>

Categories