How can I debug exec() problems?
A few more notes
For debugging always wrap your exec/shell_exec function in
var_dump()
.error_reporting(-1);
should be on, as should bedisplay_errors
, as last resort evenset_error_handler("var_dump");
- if only to see if PHP itself didn't invokeexecvp
or else.Use
2>&1
(merge the shells STDERR to STDOUT stream) to see why an invocation fails.
For some cases you may need to wrap your command in an additional shell invocation:// capture STDERR stream via standard shell echo shell_exec("/bin/sh -c 'ffmpeg -opts 2>&1' ");
Else the log file redirect as advised by @Mike is the most recommendable approach.
Alternate between the various exec functions to uncover error messages otherwise. While they mostly do the same thing, the output return paths vary:
exec()
→ either returns the output as function result, or through the optional$output
paramater.
Also provides a$return_var
parameter, which contains the errno / exit code of the run application or shell. You might get:ENOENT
(2) - No such fileEIO
(127) - IO error: file not found
// run command, conjoined stderr, output + error number var_dump(exec("ffmpeg -h 2>&1", $output, $errno), $output, $errno));
shell_exec()
→ is what you want to run mostly for shell-style expressions.
Be sure to assign/print the return value with e.g.var_dump(shell_exec("..."));
``
inline backticks → are identical toshell_exec
.system()
→ is similar toexec
, but always returns the output as function result (print it out!). Additionally allows to capture the result code.passthru()
→ is anotherexec
alternative, but always sends any STDOUT results to PHPs output buffer. Which oftentimes makes it the most fitting exec wrapper.popen()
or betterproc_open()
→ allow to individually capture STDOUT and STDERR.
Most shell errors wind up in PHPs or Apaches
error.log
when not redirected. Check your syslog or Apache log if nothing yields useful error messages.
Most common issues
As mentioned by @Kuf: for outdated webhosting plans, you could still find
safe_mode
ordisable_functions
enabled. None of the PHP exec functions will work. (Best to find a better provider, else investigate "CGI" - but do not install your own PHP interpreter while unversed.)Likewise can AppArmor / SELinux / Firejail sometimes be in place. Those limit each applications ability to spawn new processes.
The intended binary does not exist. Pretty much no webhost does have tools like
ffmpeg
preinstalled. You can't just run arbitrary shell commands without preparation. Some things need to be installed!// Check if `ffmpeg` is actually there: var_dump(shell_exec("which ffmpeg"));
The
PATH
is off. If you installed custom tools, you will need to ensure they're reachable. Usingvar_dump(shell_exec("ffmpeg -opts"))
will search all common paths - or as Apache has been told/constrained (often just/bin:/usr/bin
).Check with
print_r($_SERVER);
what your PATH contains and if that covers the tool you wanted to run. Else you may need to adapt the server settings (/etc/apache2/envvars), or use full paths:// run with absolute paths to binary var_dump(shell_exec("/bin/sh -c '/usr/local/bin/ffmpeg -opts 2>&1'"));
This is somewhat subverting the shell concept. Personally I don't think this preferrable. It does make sense for security purposes though; moreover for utilizing a custom installation of course.
Permissions
In order to run a binary on BSD/Linux system, it needs to be made "executable". This is what
chmod a+x ffmpeg
does.Furthermode the path to such custom binaries needs to be readable by the Apache user, which your PHP scripts run under.
More contemporary setups use PHPs builtin FPM mode (suexec+FastCGI), where your webhosting account equals what PHP runs with.
Test with SSH. It should go without saying, but before running commands through PHP, testing it in a real shell would be highly sensible. Probe with e.g.
ldd ffmpeg
if all lib dependencies are there, and if it works otherwise.Use
namei -m /Usr/local/bin/ffmpeg
to probe the whole path, if unsure where any access permission issues might arise from.Input values (GET, POST, FILE names, user data) that get passed as command arguments in exec strings need to be escaped with
escapeshellarg()
.$q = "escapeshellarg"; var_dump(shell_exec("echo {$q($_GET['text'])} | wc"));
Otherwise you'll get shell syntax errors easily; and probably exploit code installed later on...
Take care not to combine backticks with any of the
*exec()
functions:$null = shell_exec(`wc file.txt`); ↑ ↑
Backticks would run the command, and leave shell_exec with the output of the already ran command. Use normal quotes for wrapping the command parameter.
Also check in a shell session how the intended program works with a different account:
sudo -u www-data gpg -k
Notably for PHP-FPM setups test with the according user id.
www-data
/apache
are mostly just used by olden mod_php setups.Many cmdline tools depend on some per-user configuration. This test will often reveal what's missing.
You cannot get output for background-run processes started with
… &
ornohup …
. In such cases you definitely need to use a log file redirectexec("cmd > log.txt 2>&1 &");
On Windows
CMD invocations will not play nice with STDERR streams often.
Definitely try a Powershell script to run any CLI apps else, or use a command line like:
system("powershell -Command 'pandoc 2>&1'");
Use full paths, and prefer forward slashes always (
"C:/Program Files/Whatevs/run.exe"
with additional quotes if paths contain spaces).Forward slashes work on Windows too, ever since they were introduced in MS-DOS 2.0
Figure out which service and SAM account IIS/Apache and PHP runs as. Verify it has execute permissions.
You can't run GUI apps usually. (Typical workaround is the taskscheduler or WMI invocations.)
PHP → Python, Perl
If you're invoking another scripting interpreter from PHP, then utilize any available debugging means in case of failures:
passthru("PYTHONDEBUG=2 python -vvv script.py 2>&1");
passthru("perl -w script.pl 2>&1");
passthru("ruby -wT1 script.rb 2>&1");
Or perhaps even run with any syntax -c
check option first.
have a look at /etc/php.ini
, there under:
; This directive allows you to disable certain functions for security reasons.
; It receives a comma-delimited list of function names. This directive is
; *NOT* affected by whether Safe Mode is turned On or Off.
; http://www.php.net/manual/en/ini.sect.safe-mode.php#ini.disable-functions
disable_functions =
make sure that exec is not listed like this:
disable_functions=exec
If so, remove it and restart the apache.
For easy debugging I usually like to execute the php file manually (Can request more errors without setting it in the main ini). to do so add the header:
#!/usr/bin/php
ini_set("display_errors", 1);
ini_set("track_errors", 1);
ini_set("html_errors", 1);
error_reporting(E_ALL);
to the beginning of the file, give it permissions using chmod +x myscript.php
and execute it ./myscript.php
. It's very heedful especially on a busy server that write a lot to the log file.
EDIT
Sounds like a permissions issue. Create a bash script that does something simple as echo "helo world"
and try to run it. Make sure you have permissions for the file and for the folder containing the file. you chould just do chmod 755
just for testing.
You can retreive the outputs and return code of the exec commands, thoses might contains informations that would explain the problem...
exec('my command', $output, $return);
Since you are dropping out of the PHP context into the native shell, you are going to have a lot of issues debugging.
The best and most foolproof I have used in the past is writing the output of the script to a log file and tailing it during PHP execution.
<?php
shell_exec("filename > ~/debug.log 2>&1");
Then in a separate shell:
tail -200f ~/debug.log
When you execute your PHP script, your errors and output from your shell call will display in your debug.log
file.