2

So, I'm new to programming (learning Python on Linux) & don't know a lot about it, but I thought that it was good to try sometimes something different than the lecturer says/does. But unfortunately what I expected to happen, isn't happening.

So, the lecturer told me to type the following: subprocess.call("ifconfig", shell=True). Whenever I run this command, it will give me my eth0 etc... So I was wondering what would happen if you put the variable 'shell' to 'False', so I tried this: subprocess.call("ifconfig", shell=False).

This is still functioning and whenever I run the program, it will still execute the command 'ifconfig' even when I changed the variable to 'False'.

Why does it keep executing this?

milanbalazs
  • 4,811
  • 4
  • 23
  • 45
  • Why not? Please read [the docs](https://docs.python.org/3/library/subprocess.html#subprocess.call) – ForceBru Aug 26 '19 at 14:34
  • @ForceBru the doc section you link only lists the `shell` parameter, it doesn't say anything about its function. – Arne Aug 26 '19 at 14:53
  • @Arne, I couldn't find a way to directly link to the "Frequently Used Arguments" section, so I gave a link to the `call` function. Still, one should read the docs of the module itself too – ForceBru Aug 26 '19 at 14:57
  • One should, but honestly, the `subprocess` docs are quite confusing. If they weren't, we wouldn't get so many questions regarding things in it that could in principle be solved by just reading them. – Arne Aug 26 '19 at 14:59

3 Answers3

3

One difference between shell=True and shell=False is when you pass in two arguments to the subprocess.call function. For example:

subprocess.call("ls -l", shell=False)
# raises OSError
subprocess.call("ls -l", shell=True)
# returns the directories and files in long format

quoting from this link

args is required for all calls and should be a string, or a sequence of program arguments. Providing a sequence of arguments is generally preferred, as it allows the module to take care of any required escaping and quoting of arguments (e.g. to permit spaces in file names). If passing a single string, either shell must be True (see below) or else the string must simply name the program to be executed without specifying any arguments.

blueishpoop
  • 226
  • 1
  • 10
3

The description of the shell parameter is a little hidden in the docs, and even then it presupposes some knowledge in order to know what exactly it does.

The short version is that it's a mode flag , and in shell=True mode it expects the arguments to be a single string, like so:

# I'm using subprocess.run instead of subprocess.call, they are very similar in 
# what they do but subprocess.run has a nicer interface
from subprocess import run
>>> run('ls -l', shell=True)
total 0
-rw-r--r-- 1 root root 0 Aug 26 16:36 file_a.txt
-rw-r--r-- 1 root root 0 Aug 26 16:36 file_b.txt
CompletedProcess(args='ls -l', returncode=0)

And in shell=False mode, it expects the command as a list of strings, which it will turn internally into a proper call. This is usually preferable, since parsing a shell command correctly can end up being really really tricky:

# this is the equivalent shell=False command
>>> run(['ls', '-l'], shell=False)
total 0
-rw-r--r-- 1 root root 0 Aug 26 16:36 file_a.txt
-rw-r--r-- 1 root root 0 Aug 26 16:36 file_b.txt
CompletedProcess(args=['ls', '-l'], returncode=0)

There is a whole module dedicated to shell lexing in the standard library called shlex, and by using shell=False mode you'll never have to mess with it, which is nice.


Your example is in so far a special case as the command only contains a single argument, in which case both modes are just a little more permissive and pretend that they can handle either form of input - a single string or a list of strings.

But as soon as you have two arguments, their behavior differs:

# shell=True with an argument list, only runs the "ls" part, not "ls -l"
>>> run(['ls', '-l'], shell=True)
file_a.txt  file_b.txt
CompletedProcess(args=['ls', '-l'], returncode=0)

# and shell=False with two arguments as a non-list fares even worse
>>> run('ls -l', shell=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/subprocess.py", line 423, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/usr/lib/python3.6/subprocess.py", line 729, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.6/subprocess.py", line 1364, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'ls -l': 'ls -l'
Arne
  • 17,706
  • 5
  • 83
  • 99
1

If shell is True, the specified command will be executed through the shell. This can be useful if you are using Python primarily for the enhanced control flow it offers over most system shells and still want convenient access to other shell features such as shell pipes, filename wildcards, environment variable expansion, and expansion of ~ to a user’s home directory. However, note that Python itself offers implementations of many shell-like features (in particular, glob, fnmatch, os.walk(), os.path.expandvars(), os.path.expanduser(), and shutil).

Consult:

https://docs.python.org/2/library/subprocess.html#frequently-used-arguments

Secction: 17.1.1.1. Frequently Used Arguments

ansev
  • 30,322
  • 5
  • 17
  • 31
  • But why doesn't nothing change when I use ```subprocess.call("ifconfig", shell=False)```? – BlitzAssassin Aug 26 '19 at 14:36
  • 4
    What do you expect to change? `ifconfig` is a binary executable. It can be run via a shell or not, but since it doesn't use any features of the shell, it doesn't make any difference here. It would make a difference if you were calling `ifconfig *`, if `shell=True` here, the `*` would be expanded by the shell, for `shell=False` it would just pass the `*` as a command line arg to `ifconfig`. – pcarter Aug 26 '19 at 14:41
  • Thanks! I didn't know it didn't use any features of the shell! – BlitzAssassin Aug 26 '19 at 15:04