skip to content

What do the PHP CLI options -r, -l and -a do, and how does a shebang line make a PHP script runnable as ./import?

level: juniorimportance: should knowfreq 35%

answer

  1. inline code without open tags
  2. syntax check, not a test run
  3. interactive shell needs readline
  4. #!/usr/bin/env php on line one
  5. chmod +x, then ./script

basics

~10 s

php -r runs inline code without <?php tags; php -l only checks syntax; php -a opens an interactive shell that needs readline. A first line #!/usr/bin/env php plus chmod +x makes ./import runnable.

solid answer

~40 s

`php -r 'echo PHP_VERSION, PHP_EOL;'` runs inline code; the `<?php` tag must be left out (it causes a parse error), and single quotes stop the shell from expanding `$` variables. `php -l file.php` compiles without running and prints `No syntax errors detected in file.php`, or `Errors parsing file.php` with status 255; it cannot find runtime errors such as undefined functions, and since PHP 8.3 it accepts several files. `php -a` opens an interactive shell with history and tab completion; since PHP 8.1 it fails if the readline extension is missing. For a directly executable script, put `#!/usr/bin/env php` on the first line — the CLI skips a leading `#!` line — then `chmod +x bin/import` and run `./bin/import --dry-run`.

code

bash · 4 lines
bash
php -r 'echo PHP_VERSION, PHP_EOL;'          # inline code, no <?php
php -l src/Import.php src/Row.php          # lint several files (PHP 8.3+)
php -m | grep -i readline                  # is readline there for php -a?
chmod +x bin/import && ./bin/import --dry-run --limit=100 users.csv

go deeper

for a junior

Recall what -r, -l and -a do, that -r code has no <?php tag, and that a shebang script needs chmod +x.

for a middle

Explain shell quoting with -r, exactly what lint can and cannot catch, the readline dependency of -a, and why #!/usr/bin/env php is preferred.

for a senior

Use lint as a cheap CI gate on the target PHP version, ship executable bin/ commands with shebangs, and know which build features (readline, pcntl) a slim image may lack.

for a principal

Decide which checks run where — lint, static analysis, tests — so each catches its own class of error without duplicating the others' cost.

## Three ways to run PHP from a shell The `php` binary (the CLI SAPI) accepts code in three forms: a script file, code on standard input, or code passed with `-r`. It also has modes that do not execute anything normally, such as `-l`, and an interactive mode, `-a`. | Option | Long form | What it does | |---|---|---| | `-r <code>` | `--run` | runs the code string directly | | `-l` | `--syntax-check` | parses the file(s) without executing them | | `-a` | `--interactive` | opens an interactive PHP shell | ## `-r`: inline code `php -r` is for one-liners in the terminal or a script: - The code is given **without** `<?php` and `?>`; including them is a parse error. - **Shell quoting matters.** In `php -r "$count = 1;"` the shell replaces `$count` before PHP sees it, producing a syntax error. Use single quotes in POSIX shells: `php -r '$count = 1; echo $count;'`. - Arguments for the code go after `--`: `php -r 'var_dump($argv);' -- --dry-run`, where `$argv[0]` is `"Standard input code"`. - `-r` cannot be combined with `-l`. Typical uses: printing a setting (`php -r 'echo ini_get("memory_limit"), PHP_EOL;'`) or checking whether an extension loads. ## `-l`: lint `php -l import.php` parses and compiles the file without running it: - success prints `No syntax errors detected in import.php` and exits `0`; - failure prints the parse error plus `Errors parsing import.php` and exits with a non-zero status (255 in the source); - **since PHP 8.3** several files can be checked in one call (`php -l a.php b.php`); before that only one filename was accepted. It only catches what the compiler catches. Calling an undefined function, passing the wrong type or referencing a missing class are runtime errors that `-l` does not see — that is what tests and static analysis are for. Lint is cheap enough for a pre-commit hook or a first CI step, especially to catch files that fail to parse on the production PHP version. ## `-a`: interactive shell `php -a` starts a read-eval-print loop: type a statement, see its effect, with command history and tab completion of functions, constants and variables. It is handy for trying a function's behaviour (`var_dump(getopt(...))`, `var_dump(str_getcsv('a,"b,c"'))`). It depends on the **readline** extension. **Since PHP 8.1**, `php -a` fails outright when readline is not available; before 8.1 it fell back to a mode that simply read a whole script from standard input. Minimal container images often lack readline. ## Shebang: running a script like a program On Unix-like systems, a file that starts with `#!` names the interpreter the kernel should use. For PHP: ```php #!/usr/bin/env php <?php declare(strict_types=1); // ... ``` 1. **First line** `#!/usr/bin/env php` — `env` finds `php` on the `PATH`, which is more portable than a hard-coded `/usr/bin/php`. 2. **The CLI skips it.** PHP's CLI ignores a first line starting with `#!`, so it is not printed as output. 3. **Make it executable:** `chmod +x bin/import`. 4. **Run it:** `./bin/import --dry-run --limit=100 users.csv`. The file then needs no `.php` extension; many projects keep such commands in `bin/`. The `<?php` tag is still required on the next line, and `declare(strict_types=1)` must be the first statement after it. ## Other CLI options worth knowing - `-d name=value` sets an ini directive for one run (`php -d memory_limit=512M bin/import`). - `-n` ignores php.ini entirely, useful to test a clean configuration. - `-m` lists loaded extensions, a quick way to confirm `readline` or `pcntl` is present.

  • Why does php -r "$x = 5; echo $x;" fail in bash?
    Inside double quotes, bash expands `$x` before PHP runs, and since the shell variable is empty PHP receives ` = 5; echo ;`, a syntax error. Use single quotes, which the shell leaves untouched: `php -r '$x = 5; echo $x;'`.
  • php -l reports no syntax errors, yet the script dies with 'Call to undefined function'. Why?
    `-l` only parses and compiles. Whether a called function exists is checked when the call executes, so it is a runtime error that lint cannot see. Tests and static analysis find that class of error.

saying these in an interview costs you the question

  • php -r needs the code wrapped in <?php ... ?>
  • php -l runs the script in a safe mode and reports runtime errors
  • php -a works on any build, with or without readline
  • The shebang line is printed as output when the script runs
  • A shebang makes the script executable without chmod +x