Running Bash commands in Python
Asked Answered
W

12

520

On my local machine, I run a python script which contains this line

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)

This works fine.

Then I run the same code on a server and I get the following error message

'import site' failed; use -v for traceback
Traceback (most recent call last):
File "/usr/bin/cwm", line 48, in <module>
from swap import  diag
ImportError: No module named swap

So what I did then is I inserted a print bashCommand which prints me than the command in the terminal before it runs it with os.system().

Of course, I get again the error (caused by os.system(bashCommand)) but before that error it prints the command in the terminal. Then I just copied that output and did a copy paste into the terminal and hit enter and it works...

Does anyone have a clue what's going on?

Woolridge answered 23/11, 2010 at 12:38 Comment(6)
There seems to be a difference in the environment depending on how you run cwm. Maybe you have some configuration in your .bashrc that sets up the environment for interactive bash use?Berkie
Did you try running the command from the command line when logged in on the server? Your post just says you "pasted [it] into the terminal".Berkie
@Sven: yes I meant that I ran the command directly in the terminal of the serverWoolridge
There seems to be a difference in the PYTHONPATH depending on how you run cwm. Or maybe there is a difference in PATH, and different version of cwm are called. Or different versions of Python. It is really hard to figure this out without access to the machine...Berkie
this will save you some time: https://mcmap.net/q/25477/-running-bash-commands-in-pythonVow
@tripleee : BTW, they is missed in the bash chatroom !Picket
T
511

To somewhat expand on the earlier answers here, there are a number of details which are commonly overlooked.

  • Prefer subprocess.run() over subprocess.check_call() and friends over subprocess.call() over subprocess.Popen() over os.system() over os.popen()
  • Understand and probably use text=True, aka universal_newlines=True.
  • Understand the meaning of shell=True or shell=False and how it changes quoting and the availability of shell conveniences.
  • Understand differences between sh and Bash
  • Understand how a subprocess is separate from its parent, and generally cannot change the parent.
  • Avoid running the Python interpreter as a subprocess of Python.

These topics are covered in some more detail below.

Prefer subprocess.run() or subprocess.check_call()

The subprocess.Popen() function is a low-level workhorse but it is tricky to use correctly and you end up copy/pasting multiple lines of code ... which conveniently already exist in the standard library as a set of higher-level wrapper functions for various purposes, which are presented in more detail in the following.

Here's a paragraph from the documentation:

The recommended approach to invoking subprocesses is to use the run() function for all use cases it can handle. For more advanced use cases, the underlying Popen interface can be used directly.

Unfortunately, the availability of these wrapper functions differs between Python versions.

  • subprocess.run() was officially introduced in Python 3.5. It is meant to replace all of the following.
  • subprocess.check_output() was introduced in Python 2.7 / 3.1. It is basically equivalent to subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call() was introduced in Python 2.5. It is basically equivalent to subprocess.run(..., check=True)
  • subprocess.call() was introduced in Python 2.4 in the original subprocess module (PEP-324). It is basically equivalent to subprocess.run(...).returncode

High-level API vs subprocess.Popen()

The refactored and extended subprocess.run() is more logical and more versatile than the older legacy functions it replaces. It returns a CompletedProcess object which has various methods which allow you to retrieve the exit status, the standard output, and a few other results and status indicators from the finished subprocess.

subprocess.run() is the way to go if you simply need a program to run and return control to Python. For more involved scenarios (background processes, perhaps with interactive I/O with the Python parent program) you still need to use subprocess.Popen() and take care of all the plumbing yourself. This requires a fairly intricate understanding of all the moving parts and should not be undertaken lightly. The simpler Popen object represents the (possibly still-running) process which needs to be managed from your code for the remainder of the lifetime of the subprocess.

It should perhaps be emphasized that just subprocess.Popen() merely creates a process. If you leave it at that, you have a subprocess running concurrently alongside with Python, so a "background" process. If it doesn't need to do input or output or otherwise coordinate with you, it can do useful work in parallel with your Python program.

Avoid os.system() and os.popen()

Since time eternal (well, since Python 2.5) the os module documentation has contained the recommendation to prefer subprocess over os.system():

