56
def exec_command(self, command, bufsize=-1):
    #print "Executing Command: "+command
    chan = self._transport.open_session()
    chan.exec_command(command)
    stdin = chan.makefile('wb', bufsize)
    stdout = chan.makefile('rb', bufsize)
    stderr = chan.makefile_stderr('rb', bufsize)
    return stdin, stdout, stderr

When executing a command in paramiko, it always resets the session when you run exec_command. I want to able to execute sudo or su and still have those privileges when I run another exec_command. Another example would be trying to exec_command("cd /") and then run exec_command again and have it be in the root directory. I know you can do something like exec_command("cd /; ls -l"), but I need to do it in separate function calls.

1

9 Answers 9

52

Non-Interactive use cases

This is a non-interactive example... it sends cd tmp, ls and then exit.

import sys
sys.stderr = open('/dev/null')       # Silence silly warnings from paramiko
import paramiko as pm
sys.stderr = sys.__stderr__
import os

class AllowAllKeys(pm.MissingHostKeyPolicy):
    def missing_host_key(self, client, hostname, key):
        return

HOST = '127.0.0.1'
USER = ''
PASSWORD = ''

client = pm.SSHClient()
client.load_system_host_keys()
client.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
client.set_missing_host_key_policy(AllowAllKeys())
client.connect(HOST, username=USER, password=PASSWORD)

channel = client.invoke_shell()
stdin = channel.makefile('wb')
stdout = channel.makefile('rb')

stdin.write('''
cd tmp
ls
exit
''')
print stdout.read()

stdout.close()
stdin.close()
client.close()

Interactive use cases

If you have an interactive ssh use case, paramiko can handle it... I personally would drive interactive ssh sessions with scrapli.

Listing all the ways I can think of to use paramiko interactively:

I might have missed some libraries that use paramiko, but it should be clear that paramiko is used quite extensively by python libraries that control ssh sessions.

Sign up to request clarification or add additional context in comments.

13 Comments

But this solution doesn't allow to read output of first command before all commands are finished. Am I right?
this doesnt work because stdout.read() reads the entire file. Meaning, it reads the program that was "typed" to the terminal. This is not the intended behaviour. How do you read only the output of ls instead of the entire program and the output of ls?
I'm with the two comments above... Any way to read the output of one command before all commands are finished?
@MatthewMoisen: you should use the exit command as last command.
Why isn't there a more standard way of doing this with paramiko? Isn't sending multiple requests a super obvious feature?
|
23

Try creating a command string separated by \n character. It worked for me. For. e.g. ssh.exec_command("command_1 \n command_2 \n command_3")

Comments

21

Strictly speaking, you can't. According to the ssh spec:

A session is a remote execution of a program. The program may be a shell, an application, a system command, or some built-in subsystem.

This means that, once the command has executed, the session is finished. You cannot execute multiple commands in one session. What you CAN do, however, is starting a remote shell (== one command), and interact with that shell through stdin etc... (think of executing a python script vs. running the interactive interpreter)

1 Comment

SSH RFC doesn't say about whether session should be terminated immediately after executing command. If you have looked at most of ssh client, they keep opening the Exec/Shell after session is established. User is allowed to type any number command. When user types "exit" then only session is terminated.
6

You can do that by invoking shell on the client and sending commands. Please refer here
The page has code for python 3.5. I have modified the code a bit to work for pythin 2.7. Adding code here for reference

import threading, paramiko

strdata=''
fulldata=''

class ssh:
    shell = None
    client = None
    transport = None

    def __init__(self, address, username, password):
        print("Connecting to server on ip", str(address) + ".")
        self.client = paramiko.client.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
        self.client.connect(address, username=username, password=password, look_for_keys=False)
        self.transport = paramiko.Transport((address, 22))
        self.transport.connect(username=username, password=password)

        thread = threading.Thread(target=self.process)
        thread.daemon = True
        thread.start()

    def close_connection(self):
        if(self.client != None):
            self.client.close()
            self.transport.close()

    def open_shell(self):
        self.shell = self.client.invoke_shell()

    def send_shell(self, command):
        if(self.shell):
            self.shell.send(command + "\n")
        else:
            print("Shell not opened.")

    def process(self):
        global strdata, fulldata
        while True:
            # Print data when available
            if self.shell is not None and self.shell.recv_ready():
                alldata = self.shell.recv(1024)
                while self.shell.recv_ready():
                    alldata += self.shell.recv(1024)
                strdata = strdata + str(alldata)
                fulldata = fulldata + str(alldata)
                strdata = self.print_lines(strdata) # print all received data except last line

    def print_lines(self, data):
        last_line = data
        if '\n' in data:
            lines = data.splitlines()
            for i in range(0, len(lines)-1):
                print(lines[i])
            last_line = lines[len(lines) - 1]
            if data.endswith('\n'):
                print(last_line)
                last_line = ''
        return last_line


sshUsername = "SSH USERNAME"
sshPassword = "SSH PASSWORD"
sshServer = "SSH SERVER ADDRESS"


