Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use Python's os module methods instead of executing shell commands directly?

People also ask

Why we use os module in Python?

The OS module in Python provides functions for interacting with the operating system. OS comes under Python's standard utility modules. This module provides a portable way of using operating system-dependent functionality.

Which module in Python is used to execute os commands?

The subprocess is the most versatile approach and the recommended module to execute shell commands with Python. The subprocess module allows you to spawn new processes. It connects to the process' input/output/error pipes and obtains their return codes. This module replaces several older modules and functions like os.

How do you run a shell command in Python?

If you need to execute a shell command with Python, there are two ways. You can either use the subprocess module or the RunShellCommand() function. The first option is easier to run one line of code and then exit, but it isn't as flexible when using arguments or producing text output.


  1. It's faster, os.system and subprocess.call create new processes which is unnecessary for something this simple. In fact, os.system and subprocess.call with the shell argument usually create at least two new processes: the first one being the shell, and the second one being the command that you're running (if it's not a shell built-in like test).

  2. Some commands are useless in a separate process. For example, if you run os.spawn("cd dir/"), it will change the current working directory of the child process, but not of the Python process. You need to use os.chdir for that.

  3. You don't have to worry about special characters interpreted by the shell. os.chmod(path, mode) will work no matter what the filename is, whereas os.spawn("chmod 777 " + path) will fail horribly if the filename is something like ; rm -rf ~. (Note that you can work around this if you use subprocess.call without the shell argument.)

  4. You don't have to worry about filenames that begin with a dash. os.chmod("--quiet", mode) will change the permissions of the file named --quiet, but os.spawn("chmod 777 --quiet") will fail, as --quiet is interpreted as an argument. This is true even for subprocess.call(["chmod", "777", "--quiet"]).

  5. You have fewer cross-platform and cross-shell concerns, as Python's standard library is supposed to deal with that for you. Does your system have chmod command? Is it installed? Does it support the parameters that you expect it to support? The os module will try to be as cross-platform as possible and documents when that it's not possible.

  6. If the command you're running has output that you care about, you need to parse it, which is trickier than it sounds, as you may forget about corner-cases (filenames with spaces, tabs and newlines in them), even when you don't care about portability.


It is safer. To give you an idea here is an example script

import os
file = raw_input("Please enter a file: ")
os.system("chmod 777 " + file)

If the input from the user was test; rm -rf ~ this would then delete the home directory.

This is why it is safer to use the built in function.

Hence why you should use subprocess instead of system too.


There are four strong cases for preferring Python's more-specific methods in the os module over using os.system or the subprocess module when executing a command:

  • Redundancy - spawning another process is redundant and wastes time and resources.
  • Portability - Many of the methods in the os module are available in multiple platforms while many shell commands are os-specific.
  • Understanding the results - Spawning a process to execute arbitrary commands forces you to parse the results from the output and understand if and why a command has done something wrong.
  • Safety - A process can potentially execute any command it's given. This is a weak design and it can be avoided by using specific methods in the os module.

Redundancy (see redundant code):

You're actually executing a redundant "middle-man" on your way to the eventual system calls (chmod in your example). This middle man is a new process or sub-shell.

From os.system:

Execute the command (a string) in a subshell ...

And subprocess is just a module to spawn new processes.

You can do what you need without spawning these processes.

Portability (see source code portability):

The os module's aim is to provide generic operating-system services and it's description starts with:

This module provides a portable way of using operating system dependent functionality.

You can use os.listdir on both windows and unix. Trying to use os.system / subprocess for this functionality will force you to maintain two calls (for ls / dir) and check what operating system you're on. This is not as portable and will cause even more frustration later on (see Handling Output).

Understanding the command's results:

Suppose you want to list the files in a directory.

If you're using os.system("ls") / subprocess.call(['ls']), you can only get the process's output back, which is basically a big string with the file names.

How can you tell a file with a space in it's name from two files?

What if you have no permission to list the files?

How should you map the data to python objects?

These are only off the top of my head, and while there are solutions to these problems - why solve again a problem that was solved for you?

This is an example of following the Don't Repeat Yourself principle (Often reffered to as "DRY") by not repeating an implementation that already exists and is freely available for you.

Safety:

os.system and subprocess are powerful. It's good when you need this power, but it's dangerous when you don't. When you use os.listdir, you know it can not do anything else other then list files or raise an error. When you use os.system or subprocess to achieve the same behaviour you can potentially end up doing something you did not mean to do.

Injection Safety (see shell injection examples):

If you use input from the user as a new command you've basically given him a shell. This is much like SQL injection providing a shell in the DB for the user.

An example would be a command of the form:

# ... read some user input
os.system(user_input + " some continutation")

This can be easily exploited to run any arbitrary code using the input: NASTY COMMAND;# to create the eventual:

os.system("NASTY COMMAND; # some continuation")

There are many such commands that can put your system at risk.


For a simple reason - when you call a shell function, it creates a sub-shell which is destroyed after your command exists, so if you change directory in a shell - it does not affect your environment in Python.

Besides, creating sub-shell is time consuming, so using OS commands directly will impact your performance

EDIT

I had some timing tests running:

In [379]: %timeit os.chmod('Documents/recipes.txt', 0755)
10000 loops, best of 3: 215 us per loop

In [380]: %timeit os.system('chmod 0755 Documents/recipes.txt')
100 loops, best of 3: 2.47 ms per loop

In [382]: %timeit call(['chmod', '0755', 'Documents/recipes.txt'])
100 loops, best of 3: 2.93 ms per loop

Internal function runs more than 10 time faster

EDIT2

There may be cases when invoking external executable may yield better results than Python packages - I just remembered a mail sent by a colleague of mine that performance of gzip called through subprocess was much higher than the performance of a Python package he used. But certainly not when we are talking about standard OS packages emulating standard OS commands