The subprocess module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function.

The problems with system() are that it's obviously system-dependent and doesn't offer ways to interact with the subprocess. It simply runs, with standard output and standard error outside of Python's reach. The only information Python receives back is the exit status of the command (zero means success, though the meaning of non-zero values is also somewhat system-dependent).

PEP-324 (which was already mentioned above) contains a more detailed rationale for why os.system is problematic and how subprocess attempts to solve those issues.

os.popen() used to be even more strongly discouraged:

Deprecated since version 2.6: This function is obsolete. Use the subprocess module.

However, since sometime in Python 3, it has been reimplemented to simply use subprocess, and redirects to the subprocess.Popen() documentation for details.

Understand and usually use check=True

You'll also notice that subprocess.call() has many of the same limitations as os.system(). In regular use, you should generally check whether the process finished successfully, which subprocess.check_call() and subprocess.check_output() do (where the latter also returns the standard output of the finished subprocess). Similarly, you should usually use check=True with subprocess.run() unless you specifically need to allow the subprocess to return an error status.

In practice, with check=True or subprocess.check_*, Python will throw a CalledProcessError exception if the subprocess returns a nonzero exit status.

A common error with subprocess.run() is to omit check=True and be surprised when downstream code fails if the subprocess failed.

On the other hand, a common problem with check_call() and check_output() was that users who blindly used these functions were surprised when the exception was raised e.g. when grep did not find a match. (You should probably replace grep with native Python code anyway, as outlined below.)

All things counted, you need to understand how shell commands return an exit code, and under what conditions they will return a non-zero (error) exit code, and make a conscious decision how exactly it should be handled.

Understand and probably use text=True aka universal_newlines=True

Since Python 3, strings internal to Python are Unicode strings. But there is no guarantee that a subprocess generates Unicode output, or strings at all.

(If the differences are not immediately obvious, Ned Batchelder's Pragmatic Unicode is recommended, if not outright obligatory, reading. There is a 36-minute video presentation behind the link if you prefer, though reading the page yourself will probably take significantly less time.)

Deep down, Python has to fetch a bytes buffer and interpret it somehow. If it contains a blob of binary data, it shouldn't be decoded into a Unicode string, because that's error-prone and bug-inducing behavior - precisely the sort of pesky behavior which riddled many Python 2 scripts, before there was a way to properly distinguish between encoded text and binary data.

With text=True, you tell Python that you, in fact, expect back textual data in the system's default encoding, and that it should be decoded into a Python (Unicode) string to the best of Python's ability (usually UTF-8 on any moderately up to date system, except perhaps Windows?)

If that's not what you request back, Python will just give you bytes strings in the stdout and stderr strings. Maybe at some later point you do know that they were text strings after all, and you know their encoding. Then, you can decode them.

normal = subprocess.run([external, arg],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    check=True,
    text=True)
print(normal.stdout)

convoluted = subprocess.run([external, arg],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))

Python 3.7 introduced the shorter and more descriptive and understandable alias text for the keyword argument which was previously somewhat misleadingly called universal_newlines.

Understand shell=True vs shell=False

With shell=True you pass a single string to your shell, and the shell takes it from there.

With shell=False you pass a list of arguments to the OS, bypassing the shell.

When you don't have a shell, you save a process and get rid of a fairly substantial amount of hidden complexity, which may or may not harbor bugs or even security problems.

On the other hand, when you don't have a shell, you don't have redirection, wildcard expansion, job control, and a large number of other shell features.

A common mistake is to use shell=True and then still pass Python a list of tokens, or vice versa. This happens to work in some cases, but is really ill-defined and could break in interesting ways.

# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')

# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
    shell=True)

# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
    shell=True)

correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
    # Probably don't forget these, too
    check=True, text=True)

# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
    shell=True,
    # Probably don't forget these, too
    check=True, text=True)

The common retort "but it works for me" is not a useful rebuttal unless you understand exactly under what circumstances it could stop working.

To briefly recap, correct usage looks like

