Python Forum

Full Version: Using subprocess to execute complex command with many arguments
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I have a shell script that is as follows:

LOAD_RRD=sysmon.rrd
DISK_RRD=disk.rrd
DISKTEMP_RRD=disktemp.rrd

rrdtool graph load1h.png \
		-Y -u 1.1 -l 0 -L 5 -v "Load" -w 700 -h 200 -t "Load stats - `/bin/date`" \
        --start end-"1h" --x-grid MINUTE:1:MINUTE:5:MINUTE:10:0:%R \
		-c ARROW\#000000 \
		DEF:load1=$LOAD_RRD:load1:AVERAGE \
		DEF:load5=$LOAD_RRD:load5:AVERAGE \
		DEF:load15=$LOAD_RRD:load15:AVERAGE \
		LINE1:load1\#ff0000:"Load average 1 min" \
		LINE1:load5\#ff6600:"Load average 5 min" \
		LINE2:load15\#ffaa00:"Load average 15 min" \
		COMMENT:"	\j" \
		COMMENT:"\j" \
		COMMENT:"	" \
		GPRINT:load15:MIN:"Load 15 min minimum\: %lf" \
		GPRINT:load15:MAX:"Load 15 min maximum\: %lf" \
		GPRINT:load15:AVERAGE:"Load 15 min average\: %lf" \
		COMMENT:"	\j" \
		COMMENT:"	" \
		COMMENT:"	\j" >/dev/null;
I want to convert it to python using the subprocess module and run similar commands but with manipulating the internal variables and parameters.

I was thinking of copying the whole command in a single python string cmd_str then run
subprocess.run(cmd_str, shell=True)
But I'm not on the best way to escape all those internal quotes and other special symbols.
Any suggestions?
(Apr-26-2023, 01:47 PM)medatib531 Wrote: [ -> ]Any suggestions?
Yes you just pass a list of the command's arguments as strings
subprocess.run([ "rrdtool",  "graph", "load1h.png",
      "-Y",  "-u", "1.1", "-l", "0", "-L", "5", "-v", "Load", ...])
Also I see that there is a Python API to rrdtool. Why not use that?
But what is the correct way to escape the quotes that are originally in the shell command?
E.g. for "Load" should it be something like:
subprocess.run([ "rrdtool",  "graph", "load1h.png", "-Y",  "-u", "1.1", "-l", "0", "-L", "5", "-v", '"Load"', ...])
?
Also do I have to somehow escape the backslashes, dollar and percent symbols as well, or are they fine within double quotes in the list of strings?
https://docs.python.org/3/library/shlex....hlex.split

import shlex


my_command = """
rrdtool graph load1h.png \
        -Y -u 1.1 -l 0 -L 5 -v "Load" -w 700 -h 200 -t "Load stats - `/bin/date`" \
        --start end-"1h" --x-grid MINUTE:1:MINUTE:5:MINUTE:10:0:%R \
        -c ARROW\#000000 \
        DEF:load1=$LOAD_RRD:load1:AVERAGE \
        DEF:load5=$LOAD_RRD:load5:AVERAGE \
        DEF:load15=$LOAD_RRD:load15:AVERAGE \
        LINE1:load1\#ff0000:"Load average 1 min" \
        LINE1:load5\#ff6600:"Load average 5 min" \
        LINE2:load15\#ffaa00:"Load average 15 min" \
        COMMENT:"   \j" \
        COMMENT:"\j" \
        COMMENT:"   " \
        GPRINT:load15:MIN:"Load 15 min minimum\: %lf" \
        GPRINT:load15:MAX:"Load 15 min maximum\: %lf" \
        GPRINT:load15:AVERAGE:"Load 15 min average\: %lf" \
        COMMENT:"   \j" \
        COMMENT:"   " \
        COMMENT:"   \j" >/dev/null;
""".strip().replace("\\", "")


my_cmd = shlex.split(my_command)
print(my_cmd)
The last field ">/dev/null" is wrong, but I guess you still want to suppress stdout:
subprocess.run(my_cmd, stdout=subprocess.DEVNULL)
Just get rid of the >/dev/null
(Apr-26-2023, 02:27 PM)medatib531 Wrote: [ -> ]But what is the correct way to escape the quotes that are originally in the shell command?
I ran a command to show the arguments received by your program and I get this list, that you can pass to the subprocess.run command (the first argument is missing, it should be the program, 'rrdtool')
[ 'graph', 'load1h.png', '-Y', '-u', '1.1', '-l', '0', '-L', '5', '-v', 'Load',
 '-w', '700', '-h', '200', '-t', 'Load stats - mer. 26 avril 2023 17:11:35 CEST', 
'--start', 'end-1h', '--x-grid', 'MINUTE:1:MINUTE:5:MINUTE:10:0:%R', '-c', 'ARROW#000000', 
'DEF:load1=sysmon.rrd:load1:AVERAGE', 'DEF:load5=sysmon.rrd:load5:AVERAGE', 
'DEF:load15=sysmon.rrd:load15:AVERAGE', 'LINE1:load1#ff0000:Load average 1 min', 
'LINE1:load5#ff6600:Load average 5 min', 'LINE2:load15#ffaa00:Load average 15 min',
 'COMMENT:   \\j', 'COMMENT:\\j', 'COMMENT:   ', 'GPRINT:load15:MIN:Load 15 min minimum\\: %lf', 
'GPRINT:load15:MAX:Load 15 min maximum\\: %lf',
 'GPRINT:load15:AVERAGE:Load 15 min average\\: %lf', 'COMMENT:   \\j', 'COMMENT:   ', 'COMMENT:   \\j']
@DeaD_EyE 's method using the shlex module is not perfect because it doesn't take into account the string interpolation of the shell, for example the variables $LOAD_RRD, etc

By the way, here is my executable "echoargs" script to show arguments
#!/usr/bin/env python3
# echoargs script
import sys
 
if __name__ == '__main__':
    print(sys.argv)
If you run your shell script by removing > /dev/null and you replace rrdtool with echoargs , it will print the arguments to the console in the same way that they are received by the invoked command after the shell actually processed them.

Note that the shell ran /bin/date and replaced it in the result by today's date (in french because it is my locals). So you may need to work a little bit on this, but Python's datetime module should be invoked instead of the /bin/date command to get a more pythonic script.
Worked, thank you all!