flybonzai
flybonzai

Reputation: 3941

Subprocess command not finding files using ls command?

I am creating a program that will pull in a list of account numbers and then run an ls -lh command to find a file for each one. When I run my command on our Linux server without Python it pulls up the files no problem, but then when I do it through Python it says it can't find them.

import subprocess as sp
sp.call(['cd', input_dir])
for i, e in enumerate(piv_id_list):
    proc_out = sp.Popen(['ls', '-lh', '*CSV*APP*{0}.zip'.format(e)])
    proc_out_list.append(proc_out)
    print(proc_out)

Here is some sample output when I run the commands through the Python interpreter:

>>> ls: cannot access *CSV1000*APP*: No such file or directory

But through Linux the same command:

ls -lh *CSV*APP*

It returns the output like it should.

Upvotes: 6

Views: 2498

Answers (4)

bufh
bufh

Reputation: 3420

You should use cwd argument to Popen and shell=True, then communicate to get the output.

Your code would look like:

import subprocess as sp
for i, e in enumerate(piv_id_list):
    proc = sp.Popen(['ls', '-lh', '*CSV*APP*{0}.zip'.format(e)], cwd=input_dir, stdout=sp.PIPE, shell=True)
    proc_out_list.append(proc.communicate()[0])
    print(proc_out_list[-1])

But why are you making a subprocess instead of using the standard lib?

Edit

Like @tripleee said, it replace a few functions only. I think it's better to use builtins/stdlib when it's possible; in your case you "only" want to list files for a given pattern (glob) and show ordered (sorted) informations about their size (stat).

Using the stdlib make your code more portable; even if you don't care about Microsoft Windows portability, you might want to avoid running into surprises running your code on computer without GNU binutils (ie: Mac OS, BSD, ...).

You want to use the subprocess module for things that cannot (easily) be implemented in pure Python (ie.: encode videos with ffmpeg, change the user passowrd with passwd, escalating privileges with sudo, ...).

Upvotes: 2

das-g
das-g

Reputation: 9994

ls, as run through Python, is probably right: I guess there is no file called *CSV*APP* in the current directory. There is probably a file with a name that matches that glob pattern. But ls doesn't care about globs. What happens when you run the command on the shell, is that the shell expands the glob to matching filenames it can see in the current directory and these expanded names is what the shell passes to ls.

To get the same result in the shell as in Python (for demonstration, not because you'd want that), protect the arguments from glob expansion by single quotes:

ls -lh '*CVS*APP*'${e}'.zip'

But how can you get the shell's behavior in Python? You can use shell=True as some other answers suggest, but that is a slippery slope, as invoking an actual shell on your dynamically generated strings (maybe dependent on user input in a more complex application) can make you vulnerable to command injections and other nastiness.

Here, you only need one specific behavior of the shell, filename globbing. And Python happens to be able to do that all by itself:

import subprocess as sp
from glob import glob
sp.call(['cd', input_dir])
for i, e in enumerate(piv_id_list):
    proc_out = sp.Popen(['ls', '-lh', glob('*CSV*APP*{0}.zip'.format(e))])
    proc_out_list.append(proc_out)
    print(proc_out)

As JuniorCompressor has pointed out, this would still look in the wrong directory, because the cd would only affect the subprocess of the cd invocation, so let's fix that, too:

import subprocess as sp
from glob import glob

os.chdir(input_dir)
for i, e in enumerate(piv_id_list):
    proc_out = sp.Popen(['ls', '-lh', glob('*CSV*APP*{0}.zip'.format(e))])
    proc_out_list.append(proc_out)
    print(proc_out)
Note

You could probably use the slightly higher-level sp.check_output instead of the underlying sp.Popen directly.

Upvotes: 3

JuniorCompressor
JuniorCompressor

Reputation: 20025

This is because the shell does the replacement of wildcards with existing files that match the pattern. For example, if you have a.txt and b.txt, then ls *.txt will be expanded from the shell to ls a.txt b.txt. With your command you actually ask for ls to return info about a file containing an asterisk in its filename. Use the following if you want to verify:

sp.Popen(['bash', '-c', 'ls', '-lh', '*CSV*APP*{0}.zip'.format(e)])

Also you should use os.chdir to change the directory, since sp.call(['cd', input_dir]) changes the current directory for the new process you created and not the parent one.

Upvotes: 5

awmo
awmo

Reputation: 339

I believe you need to add shell=True as a parameter to Popen and replacing the list with one string:

proc_out = sp.Popen('ls -lh *CSV*APP*{0}.zip'.format(e), shell=True)

See here for more information and possible usage of glob: Python subprocess wildcard usage

Upvotes: 1

Related Questions