subprocess.run("string for 'the shell' to parse", shell=True)
# or
subprocess.run(["list", "of", "tokenized strings"]) # shell=False

If you want to avoid the shell but are too lazy or unsure of how to parse a string into a list of tokens, notice that shlex.split() can do this for you.

subprocess.run(shlex.split("no string for 'the shell' to parse"))  # shell=False
# equivalent to
# subprocess.run(["no", "string", "for", "the shell", "to", "parse"])

The regular split() will not work here, because it doesn't preserve quoting. In the example above, notice how "the shell" is a single string.

Refactoring Example

Very often, the features of the shell can be replaced with native Python code. Simple Awk or sed scripts should probably just be translated to Python instead.

To partially illustrate this, here is a typical but slightly silly example which involves many shell features.

cmd = '''while read -r x;
   do ping -c 3 "$x" | grep 'min/avg/max'
   done <hosts.txt'''

# Trivial but horrible
results = subprocess.run(
    cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)

# Reimplement with shell=False
with open('hosts.txt') as hosts:
    for host in hosts:
        host = host.rstrip('\n')  # drop newline
        ping = subprocess.run(
             ['ping', '-c', '3', host],
             text=True,
             stdout=subprocess.PIPE,
             check=True)
        for line in ping.stdout.split('\n'):
             if 'min/avg/max' in line:
                 print('{}: {}'.format(host, line))

Some things to note here:

  • With shell=False you don't need the quoting that the shell requires around strings. Putting quotes anyway is probably an error.
  • It often makes sense to run as little code as possible in a subprocess. This gives you more control over execution from within your Python code.
  • Having said that, complex shell pipelines are tedious and sometimes challenging to reimplement in Python.

The refactored code also illustrates just how much the shell really does for you with a very terse syntax -- for better or for worse. Python says explicit is better than implicit but the Python code is rather verbose and arguably looks more complex than this really is. On the other hand, it offers a number of points where you can grab control in the middle of something else, as trivially exemplified by the enhancement that we can easily include the host name along with the shell command output. (This is by no means challenging to do in the shell, either, but at the expense of yet another diversion and perhaps another process.)

Common Shell Constructs

