-1

I have this bash command I want to run from Python2.7:

time ( s=172.20.16 ; for i in $(seq 1 254) ; do ( ping -n -c 1 -w 1 $s.$i 1>/dev/null 2>&1 && printf "%-16s %s\n" $s.$i responded ) & done ; wait ; echo ) 

I tried running it like this:

cmd = 'time ( s=172.20.16 ; for i in $(seq 1 254) ; do ( ping -n -c 1 -w 1 $s.$i 1>/dev/null 2>&1 && printf "%-16s %s\n" $s.$i responded ) & done ; wait ; echo )'

#1. subprocess.call(cmd.split())
#2. subprocess.call(cmd, shell=True)
#3. os.system(cmd)

But all returned /bin/sh: 1: Syntax error: word unexpected (expecting ")"), while running it from bash worked prefectly. I also tried adding a /bin/bash to the head of the command, but that didn't work.

When using os.system('bash "{}"'.format(cmd)) it didn't crash with the previous error, but the loop unfolded incorecctly (it printed 1..254 instead of using them as the IP suffix)

I managed to make it work by saving the command in a bash script and then calling the script from python, but I would rather do that directly. What is the problem here?

CIsForCookies
  • 12,097
  • 11
  • 59
  • 124
  • 1
    Your assignment to `cmd` isn't even valid Python. – chepner Mar 07 '19 at 16:52
  • `time` is a *bash* builtin. It's not guaranteed to be/do what you expect in other shells, particularly including `/bin/sh`. – Charles Duffy Mar 07 '19 at 16:54
  • @CharlesDuffy I also tried using /usr/bin/time after verifying this is the `time` I'm using with `which time` – CIsForCookies Mar 07 '19 at 16:55
  • @CIsForCookies, `/usr/bin/time` has different syntax. You can't pass it a compound command the way you can with the builtin. (You *could* use `['time', 'sh', '-c', cmd]`, on the other hand; that way you're passing it a single simple command, `sh`). – Charles Duffy Mar 07 '19 at 16:59
  • Also, `which time` is completely useless/meaningless in this context; `which` doesn't know that shell functions, builtins, or aliases even *exist*. If you want to know which implementation of `time` you're using in bash, you want `type time`, not `which time`. – Charles Duffy Mar 07 '19 at 17:01

3 Answers3

2

shell=True uses /bin/sh. /bin/sh is not bash.

Leaving all the problems with the shell script in place, but invoking it with bash, would look like the following:

cmd = 'time ( s=172.20.16 ; for i in $(seq 1 254) ; do ( ping -n -c 1 -w 1 $s.$i 1>/dev/null 2>&1 && printf "%-16s %s\n" $s.$i responded ) & done ; wait ; echo )'
subprocess.call(['bash', '-c', cmd])

Rewriting it to actually be a better shell script might instead look like:

cmd = r'''
time {
  s=172.20.16
  for ((i=1; i<=254; i++)); do
    { ping -n -c 1 -w 1 "$s.$i" >/dev/null 2>&1 && \
      printf "%-16s %s\n" "$s.$i" "responded"
    } &
  done
  wait
  echo
}
'''
subprocess.call(['bash', '-c', cmd])

Note that we're using { ...; }, not ( ... ), for grouping (thus avoiding more subshell creations than necessary); and that we're always quoting substitutions.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • I'm using your answer, but now I'm stuck with another thing... the output assigned to `x = subprocess.check_output(...)` is `'\n'` even though I get more lines of output, that somehow slip away from `x` – CIsForCookies Mar 07 '19 at 17:01
  • Are those lines really *output*, or are they on stderr? – Charles Duffy Mar 07 '19 at 17:02
  • ...if you want to combine those two streams, add `stderr=subprocess.STDOUT` as a separate keyword argument to `subprocess.call()`. – Charles Duffy Mar 07 '19 at 17:04
  • yeah... I just did it to check your previous comment. Of course the output I saw was actually stderr. Thanks!! – CIsForCookies Mar 07 '19 at 17:05
0

you are splitting by space to construct the array of commands/parameters for the suprocess call method; but notice that there are parameters that include spaces, so it should count as a single parameter, not two (ie this one: "%-16s %s\n")

AlejandroVD
  • 1,576
  • 19
  • 22
-1

Try using subprocess as per this link Running Bash commands in Python

import subprocess
subprocess.call("{}".format(cmd).split())
sin tribu
  • 318
  • 1
  • 3
  • 8
  • 1
    `split()` is a problem no matter whether you use `subprocess` or not. – Charles Duffy Mar 07 '19 at 16:54
  • Good to know. Care to elaborate? – sin tribu Mar 07 '19 at 17:08
  • 1
    Without `shell=True`, the expected format of the `args` parameter to `subprocess.call` is a list with one entry per argv value. Using `string.split()` to generate that list works for very simple cases like `ls hello.txt`, but it'll change `ls "Hello World.txt"` to `['ls', '"Hello', 'World.txt"']`. And for compound commands that require a shell to connect them, you have bugs even when using shell-syntax-aware replacements like `shlex.split()`, and really need to rewrite to either let a shell do the splitting, or to connect the processes in native Python. – Charles Duffy Mar 07 '19 at 17:27
  • That makes sense. Thank you. – sin tribu Mar 07 '19 at 17:44