About me

PHP & Shell

How it works together

Why PHP developers should know about Shell?

PHP developers deal with web development in most cases, where start point is a request to a web server. In spite of this we work with Shell:

  • we use a terminal emulator: iTerm / Gnome Terminal / Konsole / xterm / etc
  • we log to a remote machine via SSH
  • we use such PHP function as exec / shell_exec / system / passthru / proc_open or the backtick operator `...`
  • we create a command line interface to the own application for ability to automate routine actions

In order to do these tasks more effectively, worth looking the details of an intersection of PHP and Shell.

What is Shell?

Shell – an interpreter of the command line language which is determined by POSIX standard. There are couple of implementations:

  • Original Bourne Shell – sh – used in most UNIX systems
  • Bourne Again Shell – bash – used in most Linux systems
  • Debian Almquist Shell – dash – used in Debian
  • Z Shell – zsh – used by advanced users of command line :)
  • etc

Running a PHP application from Shell

<?php //sapi.php
echo PHP_SAPI . PHP_EOL . php_sapi_name() . PHP_EOL;
$ php sapi.php
cli
cli

Thus we can determine running via Shell with the constant PHP_SAPI or the function php_sapi_name.

Shell variables and environment variables

We can use variables in Shell:

$ FOO=value
$ echo $FOO
value
$ BAR='another value'
$ echo "$FOO and $BAR"
value and another value

There is a special set of Shell variables: environment variables. Current environment variables are able to be shown with the tool env:

$ env
PATH=/usr/local/bin:/usr/bin:/bin
HOME=/Users/maxvoloshin
USER=maxvoloshin
SHELL=/bin/zsh
PWD=/Users/maxvoloshin
_=/usr/bin/env

If we want to transform a variable to an environment variable we should use the command export:

$ FOO=value
$ export FOO
$ env
...
FOO=value

With each Shell execution of commands the new Shell process is created, which copy environment variables of the parent process. Exclusively for a new process it is possible to pass a special value of an environment variable or even to pass a new variable.

$ FOO=value
$ FOO=new BAZ=value env
...
FOO=new
BAZ=value
$ echo $FOO
value
$ echo $BAZ
$ 

Take into account that in the current context the value of variable FOO is not changed and variable BAZ is not created (command echo $BAZ shows nothing).

Passing parameters from Shell to a PHP application

Usage of environment variables

Values of environment variables are available in a PHP application via the function getenv:

<?php //getenv.php
echo getenv('FOO') . PHP_EOL;

Look in Shell:

$ FOO=value php getenv.php
value

Depending on the PHP configuration request_order and variables_order, environment variables are available in superglobal variables $_SERVER, $_REQUEST and $_ENV.

Usage of command arguments

Receiving information about command arguments is possible via $_SERVER['argc'] and $_SERVER['argv']:

<?php //argc_argv.php
print_r($_SERVER['argc']);
echo PHP_EOL;
print_r($_SERVER['argv']);

Usage:

$ php argc_argv.php some -arguments --here
4
Array
(
    [0] => argc_argv.php
    [1] => some
    [2] => -arguments
    [3] => --here
)

Depending on the PHP configuration register_argc_argv, this information are available in superglobal variables $argc and $argv.

Alternative approach is usage of the function getopt:

<?php //getopt.php
var_dump(getopt('ab:c::', array('foo', 'bar:', 'baz::')));

Usage:

$ php getopt.php -a -b=value -c -c=value --foo --bar=value1 --bar=value2 --baz
array(6) {
  ["a"]=>
  bool(false)
  ["b"]=>
  string(5) "value"
  ["c"]=>
  array(2) {
    [0]=>
    bool(false)
    [1]=>
    string(5) "value"
  }
  ["foo"]=>
  bool(false)
  ["bar"]=>
  array(2) {
    [0]=>
    string(6) "value1"
    [1]=>
    string(6) "value2"
  }
  ["baz"]=>
  bool(false)
}

Running a PHP application in POSIX style

Running a PHP applications as php app.php, of course, is a working approach, but it can be done in a native style for the POSIX systems, similar to how we use utilities cat, ls, cp, etc.

Let’s create the file argv:

$ touch argv

For the execution of such a program by the user, the file must have the appropriate owners and permissions. The tool ls helps to receive that information:

$ ls -l argv
-rw-r--r--  maxvoloshin  staff  ...

Look at parts of the output (from left to right):

  • - – regular file
  • rw- – owner has permissions to read and write file
  • r-- – users of group have permissions to read file
  • r-- – all users have permissions to read file
  • maxvoloshin – owner of file
  • staff – group of file

For an execution this file by the user maxvoloshin must do one of the following conditions:

  • the user maxvoloshin is owner of a file and an execution by owner is allowed
  • the user maxvoloshin is included in the group staff and an execution by a group is allowed
  • an execution by any user is allowed

Permissions can be set by the tool chmod. For example, to allow an execution of a file by owner:

$ chmod u+x argv
$ ls -l argv
-rwxr--r-- ...

The fourth character (x) describes the permissions to execute the file by owner.

The file must contain “shebang line” with PHP call, then the file will be executed via PHP. “Shebang line” – the first line of the file that starts with #! and contains call of program for execution of the file.

On different systems PHP interpreter can be located on different paths, so it is commonly used the tool env for calling program by the name. The tool env resolves the path to the program by environment variables.

