1

I need to do this:

paste file1 file2 file3 > result

I have the following in my python script:

from subprocess import call

// other code here.

// Here is how I call the shell command

call ["paste", "file1", "file2", "file3", ">", "result"])

Unfortunately I get this error:

paste: >: No such file or directory.

Any help with this will be great!

2
  • Redirection is never part of the command. Commented Feb 12, 2015 at 3:37
  • It is easy to emulate paste command in Python e.g.,: for lines in izip(*files): print "\t".join(map(str.strip, lines)) Commented Feb 12, 2015 at 16:31

3 Answers 3

5

You need to implement the redirection yourself, if you're wisely deciding not to use a shell.

The docs at https://docs.python.org/2/library/subprocess.html warn you not to use a pipe -- but, you don't need to:

import subprocess
with open('result', 'w') as out:
    subprocess.call(["paste", "file1", "file2", "file3"], stdout=out)

should be just fine.

Sign up to request clarification or add additional context in comments.

7 Comments

I find that the shlex.split() function (in the standard libraries) is useful to make more readable code while using the subprocess module. So I'd use call(shlex.split('paste this that'), stdout=out) ... for example.
@JimDennis actually, with no quoting &c needed, an even simpler 'paste this that'.split() would be fine too:-). But some people prefer to write lists out explicitly, and it's fine to do so if that's somebody's preferred style.
Yes, with that example. As said the code can be considerably more readable and easier to maintain if the commands are rendered in the code as one would normally see them (quoted, etc) on a command line and then split as the shell would (shlex.split). Manually splitting them into list literals in the source code is a bit error prone and, in my view, harder to read.
@JimDennis, I may have sweated more blood and tears fighting shell quoting and escaping than on most other broken-as-designed SW issues, so managing to carefully reproduce the same hell when I don't have to doesn't strike me as the best way to invest my time:-).
@JimDennis: shlex.split() may produce a wrong output e.g., shlex.split(r'"\$x"') -> ['\\$x'] but it should be ['$x'] instead -- pass a list to avoid subtle bugs instead of using shlex.split().
|
5

There are two approaches to this.

  1. Use shell=True:

    call("paste file1 file2 file3 >result", shell=True)
    

    Redirection, >, is a shell feature. Consequently, you can only access it when using a shell: shell=True.

  2. Keep shell=False and use python to perform the redirection:

    with open('results', 'w') as f:
        subprocess.call(["paste", "file1", "file2", "file3"], stdout=f)
    

The second is normally preferred as it avoids the vagaries of the shell.

Discussion

When the shell is not used, > is just another character on the command line. Thus, consider the error message:

paste: >: No such file or directory. 

This indicates that paste had received > as an argument and was trying to open a file by that name. No such file exists. Therefore the message.

As the shell command line, one can create a file by that name:

touch '>'

If such a file had existed, paste, when called by subprocess with shell=False, would have used that file for input.

2 Comments

I had written a similar answer before stackoverflow.com/questions/26831277/… and was adapting it to this case as you commented.
0

If you don't mind adding an additional dependency in your code base you might consider installing the sh Python module (from PyPI:sh using pip, of course).

This is a rather clever wrapper around Python's subprocess module's functionality. Using sh your code would look something like:

#!/usr/bin/python
from sh import paste
paste('file1', 'file2', 'file3', _out='result')

... although I think you'd want some exception handling around that so you could use something like:

#!/usr/bin/python
from __future__ import print_function
import sys
from sh import paste
from tempfile import TemporaryFile
with tempfile.TemporaryFile() as err:
    try:
        paste('file1', 'file2', 'file3', _out='result', _err=err)
    except (EnvironmentError, sh.ErrorReturnCode) as e:
        err.seek(0)
        print("Caught Error: %s" % err.read(), file=sys.stderr)

sh makes such things almost trivially easy although there are some tricks as you get more advanced. You also have to note the difference between _out= and other keyword arguments of that form, vs. sh's magic for most other keyword arguments.

All that sh magic make confuse anyone else who ever reads your code. You might also find that using Python modules with sh code interlaced into it makes you complacent about portability issues. Python code is generally fairly portable while Unix command line utilities can vary considerably from one OS to another and even from one Linux distribution or version to another. Having lots of shell utilities interlaced with your Python code in such a transparent way may make that problem less visible.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.