Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to chain multiple command line responses in Python?

I've been trying to run a script within another python script with no luck.

The script I am trying to run is garminbackup.py which can be found in the following repo.

https://github.com/petergardfjall/garminexport

When you run this script from the command line it asks you for some information, username, location to save files, ect...

command = 'python garminbackup.py --backup-dir=Name email --format tcx'

It then asks for a password, and once entered it starts to download the garmin files to the directory provided. My goal is to create a program which loops through multiple accounts and updates the data in each folder.

My issue is that I cannot seem to get the subprocess module in python to performance this.

I've tried the following command with no luck. It seems to get stuck on the enter password input screen and does not do anything else.

import subprocess,shlex
from subprocess import PIPE,call
import os

p = subprocess.Popen(command,stdout=subprocess.PIPE,stdin=subprocess.PIPE,shell=True)
p.stdin.write("password\n")
p.wait()

I've searched for hours with little luck. Any help is appreciated.

like image 714
Michael Bawol Avatar asked Feb 27 '18 17:02

Michael Bawol


People also ask

How do you run multiple commands in Python?

If you want to execute many commands one after the other in the same session/shell, you must start a shell and feed it with all the commands, one at a time followed by a new line, and close the pipe at the end.

Can you run commands in Python?

Python allows you to execute shell commands, which you can use to start other programs or better manage shell scripts that you use for automation. Depending on our use case, we can use os. system() , subprocess. run() or subprocess.


1 Answers

Although the OP solved it by using the --password option, others might stumble upon the same problem and as already pointed out by HuStmpHrr it is not a good idea to pass a password in the command line.

Why it did not work

The called script, in this case garminbackup.py uses getpass. As hinted in the documentation the implementaion of getpass is more complecated than just reading the stdin:

[...] On Unix, the prompt is written to the file-like object stream. stream defaults to the controlling terminal (/dev/tty) or if that is unavailable to sys.stderr (this argument is ignored on Windows).

If echo free input is unavailable getpass() falls back to printing a warning message to stream and reading from sys.stdin and issuing a GetPassWarning.

How it should work

We could emulate a dummy terminal, but that is to complicated without a library (see next chapter).

Another way is to get rid of the information that there is a terminal ("If echo free input is unavailable"). This turned out harder than I expected, normally running a command as cat | command | cat should get rid of the tty detection, but it seems getpass is either to clever or to ignorant to change its behaviour.

The better way: pexpect

As already mentioned by LohmarASHAR, if you can use pexpect (pip install pexpect) you should. It is made for exactly this and will cover all the corner cases.

For example:

child = pexpect.spawn('scp foo [email protected]:.')
child.expect('Password:')
child.sendline(mypassword)

This works even for commands that ask for passwords or other input outside of the normal stdio streams. For example, ssh reads input directly from the TTY device which bypasses stdin.

To stick with the OP's example, but using pexpect:

#!/usr/bin/env python

import pexpect

command = 'python garminbackup.py --backup-dir=Name email --format tcx'

p = pexpect.spawn(command)  # instead of subprocess.Popen
p.expect(":")               # This is waiting for the prompt "Enter password:", but I rather only use ":" to cope with internationalisation issues
p.sendline("password")
print p.read()              # Output the sub-processes output 
p.wait()
like image 82
Samuel Kirschner Avatar answered Sep 21 '22 10:09

Samuel Kirschner