1

I'm trying to run a for loop in a shell through python. os.popen runs it fine, but is deprecated on 3.x and I want the stderr. Following the highest-voted answer on How to use for loop in Subprocess.run command results in Syntax error: "do" unexpected, with which shellcheck concurs:

import subprocess
proc = subprocess.run(
    "bash for i in {1..3}; do echo ${i}; done",
    shell=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE, )

print(proc.stderr)

I'm ultimately trying to reset all usbs by calling this shell code https://unix.stackexchange.com/a/611305/362437 through python, so any alternate approaches to doing that would be appreciated too.

JoshuaF
  • 1,124
  • 2
  • 9
  • 23
  • 2
    There's no need to involve bash (or any shell, for that matter) to do what you have linked. Python is perfectly capable of writing ("``echo >``") to a file as well. – MisterMiyagi Mar 14 '22 at 18:15
  • @MisterMiyagi good to know, I'll go that route unless this gets an answer – JoshuaF Mar 14 '22 at 18:18
  • I find that ``os.popen`` is not deprecated: https://docs.python.org/3.9/library/os.html#os.popen – O. Salah Mar 14 '22 at 18:28
  • Of course, you don't need Bash for this simple example. `for i in 1 2 3` does the same thing in the Bourne shell. It's hard to think of situations where a shell loop would be preferable over a Python loop. – tripleee Mar 14 '22 at 18:35

1 Answers1

4

When you do

subprocess.run('foo', shell=True)

it actually runs the equivalent of

/bin/sh -c 'foo'

(except that it magically gets all quotes right :-) ). So, in your case, it executes

/bin/sh -c "bash for i in {1..3}; do echo ${i}; done"

So the "command" given with the -c switch is actually a list of three commands: bash for i in {1..3}, do echo ${i}, and done. This is going to leave you with a very confused shell.

The easiest way of fixing this is probably to remove that bash from the beginning of the string. That way, the command passed to /bin/sh makes some sense.

If you want to run bash explicitly, you're probably better off using shell=False and using a list for the first argument to preserve your quoting sanity. Something like

import subprocess
proc = subprocess.run(
    ['/bin/bash', '-c', 'for i in {1..3}; do echo ${i}; done'],
    shell=False,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE, )
Ture Pålsson
  • 6,088
  • 2
  • 12
  • 15