0

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

hlovdal
  • 23,353
  • 10
  • 78
  • 148
Gerdinho
  • 3
  • 1

1 Answers1

2

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.

Charles Duffy
  • 235,655
  • 34
  • 305
  • 356
  • your solution with `sp.Popen()` is working. Thank you very much :) – Gerdinho Jul 08 '18 at 18:49
  • Glad to help! If you no longer need additional answers, consider clicking the checkbox at the top of this one to accept it and mark your question solved. – Charles Duffy Jul 08 '18 at 19:36
  • A few things: first, you're trying to write a string to the subprocess's stdin, but it needs a bytestring. Second, it'd be simpler to pass the input to `communicate` instead of working with `p.stdin` manually, as well as safer when the input is large (avoiding cases where the pipe buffers get full and the processes deadlock). – user2357112 supports Monica Jul 08 '18 at 19:59
  • @user2357112, good call on passing stdin to `communicate()` -- and I'll admit to doing most of my work in Python 2, so I completely missed the bytestring distinction. – Charles Duffy Jul 08 '18 at 20:40