Execute bash-command with "at" (<<<) via python: syntax error, last token seen

679 Views Asked by At

I'm using a radio sender on my RPi to control some light-devices at home. I'm trying to implement a time control and had successfully used the program "at" in the past.

#!/usr/bin/python

import subprocess as sp
##### some code #####
sp.call(['at', varTime, '<<<', '\"sudo', './codesend', '111111\"'])

When I execute the program, i receive the

errmsg: syntax error. Last token seen: <

Garbled time

This codesnipped works fine with every command by itself (as long every parameter is from type string).

It's neccessary to call "at" in this way: at 18:25 <<< "sudo ./codesend 111111" to hold the command in the queue (viewable in "atq"), because sudo ./codesend 111111 | at 18:25 just executes the command directly and writes down the execution in "/var/mail/user".

My question ist, how can I avoid the syntax error. I'm using a lot of other packages in this program, so I have to stay with Python

I hope someone has a solution for this problem or can help to find my mistake. Many thanks in advance

1

There are 1 best solutions below

4
Charles Duffy On BEST ANSWER

Preface: Shared Code

Consider the following context to be part of both branches of this answer.

import subprocess as sp
try:
    from shlex import quote # Python 3
except ImportError:
    from pipes import quote # Python 2

# given the command you want to schedule, as an array...
cmd = ['sudo', './codesend', '111111']

# ...generate a safely shell-escaped string.
cmd_str = ' '.join(quote(x) for x in cmd))

Solution A: Feed Stdin In Python

<<< is shell syntax. It has no meaning to at, and it's completely normal and expected for at to reject it if given as a literal argument.

You don't need to invoke a shell, though -- you can do the same thing directly from native Python:

p = sp.Popen(['at', vartime], stdin=sp.PIPE)
p.communicate(cmd_str)

Solution B: Explicitly Invoke A Shell

Moreover, <<< isn't /bin/sh syntax -- it's an extension honored in bash, ksh, and others; so you can't reliably get it just by adding the shell=True flag (which uses /bin/sh and so guarantees only POSIX-baseline features). If you want it, you need to explicitly invoke a shell with the feature, like so:

bash_script = '''
at "$1" <<<"$2"
'''
sp.call(['bash', '-c', bash_script,
         '_',                      # this is $0 for that script
         vartime,                  # this is its $1
         cmd_str,                  # this is its $2
         ])

In either case, note that we're using shlex.quote() or pipes.quote() (as appropriate for our Python release) when generating a shell command from an argument list; this is critical to avoid creating shell injection vulnerabilities in our software.