2

I've created an expect script that, when executed, ssh's onto a server and executes a series of commands. Pseudocode looks like this:

#!/usr/bin/expect
spawn ssh usr@myip
expect "password:"
send "mypassword\n";
send "./mycommand1\r"
send "./mycommand2\r"
interact

When executed from a bash shell ($ ./myscript.txt) the code executes fine. What I would now like to do is have a line in python file that runs the commands in the script the same way the bash shell does. Pseudocode looks like this:

import subprocess
def runmyscript():
    subprocess.call("myscript.txt", executable="expect", shell=True)
def main():
    run = runmyscript():
if __name__ == '__main__': main()   

I have placed the myscript.txt script file in the same directory as my runmyscript.py file, yet when I run the python file I receive the error:

WindowsError: [Error 2] The system cannot find the file specified

I've read through the documentation on the python.org site, but to no avail. Does anyone have a cunning solution for executing bash scripts from within .py code?

SOLUTION: this code works for me.

child = subprocess.Popen(['bash', '-c', './myscript.txt'], stdout = subprocess.PIPE)

Used this code to call an Expect file to ssh and send commands to server from .py file - useful solution if you are having trouble getting pycrypto/paramiko built onto your machine.

gortron
  • 147
  • 1
  • 3
  • 10
  • 1
    Well, you could run /usr/bin/expect myscript.txt, but you could also write the whole expect script in python (which I'd recommend) – MiJyn Jun 21 '12 at 22:15
  • 1
    See also http://stackoverflow.com/questions/1233655/what-is-the-simplest-way-to-ssh-using-python – krlmlr Jun 21 '12 at 22:17
  • lkjoel - how would I rewrite my command lines in myscript.txt from python? Writing the expect script in python would be certainly be preferable - would make it easier to add commands. user946850's link is useful for the ssh section. – gortron Jun 21 '12 at 22:24
  • For that matter, what python command could 'run /usr/bin/expect myscript.txt'? Thanks for all of your help. – gortron Jun 21 '12 at 22:26
  • @gortron - you could write it in python using the pexepect module. Even better would be to use paramiko in your case. Also, that script is not bash at all. – jordanm Jun 21 '12 at 22:30
  • Couldn't seem to make an edit to remove the colon, but the line should probably not have one as in "run = runmyscript()" – Steven Almeroth Jun 21 '12 at 23:04
  • For posterity: @jordanm's solution is brilliant if you're running on a Linux platform - the pexpect module implements expect commands into python code. You can get more info on it here http://www.noah.org/wiki/pexpect - if you're in windows, you're SOL. – gortron Jun 21 '12 at 23:08
  • A "bash expect script" -- which is it? Is it an expect script or a bash script? Those are two different languages. – Bryan Oakley Jun 22 '12 at 21:18

2 Answers2

3

Here is a python implementation of your expect script:

import paramiko

user = "user"
pass = "pass"
host = "host"

client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, port=22, username=user, password=pass)
client.exec_command("./mycommand1")
client.exec_command("./mycommand2")
client.close()
jordanm
  • 33,009
  • 7
  • 61
  • 76
  • Paramiko has proven very difficult to install on my Python build. I'll keep striving for the subprocess solution and post it here when I find it. – gortron Jun 21 '12 at 23:56
1

You can use pexpect (http://www.noah.org/wiki/pexpect)

Here is an example function that handles quite a few cases you can run into when executing commands remotely over ssh.

import pexpect 

## Cleanly handle a variety of scenarios that can occur when ssh or scp-ing to an ip:port
# amongst them are:
# 
# (1) key has not been setup
# (2) key has changed since last time
# (3) command was executed (check exit status and output) 
#
# @param cmdLine  The "scp" or "ssh" command-line
# @param mtimeout The millisecond timeout to wait for the child process to return
# @param log      The log to record events to if necessary
def cleanlyHandleSecureCmd(cmdLine, mtimeout = None, log = None):
  status = -1
  output = None

  if mtimeout == None:
    mtimeout = 60 * 1000

  if cmdLine != None and ('scp' in cmdLine or 'ssh' in cmdLine):
    # Scenarios for ssh include: (1) key not setup (2) key changed (3) remote cmd was executed (check exit status)
    scenarios = ['Are you sure you want to continue connecting', '@@@@@@@@@@@@', EOF]
    child     = spawn(cmdLine, timeout = mtimeout)
    scenario  = child.expect(scenarios)

    if scenario == 0:
      # (1) key not setup ==> say 'yes' and allow child process to continue
      child.sendline('yes')

      scenario = child.expect(scenarios)

    if scenario == 1:
      if log != None:
        # (2) key changed ==> warn the user in the log that this was encountered
        log.write('WARNING (' + cmdLine  + '): ssh command encountered man-in-the-middle scenario! Please investigate.')

      lines    = child.readlines()
      scenario = child.expect([EOF])

      child.close()
    else:
      # (3) remote cmd was executed ==> check the exit status and log any errors
      child.close()

      status = child.exitstatus
      output = child.before
      output = sub('\r\n', '\n', output)  # Do not be pedantic about end-of-line chars 
      output = sub('\n$',  '',   output)  # Ignore any trailing newline that is present

      if status == None:
        status = child.status

      if status != 0 and log != None:
        log.error('Error executing command \'' + str(cmdLine) + '\' gave status of ' + str(status) + ' and output: ' + str(output))
  else:
    if log != None:
      log.error('Command-line must contain either ssh or scp: ' + str(cmdLine))

  return (status, output)
Greg Smethells
  • 410
  • 4
  • 17