For completeness, here are brief explanations of some of these shell features, and some notes on how they can perhaps be replaced with native Python facilities.

  • Globbing aka wildcard expansion can be replaced with glob.glob() or very often with simple Python string comparisons like for file in os.listdir('.'): if not file.endswith('.png'): continue. Bash has various other expansion facilities like .{png,jpg} brace expansion and {1..100} as well as tilde expansion (~ expands to your home directory, and more generally ~account to the home directory of another user)
  • Shell variables like $SHELL or $my_exported_var can sometimes simply be replaced with Python variables. Exported shell variables are available as e.g. os.environ['SHELL'] (the meaning of export is to make the variable available to subprocesses -- a variable which is not available to subprocesses will obviously not be available to Python running as a subprocess of the shell, or vice versa. The env= keyword argument to subprocess methods allows you to define the environment of the subprocess as a dictionary, so that's one way to make a Python variable visible to a subprocess). With shell=False you will need to understand how to remove any quotes; for example, cd "$HOME" is equivalent to os.chdir(os.environ['HOME']) without quotes around the directory name. (Very often cd is not useful or necessary anyway, and many beginners omit the double quotes around the variable and get away with it until one day ...)
  • Redirection allows you to read from a file as your standard input, and write your standard output to a file. grep 'foo' <inputfile >outputfile opens outputfile for writing and inputfile for reading, and passes its contents as standard input to grep, whose standard output then lands in outputfile. This is not generally hard to replace with native Python code.
  • Pipelines are a form of redirection. echo foo | nl runs two subprocesses, where the standard output of echo is the standard input of nl (on the OS level, in Unix-like systems, this is a single file handle). If you cannot replace one or both ends of the pipeline with native Python code, perhaps think about using a shell after all, especially if the pipeline has more than two or three processes (though look at the pipes module in the Python standard library or a number of more modern and versatile third-party competitors).
  • Job control lets you interrupt jobs, run them in the background, return them to the foreground, etc. The basic Unix signals to stop and continue a process are of course available from Python, too. But jobs are a higher-level abstraction in the shell which involve process groups etc which you have to understand if you want to do something like this from Python.
  • Quoting in the shell is potentially confusing until you understand that everything is basically a string. So ls -l / is equivalent to 'ls' '-l' '/' but the quoting around literals is completely optional. Unquoted strings which contain shell metacharacters undergo parameter expansion, whitespace tokenization and wildcard expansion; double quotes prevent whitespace tokenization and wildcard expansion but allow parameter expansions (variable substitution, command substitution, and backslash processing). This is simple in theory but can get bewildering, especially when there are several layers of interpretation (a remote shell command, for example).

Understand differences between sh and Bash

subprocess runs your shell commands with /bin/sh unless you specifically request otherwise (except of course on Windows, where it uses the value of the COMSPEC variable). This means that various Bash-only features like arrays, [[ etc are not available.

If you need to use Bash-only syntax, you can pass in the path to the shell as executable='/bin/bash' (where of course if your Bash is installed somewhere else, you need to adjust the path).

subprocess.run('''
    # This for loop syntax is Bash only
    for((i=1;i<=$#;i++)); do
        # Arrays are Bash-only
        array[i]+=123
    done''',
    shell=True, check=True,
    executable='/bin/bash')

A subprocess is separate from its parent, and cannot change it

A somewhat common mistake is doing something like

subprocess.run('cd /tmp', shell=True)
subprocess.run('pwd', shell=True)  # Oops, doesn't print /tmp

The same thing will happen if the first subprocess tries to set an environment variable, which of course will have disappeared when you run another subprocess, etc.

A child process runs completely separate from Python, and when it finishes, Python has no idea what it did (apart from the vague indicators that it can infer from the exit status and output from the child process). A child generally cannot change the parent's environment; it cannot set a variable, change the working directory, or, in so many words, communicate with its parent without cooperation from the parent.

The immediate fix in this particular case is to run both commands in a single subprocess;

subprocess.run('cd /tmp; pwd', shell=True)

though obviously this particular use case isn't very useful; instead, use the cwd keyword argument, or simply os.chdir() before running the subprocess. Similarly, for setting a variable, you can manipulate the environment of the current process (and thus also its children) via

os.environ['foo'] = 'bar'

or pass an environment setting to a child process with

subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})

(not to mention the obvious refactoring subprocess.run(['echo', 'bar']); but echo is a poor example of something to run in a subprocess in the first place, of course).

Don't run Python from Python

This is slightly dubious advice; there are certainly situations where it does make sense or is even an absolute requirement to run the Python interpreter as a subprocess from a Python script. But very frequently, the correct approach is simply to import the other Python module into your calling script and call its functions directly.

If the other Python script is under your control, and it isn't a module, consider turning it into one. (This answer is too long already so I will not delve into details here.)

If you need parallelism, you can run Python functions in subprocesses with the multiprocessing module. There is also threading which runs multiple tasks in a single process (which is more lightweight and gives you more control, but also more constrained in that threads within a process are tightly coupled, and bound to a single GIL.)

Tyika answered 21/8, 2018 at 14:5 Comment(12)
For a more detailed exposition of how you might avoid calling Python as a subprocess, see this answer on a tangentially similar question.Tyika
it boggles my mind that I had to post a new answer to such a basic question in order to show how to run the command from the question idiomatically. Your answer is long but I don't see such example. Unrelated: avoid cargo-culting. If check_call() works in your case, use it. I had to fix a code that used run() blindly. Missing check=True caused a bug which would be avoided if check_call were used — "check" is in the name, you can't lose it —it is the correct default: don't ignore errors silently. I didn't read further.Bethink
@Bethink Thanks for the feedback, I was in fact planning to add a section about Bash vs sh but you beat me to it. I'm trying to spell out the specifics in enough detail to help beginners for whom these pitfalls are not obvious so that does get a bit longwinded. Yours should be quite sufficient otherwise; +1Tyika
Does stderr/stdout = subprocess.PIPE have a higher performance overhead than the default settings?Kronos
@Kronos I have not tested, but I don't see why it should. If you connect those pipes to something which does some processing, then of course that processing needs to oe accouted for; but it doesn't happen in the pipe itself. The default is to not capture stdout or stderr at all, i.e. whatever gets printed there is out of Python's visibility and control, just like with os.system().Tyika
In Python 3.8 (possibly earilier) you can now use capture_output=True as a replacement for stdout=subprocess.PIPE, stderr=subprocess.PIPE .Alvinia
This post should have been a blog article.Aviva
Are calls to subprocess.run()(which I assume calls to /bin/sh) logged somewhere?Presentationism
@Presentationism No, you'd have to arrange for that yourself. Some systems have extensive OS-level audit logs but that's not standard.Tyika
@Tyika Thanks. I specifically want to avoid logging. Say, if I'm passing some sensitive information like password to a commandline utility, I generally add a space before the command to avoid logging in bash history(or export the variable before hand and use "$password"). I wonder if any such precautions are necessary here in subprocess.run()Presentationism
@Presentationism If the kernel is configured to log every exec or even every system call, there isn't really any way to bypass that; but this type of facility is only used on high-security systems as far as I know. I only have some limited experience with GNU acct from many years ago, but ... not really a Python or shell topic anyway. Either way, nothing you can or want to do about this from a subprocess call.Tyika
For what it's worth, I'll repeat for emphasis that subprocess.run doesn't run a shell at all without shell=True; it simply (needs the first argument to be a list of strings and) passes its arguments to execve under the hood.Tyika
S
470

Don't use os.system. It has been deprecated in favor of subprocess. From the docs: "This module intends to replace several older modules and functions: os.system, os.spawn".

Like in your case:

import subprocess

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()
Sharpset answered 23/11, 2010 at 12:44 Comment(12)
This didn't do what I wanted when I needed to do a cd 'path\to\somewhere' followed by another bash command that needed to be run in that somewhere. @SharpsetPeale
you can easily just set the command like this "nano /home/you/path/a.txt" or whatever command you want, you can even you | &Penalty
@Peale If you need your subprocess to be run in a particular working directory, you can use the cwd argument to Popen: subprocess.Popen(..., cwd='path\to\somewhere')Futurity
For my command I needed shell=True as here; #18963285Pectoral
You really want to avoid shell=True if you can.Tyika
As recommended in the subprocess documentation, you actually also want to avoid subprocess.Popen() if one of the user-friendly wrappers does what you want. For Python 3.5+ your first preference should be subprocess.run() which replaces a set of legacy functions with somewhat haphazard names like subprocess.call(), subprocess.check_call(), and subprocess.check_output()Tyika
@Peale The real problem here is that each subprocess starts a separate subprocess. A shell will execute your cd and exit; then the new subprocess you run will know nothing about the context of the one which exited already. (Also, '\t' in a regular Python string represents a tab character. Use forward slashes, or a raw string if you need literal backslashes, or double the backslashes to escape them.)Tyika
@StevenC.Howell split() does the wrong thing with quoted strings etc; but for simple command lines without quoting, sure. Here, it won't work, because the redirection still requires a shell.Tyika
It's better use shlex.split() instead string.split() in this caseProximo
the code is wrong > file syntax (output redirection) needs a shell, split() tries to pass '>', 'file' as arguments to the cwm` program itself. Either use shell and pass the command as a string: subprocess.check_call(shell_command, shell=True) or run the command without a shell directly (default) and pass the command-line arguments as a list. You need to implement the redirection (and other shell features) in Python in this case e.g., with open('test.nt', 'wb', 0) as file: subprocess.check_call(['cwm', '--rdf', 'test.rdf', '--ntriples'], stdout=file)...Bethink
... (stdout=file redirects the output to a file in this case. It implements > file). It would be wrong to pass ..., '>', 'file'] in the last command expecting the redirection (it won't work without a shell and if you use a shell, you should pass the command as a string)Bethink
os.system is not deprecated. There are valid uses for itWad
T
56

Call it with subprocess

import subprocess
subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt")

The error you are getting seems to be because there is no swap module on the server, you should install swap on the server then run the script again

Terrigenous answered 23/11, 2010 at 12:43 Comment(9)
The swap module is obviously there, because running the command from the shell works.Berkie
Not on the server, when he runs it on the server there is an import error.Terrigenous
@mkn: "Then I just copied that output and did a copy paste into the terminal and hit enter and it works..." -- Did you try this on the server or on your machine?Berkie
Is it you are running this on a stand alone computer fine but its not working when you run it on your server? Or are you able to run it on a server terminal but not the server itselfTerrigenous
I'am able to run it on the server, meaning I run the command in the terminal of the server. But when I run it in a python script with os.system(theCommand) then it doesn't work. As I said, the weird thing is, that the same script runs on my local machine... Is there probably a difference with this os.system() command between Unix Server and my mac box?Woolridge
Perhaps the terminal you are using does not have the module installed. Grab the module for the terminal then run the script again with subprocess doing this should yield the results you want.Terrigenous
it is wrong If you don't use shell=True then you should use a list to pass multiple arguments i.e., use ['a', 'b', 'c'] instead of 'a b c'. Though a naive split won't work due to > file (shell redirection) in the command. More detailsBethink
Does not work for python 3.xBraud
This is the only command provided here that worked for me (including shell=True): subprocess.Popen(bash_command, shell=True)Stainless
H
30

It is possible you use the bash program, with the parameter -c for execute the commands:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])
Hayashi answered 20/8, 2015 at 3:48 Comment(3)
subprocess.check_output(bashCommand, shell=True) does the same thing. If your command is a static string, try parsing it into a list yourself and avoid the shell=True; though in this case you need the shell for the redirection anyway, or else you will need to refactor it to pure Python -- with open('test.nt', 'w') as dest: output = subprocess.check_output(['cwm' ,'--rdf', 'test.rdf', '--ntriples'], stdout=dest, shell=False)Tyika
@Tyika note: /bin/sh (used by subprocess) is not necessarily bash (you can't use bashisms). Though one could use executable='/bin/bash if desired. Here's a code exampleBethink
it is the first answer where the command should start successfully (the accepted and the 2nd popular answers are just wrong. A minor quibble: check_output() is useless here (the output is always empty due to the > file redirection; use check_call() instead.Bethink
C
23

You can use subprocess, but I always felt that it was not a 'Pythonic' way of doing it. So I created Sultan (shameless plug) that makes it easy to run command line functions.

https://github.com/aeroxis/sultan

Caloric answered 1/9, 2016 at 15:0 Comment(5)
Well done! Much cleaner and more intuitive than subprocess.Cheerio
This should honestly be adopted into the standard library.Closed
Is there a way to capture the output from the terminal using Sultan?Stability
Yes you can @Stability ... Here is the docs on how to do it: sultan.readthedocs.io/en/latest/…Caloric
@DavidDaniel hey david, thanks for the excellent lib. I was wondering if Sultan can help me with a use case here. I have 2 bash scripts and 1 shell script. I want to run them from within my python script - one at a time so the number isn't a problem. The scripts do have sudo calls in there which would require the user to enter their password (this is on osx btw but shell/bash function almost the same). What I want is a way to launch them with a terminal open where user can interact with it to enter sudo pwd. Is that possible? Any help is appreciated :)Hatpin
C
9

Also you can use 'os.popen'. Example:

import os

command = os.popen('ls -al')
print(command.read())
print(command.close())

Output:

total 16
drwxr-xr-x 2 root root 4096 ago 13 21:53 .
drwxr-xr-x 4 root root 4096 ago 13 01:50 ..
-rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py
-rw-r--r-- 1 root root   77 ago 13 21:53 test.py

None
Carnarvon answered 14/8, 2018 at 2:57 Comment(2)
The documentation contains a big red box: "Deprecated since version 2.6: This function is obsolete. Use the subprocess module."Tyika
In fairness, os.popen no longer has this warning, and is simply a thin wrapper around subprocess.Popen() now.Tyika
A
8

According to the error you are missing a package named swap on the server. This /usr/bin/cwm requires it. If you're on Ubuntu/Debian, install python-swap using aptitude.

Accost answered 23/11, 2010 at 12:44 Comment(4)
but it works when I run it directly in the terminal... so the swap must be there, not?Woolridge
there are two options. either it can't find swap or it shouldn't have imported it in the first place. can you import swap manually? does it work?Accost
hm I can't. If i start python with typing python in the terminal and then i type import swap then I got the error "ImportError: No module named swap". The weird thing is still that it works when I run the cwm command directly in the terminal of the serverWoolridge
Try printing sys.path where it's working and where it's not. Then try looking for the swap folder or swap.py in the folders printed. As Sven said, there may be a problem with those paths, and this will help you figure it out.Accost
B
7

To run the command without a shell, pass the command as a list and implement the redirection in Python using [subprocess]:

#!/usr/bin/env python
import subprocess

with open('test.nt', 'wb', 0) as file:
    subprocess.check_call("cwm --rdf test.rdf --ntriples".split(),
                          stdout=file)

Note: no > test.nt at the end. stdout=file implements the redirection.


To run the command using the shell in Python, pass the command as a string and enable shell=True:

#!/usr/bin/env python
import subprocess

subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt",
                      shell=True)

Here's the shell is responsible for the output redirection (> test.nt is in the command).


To run a bash command that uses bashisms, specify the bash executable explicitly e.g., to emulate bash process substitution:

#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')
Bethink answered 31/10, 2018 at 19:13 Comment(2)
Perhaps mention that .split() is not adequate when there are quoted strings etc. There is a separate routine shlex.split() which copes with arbitrarily complex shell syntax.Tyika
@Tyika the .split() works in this case. shlex.split() can be useful sometimes but it may fail in some cases too. There are great many things that could be mentioned. You could start with the link to the subprocess tag description provided above.Bethink
V
4

copy paste this:

def run_bash_command(cmd: str) -> Any:
    import subprocess

    process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
    output, error = process.communicate()
    if error:
        raise Exception(error)
    else:
        return output
Vow answered 23/11, 2010 at 12:38 Comment(0)
I
1

The pythonic way of doing this is using subprocess.Popen

subprocess.Popen takes a list where the first element is the command to be run followed by any command line arguments.

As an example:

import subprocess

args = ['echo', 'Hello!']
subprocess.Popen(args) // same as running `echo Hello!` on cmd line

args2 = ['echo', '-v', '"Hello Again"']
subprocess.Popen(args2) // same as running 'echo -v "Hello Again!"` on cmd line
Impermissible answered 31/3, 2018 at 18:20 Comment(2)
No, the last example is the same as running echo -v '"Hello Again!"' with single quotes around the double quotes.Tyika
Also, to correctly use subprocesss.Popen, you have to manage the resulting process object (at a minimum, perform a wait() to prevent it from turning into a zombie process).Tyika
L
1

subprocess.Popen() is prefered over os.system() as it offers more control and visibility. However, If you find subprocess.Popen() too verbose or complex, peasyshell is a small wrapper I wrote above it, which makes it easy to interact with bash from Python.

https://github.com/davidohana/peasyshell

Lidialidice answered 8/11, 2020 at 20:55 Comment(0)
P
0

I am big fan of https://github.com/amoffat/sh, and usually I would point it out. But this time I want to use a revamp of much simpler shell embedding called pshlib https://gitlab.com/ewiger/pshlib

Disclaimer: I just wrote that somewhat simpler library, which I would now illustrate by providing an alternative answer.

It allows following causal embedding of shell commands into your python code.

You can break single long bash lines into nested python like multi-line statements:

res = psh(
    'VAR=world',
    """
        echo This is
            a multiline hello
            $VAR!
""").output
print(res)
excepted = 'This is a multiline hello world!\n'
assert excepted == res

So for your answer you get:

def cwm(rdf_file="test.rdf", with_ntriples="--ntriples", output_file="test.nt"):
    res = psh(f"""
        cwm 
            --rdf {rdf_file}
            {with_ntriples}
            > {output_file}
    """).output
    print(res)

Piccolo answered 25/5, 2023 at 21:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.