Piping output using subprocess.Popen and subprocess.PIPE [duplicate] - linux

I know how to run a command using cmd = subprocess.Popen and then subprocess.communicate.
Most of the time I use a string tokenized with shlex.split as 'argv' argument for Popen.
Example with "ls -l":
import subprocess
import shlex
print subprocess.Popen(shlex.split(r'ls -l'), stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE).communicate()[0]
However, pipes seem not to work... For instance, the following example returns noting:
import subprocess
import shlex
print subprocess.Popen(shlex.split(r'ls -l | sed "s/a/b/g"'), stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE).communicate()[0]
Can you tell me what I am doing wrong please?
Thx

I think you want to instantiate two separate Popen objects here, one for 'ls' and the other for 'sed'. You'll want to pass the first Popen object's stdout attribute as the stdin argument to the 2nd Popen object.
Example:
p1 = subprocess.Popen('ls ...', stdout=subprocess.PIPE)
p2 = subprocess.Popen('sed ...', stdin=p1.stdout, stdout=subprocess.PIPE)
print p2.communicate()
You can keep chaining this way if you have more commands:
p3 = subprocess.Popen('prog', stdin=p2.stdout, ...)
See the subprocess documentation for more info on how to work with subprocesses.

I've made a little function to help with the piping, hope it helps. It will chain Popens as needed.
from subprocess import Popen, PIPE
import shlex
def run(cmd):
"""Runs the given command locally and returns the output, err and exit_code."""
if "|" in cmd:
cmd_parts = cmd.split('|')
else:
cmd_parts = []
cmd_parts.append(cmd)
i = 0
p = {}
for cmd_part in cmd_parts:
cmd_part = cmd_part.strip()
if i == 0:
p[i]=Popen(shlex.split(cmd_part),stdin=None, stdout=PIPE, stderr=PIPE)
else:
p[i]=Popen(shlex.split(cmd_part),stdin=p[i-1].stdout, stdout=PIPE, stderr=PIPE)
i = i +1
(output, err) = p[i-1].communicate()
exit_code = p[0].wait()
return str(output), str(err), exit_code
output, err, exit_code = run("ls -lha /var/log | grep syslog | grep gz")
if exit_code != 0:
print "Output:"
print output
print "Error:"
print err
# Handle error here
else:
# Be happy :D
print output