connection = ssh(sshServer, sshUsername, sshPassword)
connection.open_shell()
connection.send_shell('cmd1')
connection.send_shell('cmd2')
connection.send_shell('cmd3')
time.sleep(10)
print(strdata)    # print the last line of received data
print('==========================')
print(fulldata)   # This contains the complete data received.
print('==========================')
connection.close_connection()

2 Comments

Did you mean to type sendShell, instead of send_shell? Or is send_shell a builtin of the paramiko.ssh class you're invoking?
@Fields, good catch. I have updated the code. I had forgotten to rename sendShell to send_shell in the ssh class. Thanks!
2
cmd = 'ls /home/dir'
self.ssh_stdin, self.ssh_stdout, self.ssh_stderr = self.ssh.exec_command(cmd)
print self.ssh_stdout.read()
cmd2 = 'cat /home/dir/test.log'
self.ssh_stdin2, self.ssh_stdout2, self.ssh_stderr2 = self.ssh.exec_command(cmd2)
print self.ssh_stdout2.read()

Comments

2

You can run multiple command using the below technique. Use semicolon to separate the Linux commands Eg:

chan.exec_command("date;ls;free -m")

Comments

2

If you wish each command to have an effect on the next command you should use:

stdin, stdout, stderr = client.exec_command("command1;command2;command3")

but in some cases, I found that when ";" doesn't work, using "&&" does work.

stdin, stdout, stderr = client.exec_command("command1 && command2 && command3")

Comments

1

You can execute an entire BASH script file for better use, here is the code for that:

import paramiko

hostname = "192.168.1.101"
username = "test"
password = "abc123"

# initialize the SSH client
client = paramiko.SSHClient()
# add to known hosts
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
    client.connect(hostname=hostname, username=username, password=password)
except:
    print("[!] Cannot connect to the SSH Server")
    exit()

# read the BASH script content from the file
bash_script = open("script.sh").read()
# execute the BASH script
stdin, stdout, stderr = client.exec_command(bash_script)
# read the standard output and print it
print(stdout.read().decode())
# print errors if there are any
err = stderr.read().decode()
if err:
    print(err)
# close the connection
client.close()

This will execute the local script.sh file on the remote 192.168.1.101 Linux machine.

script.sh (just an example):

cd Desktop
mkdir test_folder
cd test_folder
echo "$PATH" > path.txt

This tutorial explains this in detail: How to Execute BASH Commands in a Remote Machine in Python.

6 Comments

It's not really a bash script. You are executing the contents of the file as a command in the user's default shell.
Also by using AutoAddPolicy this way, you are losing a protection against MITM attacks.
@MartinPrikryl Can you point out the difference between reading the file content like that or executing a BASH script ?
I do not understand how your comment relates to mine. I was saying that your are not executing your command in bash, but in user's default shell (what can be any other shell).
Your code will blindly accept any host key, and what's worse any changed host key. So your code is vulnerable to MITM attacks.
|
0

This is the top result when googling this question, but none of the answers present in this thread (or any other resources I was able to find) resolved my issue so I wanted to contribute my solution.

import paramiko as pm
import time

USER = "user"
PASSWORD = "password"
HOST = "host"

client = pm.SSHClient()
client.set_missing_host_key_policy(pm.AutoAddPolicy())
client.connect(HOST, username=USER, password=PASSWORD)

channel = client.get_transport().open_session()
channel.invoke_shell()

commands = [
    "command1",
    "command2",
    "command3"
    ]

for cmd in commands:
    channel.send(cmd + "\n")

    wait_time = 0
    while wait_time <= 5:
        if channel.recv_ready():
            out = channel.recv(1024).decode('utf-8')
            print(out)
        else:
            time.sleep(1)
            wait_time += 1

channel.close()
client.close()

I would say this answer is most similar to the one provided by @nagabhushan, but I had issues with deploying child threads to handle processing the stdout of each command. My method is fairly compact and makes it easy to wrap into a function that accepts inputs for user, password, host, and a list of commands.

The biggest point that I haven't seen emphasized in other posts is that (on the servers I tested with) the channel.send(cmd) method needs to be instantly received via channel.recv() or else the subsequent commands will not be executed. I tested this on a Dell switch and an Ubuntu server, which both exhibited the same behavior. Some resources online have examples showing multiple channel.send() executions without receiving the output, and I was unable to get that working on any target device. It's worth noting that the Dell switch OS is built on Ubuntu, so this may be an Ubuntu-architecture specific issue.

As mentioned in other sources (but worth reiterating) the exec_command() method creates a new shell for each execution. If you have a series of commands that must be executed in a single shell session, exec_command() will not work.

3 Comments

Ubuntu by default uses OpenSSH. Any issue that happens only on an embedded device such as a switch is more likely to be tied to use of a special-purpose embedded SSH service (of which there are many, often as proprietary/commercial products) instead of general-purpose OpenSSH, so pointing the blame at Ubuntu is questionable.
Also by using AutoAddPolicy this way, you are losing a protection against MITM attacks. + Using "shell" channel for command automation is bad practice. Some of the existing answers show how to use exec_command to execute multiple commands in the same shell.
In my case the OS of the target device did not support multi-line commands using common patterns ("&&", ";", "\n") so I had to resort to invoke_shell

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.