skip to content

In PHP, how do exec(), shell_exec(), system() and passthru() differ in what they return, what they print and how they report the exit code?

level: juniorimportance: must knowfreq 52%

answer

  1. capture, print, or pass through
  2. exec fills an array, returns last line
  3. shell_exec: null hides failures
  4. result_code by reference
  5. backticks deprecated in 8.5

basics

~20 s

exec() returns the last output line, appends every line to $output and sets $result_code. shell_exec() returns the whole output but no exit code. system() prints output and returns the last line; passthru() streams raw bytes.

solid answer

~50 s

All four hand the command string to the shell (`/bin/sh -c` on POSIX systems), and all capture only **stdout**. `exec($cmd, $output, $result_code)` prints nothing. It appends each line to `$output` with trailing whitespace stripped, writes the exit status to `$result_code`, and returns the last line, or `false` if the command could not be started. `shell_exec($cmd)` returns the entire output as one string, but `null` both when the command produced no output and when it failed, and it gives no exit code. `system($cmd, $result_code)` prints the output as it arrives and returns the last line. `passthru($cmd, $result_code)` sends the raw bytes straight to output, which suits binary data, and returns `null` on success. The backtick operator is an alias of `shell_exec()` and is deprecated in PHP 8.5. For anything that must succeed, I use `exec()` and check `$result_code === 0`.

code

php · 18 lines
php
<?php
declare(strict_types=1);

$pdf = '/var/app/uploads/report.pdf';
$png = '/var/app/thumbs/report.png';

$output = [];
$code = -1;
// pdfthumb stands for any command-line thumbnail tool.
$last = exec(
    'pdfthumb --page 1 --width 320 ' . escapeshellarg($pdf) . ' ' . escapeshellarg($png) . ' 2>&1',
    $output,
    $code,
);

if ($last === false || $code !== 0) {
    throw new RuntimeException("Thumbnail failed ($code): " . implode("\n", $output));
}

go deeper

for a junior

Recall which function prints, which returns everything, and which gives you the exit code through a by-reference argument.

for a middle

Explain the exact return values, why exec() returning a string does not mean success, the append behaviour of $output, and capturing stderr with 2>&1.

for a senior

Choose the function by output handling and failure detection, remove backticks during PHP 8.5 upgrades, and move to proc_open() when you need stderr, stdin or timeouts.

for a principal

Treat every shell call as an operational dependency: centralise command execution behind one wrapper that logs the command, the exit status and the elapsed time.

## Four functions, one mechanism PHP's program execution functions all take a **command string**, give it to the system shell (`/bin/sh -c` on Linux and other POSIX systems), and connect to the command's standard output. They differ only in what they do with that output and whether they tell you the **exit status**, the integer a program returns to say whether it succeeded (0 by convention) or failed (non-zero). | Function | Signature (PHP 8.5) | Output goes to | Returns | Exit status | |---|---|---|---|---| | `exec()` | `exec(string $command, &$output = null, &$result_code = null): string\|false` | the `$output` array, one line per element | last line, `false` if it could not run | via `$result_code` | | `shell_exec()` | `shell_exec(string $command): string\|false\|null` | the return value, as one string | full output; `null` if none or on error; `false` if no pipe | not available | | `system()` | `system(string $command, &$result_code = null): string\|false` | printed as it arrives | last line, or `false` on failure | via `$result_code` | | `passthru()` | `passthru(string $command, &$result_code = null): false\|null` | printed raw, unmodified | `null` on success, `false` on failure | via `$result_code` | ## Details that cause bugs - **exec() appends.** If `$output` already holds elements, new lines are added to the end. Reusing one variable across calls mixes their output; reset it with `$output = []` first. - **exec() trims.** Each line loses its trailing whitespace, including the newline. That is convenient for text, and wrong for binary output. - **A failing command is not a `false` return.** `exec()` returns `false` only when PHP cannot start the command at all. A program that runs and exits with status 1, or a missing program (the shell then exits with 127), still returns a string. The failure is visible only in `$result_code`. - **shell_exec() cannot detect failure.** The manual warns that `null` means either an error or no output, and recommends `exec()` whenever the exit code matters. - **stderr is not captured.** Error messages from the command go to the PHP process's own standard error: the terminal for CLI, and usually a log for PHP-FPM. Append `2>&1` to the command to merge them into the captured output. - **Empty commands throw.** Since PHP 8.0, an empty command or one containing a null byte throws `ValueError` instead of warning. - **system() flushes.** When PHP runs as a server module, `system()` tries to flush the web server's output buffer after each line, which is why it is used for progress output and rarely for capture. ## When each one fits Imagine a feature that generates PDF thumbnails with an external command-line tool: 1. **Run it and check success**: `exec()`, with the output array for diagnostics and `$result_code` for the verdict. 2. **Stream an image the tool writes to stdout** directly to the browser: `passthru()`, after sending the right `Content-Type` header, because it does not alter bytes. 3. **Grab a short text answer** where failure does not matter: `shell_exec()`, accepting that errors are indistinguishable from empty output. 4. **Show live progress in a CLI script**: `system()`. ## The backtick operator `` `ls -l` `` is the same as `shell_exec('ls -l')`. It hides a shell call inside what looks like a string literal, so it is easy to miss in review. PHP 8.5 deprecates it: compiling code that uses it emits `E_DEPRECATED` with the message "The backtick (`) operator is deprecated, use shell_exec() instead". Replace it with an explicit function call. ## Where the command runs The command inherits the PHP process's environment: its user, working directory and environment variables. Under PHP-FPM that usually means the pool's user, a working directory you did not choose, and a minimal `PATH`, so a tool that works in your terminal may not be found from a web request. Call tools by absolute path, or check `PATH` in the environment the worker actually has. Hosts sometimes disable these functions entirely through the `disable_functions` ini directive; calling a disabled function then throws an `Error` for an undefined function. ## Beyond the four All four pass a **string** to a shell, so every dynamic part must be escaped with `escapeshellarg()`. When you need separate stdout and stderr, input on stdin, a timeout, or no shell at all, `proc_open()` with an array command is the better tool. In a web request, remember that the command runs synchronously: PHP waits for it to finish.

  • Why can't you rely on shell_exec() to detect that a command failed?
    `shell_exec()` returns `null` both when the command produced no output and when an error occurred, and `false` only if the pipe could not be established. It never exposes the exit status. The manual itself recommends `exec()` when you need the program's exit code.
  • Why does error text from the command not appear in exec()'s $output?
    The exec family captures only standard output. Standard error is inherited from the PHP process, so it goes to the terminal in CLI or to a log under PHP-FPM. Appending `2>&1` to the command string tells the shell to merge stderr into stdout, so the messages land in `$output`.
  • What happens in PHP 8.5 when code uses the backtick operator, as in $files = `ls`;?
    It still runs, as an alias of `shell_exec()`, but compiling it emits `E_DEPRECATED`: 'The backtick (`) operator is deprecated, use shell_exec() instead'. Replace it with an explicit `shell_exec()` or, better, `exec()` when the exit code matters.

saying these in an interview costs you the question

  • exec() returns false whenever the command exits with a non-zero status.
  • shell_exec() returns the command's exit code.
  • exec() replaces the contents of the $output array on each call.
  • The exec family captures stderr along with stdout by default.
  • system() returns the full output without printing it.
  • Backticks are the modern preferred shorthand for running commands in PHP 8.5.