shlex only splits up spaces according to the shell rules, but does not deal with pipes.
It should, however, work this way:
import subprocess
import shlex
sp_ls = subprocess.Popen(shlex.split(r'ls -l'), stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
sp_sed = subprocess.Popen(shlex.split(r'sed "s/a/b/g"'), stdin = sp_ls.stdout, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
sp_ls.stdin.close() # makes it similiar to /dev/null
output = sp_ls.communicate()[0] # which makes you ignore any errors.
print output
according to help(subprocess)'s
Replacing shell pipe line
-------------------------
output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]
HTH

"""
Why don't you use shell
"""
def output_shell(line):
try:
shell_command = Popen(line, stdout=PIPE, stderr=PIPE, shell=True)
except OSError:
return None
except ValueError:
return None
(output, err) = shell_command.communicate()
shell_command.wait()
if shell_command.returncode != 0:
print "Shell command failed to execute"
return None
return str(output)

Thank #hernvnc, #glglgl, and #Jacques Gaudin for the answers. I fixed the code from #hernvnc. His version will cause hanging in some scenarios.
import shlex
from subprocess import PIPE
from subprocess import Popen
def run(cmd, input=None):
"""Runs the given command locally and returns the output, err and exit_code."""
if "|" in cmd:
cmd_parts = cmd.split('|')
else:
cmd_parts = []
cmd_parts.append(cmd)
i = 0
p = {}
for cmd_part in cmd_parts:
cmd_part = cmd_part.strip()
if i == 0:
if input:
p[i]=Popen(shlex.split(cmd_part),stdin=PIPE, stdout=PIPE, stderr=PIPE)
else:
p[i]=Popen(shlex.split(cmd_part),stdin=None, stdout=PIPE, stderr=PIPE)
else:
p[i]=Popen(shlex.split(cmd_part),stdin=p[i-1].stdout, stdout=PIPE, stderr=PIPE)
i = i +1
# close the stdin explicitly, otherwise, the following case will hang.
if input:
p[0].stdin.write(input)
p[0].stdin.close()
(output, err) = p[i-1].communicate()
exit_code = p[0].wait()
return str(output), str(err), exit_code
# test case below
inp = b'[ CMServer State ]\n\nnode node_ip instance state\n--------------------------------------------\n1 linux172 10.90.56.172 1 Primary\n2 linux173 10.90.56.173 2 Standby\n3 linux174 10.90.56.174 3 Standby\n\n[ ETCD State ]\n\nnode node_ip instance state\n--------------------------------------------------\n1 linux172 10.90.56.172 7001 StateFollower\n2 linux173 10.90.56.173 7002 StateLeader\n3 linux174 10.90.56.174 7003 StateFollower\n\n[ Cluster State ]\n\ncluster_state : Normal\nredistributing : No\nbalanced : No\ncurrent_az : AZ_ALL\n\n[ Datanode State ]\n\nnode node_ip instance state | node node_ip instance state | node node_ip instance state\n------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n1 linux172 10.90.56.172 6001 P Standby Normal | 2 linux173 10.90.56.173 6002 S Primary Normal | 3 linux174 10.90.56.174 6003 S Standby Normal'
cmd = "grep -E 'Primary' | tail -1 | awk '{print $3}'"
run(cmd, input=inp)

Related

subprocess.run().stdout.read() returns CompletedProcess not a string

I'm using subprocess to get the output of the command line bitwarden tool to redirect it to albert (a launcher for linux). I'm using:
returned_user = subprocess.run(["bw", "get", "username", query, "--raw", "--session", session_key], text=True, stdout=subprocess.PIPE, shell=True, check=True).stdout.read()
Checking type(returned_user) gives CompletedProcess. How do I get the stdout as a string? subprocess.check_output returns a CompletedProcess too.
Everything done in Python 3.9.1.
referring to https://docs.python.org/3/library/subprocess.html, you missed
capture_output=True in the parameters.
i use python 3.7.3 (on my beagelbone):
just try on the python shell:
>>> import subprocess
>>> command = "echo 123" # your command here.
>>> result = subprocess.run(command, text=True, shell=True, check=True, capture_output=True)
>>> user = 'unknown'
>>> print (user)
unknown
>>> if result.returncode == 0:
... user = result.stdout.strip()
...
>>> print (user)
123
>>>
Best Regards,
Bernd

Piping bash commands using Python subprocess.call - Syntax error: "|" unexpected

I was testing pipe using subprocess.call and I came to the following problem.
For example an automated ssh using users as strings and as a text file.
The pipe works perfectly using strings (Example 1) but it fails when opening a text file to check the users (Example 2).
Is there a limitation when piping values from a file?
Example 1:
$ python3 script.py myhost
import subprocess
import sys
host = sys.argv[1]
for j in range(0, 2):
words = ['test1', 'test2']
user = words[j]
print(user)
print('Loggin in %s...' % repr(user))
subp = subprocess.call(['echo %s | ssh %s' % (user, host)],
shell=True, stdout=None, stderr=None)
Output:
test1
Trying 'test1'...
Logged in
test2
Trying 'test2'...
Logged in
Example 2:
$ python3 script.py myhost users.txt
import subprocess
import sys
host = sys.argv[1]
user_list = sys.argv[2]
with open(user_list) as usr:
user = usr.readline()
cnt = 0
while user:
print(user.strip())
user = usr.readline()
subp = subprocess.call(['echo %s | ssh %s' % (user, host)],
shell=True, stdout=None, stderr=None)
cnt += 1
Output:
test1
/bin/sh: 2: Syntax error: "|" unexpected
test2
/bin/sh: 2: Syntax error: "|" unexpected
In case someone stumbles into this, I ended up fixing by getting rid of the first readline, adding a strip and modifying the while condition:
with open(user_list) as usr:
cnt = 0
while usr:
line = usr.readline()
user = line.strip()
subp = subprocess.call(['echo %s | ssh %s' % (user, host)],
shell=True, stdout=None, stderr=None)
cnt += 1

resolve 'Can't export GPIO problem on Rpi using python script

I am writing a python application which is programming and testing atmel microcontrollers through a SPI port.
The application is running on RaspberryPi model 3B+ and I use the command line application 'avrdude' to do the job. I use subprocess.Popen() from within my python script and in general this runs just fine.
Sometimes, the SPI port gets in a blocked state. The avrdude application then typically reports something like 'Can't export GPIO 8, already exported/busy?: Device or resource busy'
One can observe the exported GPIO's by:
pi#LeptestPost:/ $ ls /sys/class/gpio/
export gpio10 gpio11 gpio8 gpiochip0 gpiochip504 unexport
I get out of this situation by invoking:
pi#LeptestPost:/ $ sudo echo 8 > /sys/class/gpio/unexport
resulting in:
pi#LeptestPost:/ $ ls /sys/class/gpio/
export gpio10 gpio11 gpiochip0 gpiochip504 unexport
So I can unexport them all and move on manually but I would like to have this automated in the application with the following code (after detecting the error in the avrdude output):
args = ['sudo', 'echo', '8', '>', '/sys/class/gpio/unexport']
result, error = self.runCommand(args, wait=True)
def runCommand(self, args, wait = False, outputFileStr = "", errorFileStr = "", workingDir = ""):
# documentation:
#class subprocess.Popen(args,
# bufsize=-1,
# executable=None,
# stdin=None,
# stdout=None,
# stderr=None,
# preexec_fn=None,
# close_fds=True,
# shell=False,
# cwd=None,
# env=None,
# universal_newlines=None,
# startupinfo=None,
# creationflags=0,
# restore_signals=True,
# start_new_session=False,
# pass_fds=(),
# *,
# encoding=None,
# errors=None,
# text=None)
print("Working on executing command " + str(args))
if (outputFileStr != ""):
stdoutFile = open(outputFileStr,'w')
else:
stdoutFile = None
if (errorFileStr != ""):
stderrFile = open(errorFileStr,'w')
else:
stderrFile = None
if (workingDir != ""):
cwdStr = workingDir
else:
cwdStr = None
try:
if (wait):
p = subprocess.Popen(args, stdout = subprocess.PIPE, cwd = cwdStr)
print("started subprocess with PID " + str(p.pid))
p.wait() # Wait for child process to terminate, This will deadlock when using stdout=PIPE or stderr=PIPE
else:
#p = subprocess.Popen(args, stdin = None, stdout = None, stderr = None, close_fds = True)
p = subprocess.Popen(args, stdin = None, stdout = stdoutFile, stderr = stderrFile, close_fds = True, cwd = cwdStr)
print("started subprocess with PID " + str(p.pid))
(result, error) = p.communicate(timeout=15) # Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached.
except subprocess.CalledProcessError as e:
sys.stderr.write("common::run_command() : [ERROR]: output = %s, error code = %s\n" % (e.output, e.returncode))
return e.output, e.returncode
except FileNotFoundError as e:
self.master.printERROR("common::run_command() : [ERROR]: output = %s, error code = " + str(e) + "\n")
return "error", str(e)
except subprocess.TimeoutExpired as e:
self.master.printERROR("Process timeout on PID "+ str(p.pid) + ", trying to kill \n")
p.kill()
outs, errs = p.communicate()
return "error", str(e)
if (outputFileStr != ""):
stdoutFile.close()
if (errorFileStr != ""):
stderrFile.close()
return result, error
But that does not do the job (no error, not the wanted result). I can imagine it's related to how the process is started within its shell or environment - but that's beyond my knowledge.
Any idea how to get this working?
Note: the avrdude application is also called using the 'runcommand' method and running fine.

python3 multiprocessing.Process approach fails

I saw somewhere a hint on how to process a large dataset (say lines of text) faster with the multiprocessing module, something like:
... (form batch_set = nump batches [= lists of lines to process], batch_set
is a list of lists of strings (batches))
nump = len(batch_set)
output = mp.Queue()
processes = [mp.Process(target=proc_lines, args=(i, output, batch_set[i])) for i in range(nump)]
for p in processes:
p.start()
for p in processes:
p.join()
results = sorted([output.get() for p in processes])
... (do something with the processed outputs, ex print them in order,
given that each proc_lines function returns a couple (i, out_batch))
However, when i run the code with a small number of lines/batch it works fine
[ex: './code.py -x 4:10' for nump=4 and numb=10 (lines/batch)] while after a
certain number of lines/batch is hangs [ex: './code.py -x 4:4000'] and when i
interrupt it i see a traceback hint about a _wait_for_tstate_lock and the system
threading library. It seems that the code does not reach the last shown code
line above...
I provide the code below, in case somebody needs it to answer why this is
happening and how to fix it.
#!/usr/bin/env python3
import sys
import multiprocessing as mp
def fabl(numb, nump):
'''
Form And Batch Lines: form nump[roc] groups of numb[atch] indexed lines
'<idx> my line here' with indexes from 1 to (nump x numb).
'''
ret = []
idx = 1
for _ in range(nump):
cb = []
for _ in range(numb):
cb.append('%07d my line here' % idx)
idx += 1
ret.append(cb)
return ret
def proc_lines(i, output, rows_in):
ret = []
for row in rows_in:
row = row[0:8] + 'some other stuff\n' # replacement for the post-idx part
ret.append(row)
output.put((i,ret))
return
def mp_proc(batch_set):
'given the batch, disperse it to the number of processes and ret the results'
nump = len(batch_set)
output = mp.Queue()
processes = [mp.Process(target=proc_lines, args=(i, output, batch_set[i])) for i in range(nump)]
for p in processes:
p.start()
for p in processes:
p.join()
print('waiting for procs to complete...')
results = sorted([output.get() for p in processes])
return results
def write_set(proc_batch_set, fout):
'write p[rocessed]batch_set'
for _, out_batch in proc_batch_set:
for row in out_batch:
fout.write(row)
return
def main():
args = sys.argv
if len(args) < 2:
print('''
run with args: -x [ NumProc:BatchSize ]
( ex: '-x' | '-x 4:10' (default values) | '-x 4:4000' (hangs...) )
''')
sys.exit(0)
numb = 10 # suppose we need this number of lines/batch : BatchSize
nump = 4 # number of processes to use. : NumProcs
if len(args) > 2 and ':' in args[2]: # use another np:bs
nump, numb = map(int, args[2].split(':'))
batch_set = fabl(numb, nump) # proc-batch made in here: nump (groups) x numb (lines)
proc_batch_set = mp_proc(batch_set)
with open('out-min', 'wt') as fout:
write_set(proc_batch_set, fout)
return
if __name__ == '__main__':
main()
The Queue have a certain capacity and can get full if you do not empty it while the Process are running. This does not block the execution of your processes but you won't be able to join the Process if the put did not complete.
So I would just modify the mp_proc function such that:
def mp_proc(batch_set):
'given the batch, disperse it to the number of processes and ret the results'
n_process = len(batch_set)
output = mp.Queue()
processes = [mp.Process(target=proc_lines, args=(i, output, batch_set[i]))
for i in range(process)]
for p in processes:
p.start()
# Empty the queue while the processes are running so there is no
# issue with uncomplete `put` operations.
results = sorted([output.get() for p in processes])
# Join the process to make sure everything finished correctly
for p in processes:
p.join()
return results

passing UTF-8 through a python script from a subprocess

In a python3 script, I setup UTF-8 output with:
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
I then launch a subprocess with subprocess.Popen, and scan the results, trying to print a subset. The output is \x-escaped when it contains interesting characters. This is not what I want, I just want the UTF-8. The unwanted output is at the bottom.
process = subprocess.Popen([MVN, "-Ptrain-model"],
cwd=JPN_MODELS_DIR, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print_interesting_info(process.stdout)
def print_interesting_info(out):
TRAINING_FLAG = False
for buffer_line in out:
#line = str(buffer_line, "utf8").strip()
line = str(buffer_line).strip()
if ("ERROR" in line):
print(line)
Unwanted output:
b'[ERROR] next segment length 109 > LONGEST_WORD input \xef\xbc\xa1\xef\xbc\x8e\xef\xbc\xa1\xef\xbd\x92\xef\xbd\x86\xef\xbd\x8f\xef\xbd\x8c\xef\xbd\x84\xef\xbd\x89\xef\xbc\x8c\xef\xbc\xa4\xef\xbd\x89\xef\xbd\x85\xef\xbc\xab\xef\xbd\x8f\xef\xbd\x8e\xef\xbd\x94\xef\xbd\x8f\xef\xbd\x92\xef\xbd\x8e\xef\xbd\x89\xef\xbd\x81\xef\xbd\x94\xef\xbd\x85\xef\xbd\x8e\xef\xbc\x8c\xef\xbd\x85\xef\xbd\x89\xef\xbd\x8e\xef\xbd\x96\xef\xbd\x85\xef\xbd\x92\xef\xbd\x8b\xef\xbd\x81\xef\xbd\x8e\xef\xbd\x8e\xef\xbd\x94\xef\xbd\x85\xef\xbd\x93\xef\xbc\xb0\xef\xbd\x92\xef\xbd\x8f\xef\xbd\x90\xef\xbd\x81\xef\xbd\x87\xef\xbd\x81\xef\xbd\x8e\xef\xbd\x84\xef\xbd\x81\xef\xbd\x8d\xef\xbd\x89\xef\xbd\x94\xef\xbd\x94\xef\xbd\x85\xef\xbd\x8c\xef\xbd\x84\xef\xbd\x85\xef\xbd\x92\xef\xbd\x93\xef\xbd\x94\xef\xbd\x81\xef\xbd\x84\xef\xbd\x94\xef\xbd\x92\xef\xbd\x8f\xef\xbd\x8d\xef\xbd\x89\xef\xbd\x93\xef\xbd\x83\xef\xbd\x88\xef\xbd\x85\xef\xbd\x8e\xef\xbd\x88\xef\xbd\x85\xef\xbd\x89\xef\xbd\x84\xef\xbd\x8e\xef\xbd\x89\xef\xbd\x93\xef\xbd\x83\xef\xbd\x88\xef\xbd\x85\xef\xbd\x8e\xef\xbc\xa1\xef\xbd\x92\xef\xbd\x89\xef\xbd\x93\xef\xbd\x94\xef\xbd\x8f\xef\xbd\x8b\xef\xbd\x92\xef\xbd\x81\xef\xbd\x94\xef\xbd\x89\xef\xbd\x85\xef\xbd\x89\xef\xbd\x8e\xef\xbd\x89\xef\xbd\x88\xef\xbd\x92\xef\xbd\x85\xef\xbd\x8d\xef\xbc\xab\xef\xbd\x81\xef\xbd\x8d\xef\xbd\x90\xef\xbd\x86\xef\xbd\x87\xef\xbd\x85\xef\xbd\x87\xef\xbd\x85\xef\xbd\x8e\xef\xbd\x84\xef\xbd\x81\xef\xbd\x93\xef\xbd\x83\xef\xbd\x88\xef\xbd\x92\xef\xbd\x89\xef\xbd\x93\xef\xbd\x94\xef\xbd\x8c\xef\xbd\x89\xef\xbd\x83\xef\xbd\x88\xef\xbd\x85\xef\xbc\xab\xef\xbd\x81\xef\xbd\x89\xef\xbd\x93\xef\xbd\x85\xef\xbd\x92\xef\xbd\x94\xef\xbd\x95\xef\xbd\x8d\xef\xbc\x8c\xef\xbc\xa2\xef\xbd\x95\xef\xbd\x84\xef\xbd\x81\xef\xbd\x90\xef\xbd\x85\xef\xbd\x93\xef\xbd\x94\xef\xbc\x8c\xef\xbc\x91\xef\xbc\x99\xef\xbc\x94\xef\xbc\x93\xef\xbc\x91\xef\xbc\x99\xef\xbc\x94\xef\xbc\x93\xef\xbc\x91\xef\xbc\x99\xef\xbc\x94\xef\xbc\x93\xef\xbc\x91\xef\xbc\x99\xef\xbc\x94\xef\xbc\x93\xe5\x8f\x82\xe7\x85\xa7\xe3\x80\x82\n'
process.stdout is an io.BufferedReader. It reads bytes, and bytes.__str__ is basically a repr of the byte string. You can wrap it in an io.TextIOWrapper:
import io
print_interesting_info(out):
TRAINING_FLAG = False
out = io.TextIOWrapper(out, 'utf-8')
for line in out:
line = line.strip()
if "ERROR" in line:
print(line)

Resources