It is because of the way how the call happens:
With shell=True
, the call is performed via the shell, and the command is given to the shell as one string.
With shell=False
, the call is performed directly, via execv()
and related functions. These functions expet an array of arguments.
If you only pass one string, it is treated as an abbreviation for a call with only the executable's name without arguments. But there is (probably) no executable called ls -lh
on your system.
To be exact, somewhere deep inside subprocess.py
, the following happens:
if isinstance(args, types.StringTypes):
args = [args]
else:
args = list(args)
So every string passed is turned into a list with one element.
if shell:
args = ["/bin/sh", "-c"] + args
This one I didn't know: obviously, this allows for passing additional arguments to the called shell. Although it is documented this way, don't use it as it is subject to create too much confusion.
If shell=False
, we have further down below
if env is None:
os.execvp(executable, args)
else:
os.execvpe(executable, args, env)
which just takes a list and uses it for the call.