A simple solution to determine the difference between any two similar-sounding PHP functions is to write a quick command-line script in PHP that outputs the entire possible search space and just show differences (in this case, comparing 256 values):
<?php
for ($x = 0; $x < 256; $x++)
{
if (chr($x) !== escapeshellcmd(chr($x))) echo $x . " - cmd: " . chr($x) . " != " . escapeshellcmd(chr($x)) . "\n";
}
echo "\n\n";
for ($x = 0; $x < 256; $x++)
{
if (chr($x) !== substr(escapeshellarg(chr($x)), 1, -1)) echo $x . " - arg: " . chr($x) . " != " . substr(escapeshellarg(chr($x)), 1, -1) . "\n";
}
?>
Running the above under PHP 5.6 on the Windows Command Prompt outputs:
0 - cmd: !=
10 - cmd:
!= ^
33 - cmd: ! != ^!
34 - cmd: " != ^"
35 - cmd: # != ^#
36 - cmd: $ != ^$
37 - cmd: % != ^%
38 - cmd: & != ^&
39 - cmd: ' != ^'
40 - cmd: ( != ^(
41 - cmd: ) != ^)
42 - cmd: * != ^*
59 - cmd: ; != ^;
60 - cmd: < != ^<
62 - cmd: > != ^>
63 - cmd: ? != ^?
91 - cmd: [ != ^[
92 - cmd: \ != ^\
93 - cmd: ] != ^]
94 - cmd: ^ != ^^
96 - cmd: ` != ^`
123 - cmd: { != ^{
124 - cmd: | != ^|
125 - cmd: } != ^}
126 - cmd: ~ != ^~
255 - cmd: != ^
0 - arg: !=
33 - arg: ! !=
34 - arg: " !=
37 - arg: % !=
92 - arg: \ != \\
Running the same script under PHP 5.5 for Linux outputs:
0 - cmd: !=
10 - cmd:
!= \
34 - cmd: " != \"
35 - cmd: # != \#
36 - cmd: $ != \$
38 - cmd: & != \&
39 - cmd: ' != \'
40 - cmd: ( != \(
41 - cmd: ) != \)
42 - cmd: * != \*
59 - cmd: ; != \;
60 - cmd: < != \<
62 - cmd: > != \>
63 - cmd: ? != \?
91 - cmd: [ != \[
92 - cmd: \ != \\
93 - cmd: ] != \]
94 - cmd: ^ != \^
96 - cmd: ` != \`
123 - cmd: { != \{
124 - cmd: | != \|
125 - cmd: } != \}
126 - cmd: ~ != \~
128 - cmd: !=
...
255 - cmd: ÿ !=
0 - arg: !=
39 - arg: ' != '\''
128 - arg: !=
...
255 - arg: ÿ !=
The main difference is that PHP escapeshellcmd() under Windows prefixes characters with a caret ^ instead of a backslash \. The oddities under Linux from chr(128) through chr(255) for both escapeshellcmd() and escapeshellarg() can be explained by the use of invalid UTF-8 code points being dropped, truncated, or misinterpreted.
Also of note is that escapeshellarg() escapes far fewer characters and still gets the job done.
In terms of overall system and application safety and security, you are better off using escapeshellarg() and individually escape each argument that consists of user input.
One final example:
echo escapeshellarg("something here") . "\n";
echo escapeshellarg("'something here'") . "\n";
echo escapeshellarg("\"something here\"") . "\n";
Windows outputs:
"something here"
"'something here'"
" something here "
Linux outputs:
'something here'
''\''something here'\'''
'"something here"'
PHP escapeshellarg() on Windows surrounds the string with the double-quote " character while Linux uses the single-quote ' character. PHP on Windows completely replaces internal double-quotes with spaces (which could be a problem in some cases). PHP on Linux goes a bit out of its way to escape single-quotes and backslashes \ are escaped \\ on Windows. PHP escapeshellarg() on Windows also replaces ! and % characters with spaces. All platforms replace \0 with spaces.
Note that behavior is not necessarily consistent between PHP versions and the PHP documentation doesn't always reflect reality. Writing a quick script or reading the source code of PHP are two ways to figure out what's happening behind the scenes.