#!/usr/bin/env php
<?php //argv
print_r($_SERVER['argv']);

The easiest way to execute the file in Shell – call it by an absolute or a relative path:

$ /Users/maxvoloshin/argv
Array
(
    [0] => /Users/maxvoloshin/argv
)

$ ./argv
Array
(
    [0] => ./argv
)

In most cases such way is not convenient for working with global tools, because it is necessary to keep in mind a path of an each tool. This task is solved by the environment variable PATH.

$ echo $PATH
/usr/local/bin:/usr/bin:/bin

Value of this variable contains paths to directories with tools (: is used as a separator). Let’s add the current directory to the environment variable PATH:

$ PATH=$PATH:`pwd`
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/Users/maxvoloshin

The tool which shows the absolute path to the tool by name:

$ which argv
/Users/maxvoloshin/argv

Thus we can execute an application by name:

$ argv
Array
(
    [0] => /Users/maxvoloshin/argv
)

Streams: input, output and errors

An each program in Shell can receive data from STDIN (input stream) and send data to STDOUT (output stream) and STDERR (errors stream):

For working with those streams in PHP (CLI mode) we should use special constants:

STDIN  = fopen('php://stdin', 'r')
STDOUT = fopen('php://stdout', 'w')
STDERR = fopen('php://stderr', 'w')

Usage of the output stream

#!/usr/bin/env php
<?php //stdout
fwrite(STDOUT, "Content\n"); //echo "Content\n"

Usage:

$ stdout
Content

Additionally we can redirect the output to the file:

$ stdout > file
$ cat file
Content

Also we can append the output to the existing file:

$ stdout > file
$ stdout >> file
$ cat file
Content
Content

ProTip: Do not implement those features in your application because its are implemented in Shell already.

Usage of the errors stream

Taking into account that the output can be redirected, good practice is to split the output and errors. For example, we want notify user in case uncaught exception:

#!/usr/bin/env php
<?php //stdout_stderr
fwrite(STDOUT, "Content\n");
set_exception_handler(function(Exception $e) {
   fwrite(STDERR, $e->getMessage());
});
throw new Exception('Error!');

Usage:

$ stdout_stderr
Content
Error!

Looks like a simple output, but we can feel the difference if we use redirection:

$ stdout_stderr > file
Error!
$ cat file
Content

We can redirect errors to the file:

$ stdout_stderr 2> file
Content
$ cat file
Error!

We can redirect union of the output and errors to the file:

$ stdout_stderr > file 2>&1
$ cat file
Content
Error!

We can redirect the output and errors separately:

$ stdout_stderr > content 2> error
$ cat content
Content
$ cat error
Error!

Usage of the input stream

One of the approaches to pass a data to an application is usage of the input stream:

#!/usr/bin/env php
<?php //stdin
var_dump(trim(stream_get_contents(STDIN)));

Usage:

$ stdin
Hello from Shell
<Ctrl+D>
string(16) "Hello from Shell"

After starting stdin, Shell waits an input of contents. When the input is finished, the combination Ctrl + D must be pressed.

There is ability to redirect a content of a file to the input stream:

$ stdout > file
$ cat file
Content
$ stdin < file
string(7) "Content"

or an arbitrary text:

$ stdin <<< "some string"
string(11) "some string"

It is important to realize that stream_get_contents(STDIN) is a blocking call. If you want the application to run, regardless of the availability of data in the input stream, you can use the following approach:

#!/usr/bin/env php
<?php //non_blocking_stdin
$read = [STDIN];
$write = null;
$except = null;
if (stream_select($read, $write, $except, 0) === 1) {
   var_dump(trim(stream_get_contents(STDIN)));
} else {
   echo "Data is not available in STDIN\n";
}

Usage:

$ non_blocking_stdin
Data is not available in STDIN
$ non_blocking_stdin <<< "Hello"
string(5) "Hello"

Composition of Shell programs

One of the beautiful opportunities of Shell is composition of programs. It helps to follow the principle “one program – one task” and build solutions of multiple problems with several tools at the same time.

Sequential execution

Simplest approach of program’s composition is sequential execution:

$ stdout; stdout
Content
Content

It is execution of programs one after one.

Determining the success of the program

It is often necessary that the program in the chain depended on the success of the previous program. The success of the execution of the program is determined by the return code: 0 - successful, otherwise - error.

Return code of PHP applications is argument of the function exit. In Shell, the return code of the previous command is determined by using $?.

<?php //ok.php
fwrite(STDOUT, "OK\n");
exit(0);
<?php //fail.php
fwrite(STDERR, "FAIL\n");
exit(1);

Usage:

$ php ok.php
OK
$ echo $?
0
$ php fail.php
FAIL
$ echo $?
1

Conditional execution

Program execution in case of the success of previous is possible through operator &&:

$ php fail.php && php ok.php
FAIL
$ echo $?
1

Program execution in case of the failure of previous is possible through operator ||:

$ php ok.php || php fail.php
OK
$ echo $?
0

Pipeline execution

The most powerful (IMO) ability of composition of programs in Shell is pipeline execution. The essence of the pipeline that the data of the output stream of one program is sent to the input stream of another program. It is possible through operator |:

$ stdout | stdin | stdin | stdin
string(32) "string(19) "string(7) "Content"""

Conclusion

I hope that this post gives to you an initial idea how to work with PHP in Shell. If some aspect of this post is not clear enough, feel free to ask about it in comments.