55
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.

Takkun
  • 8,119
  • 13
  • 38
  • 41
  • Possible duplicate of [Implement an interactive shell over ssh in Python using Paramiko?](https://stackoverflow.com/questions/35821184/implement-an-interactive-shell-over-ssh-in-python-using-paramiko) – misha May 15 '18 at 20:57

8 Answers8

50

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.

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
  • 3
    But this solution doesn't allow to read output of first command before all commands are finished. Am I right? – Nikolai Golub Aug 29 '14 at 15:45
  • 2
    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`? – Jenia Ivanov Jun 17 '15 at 21:42
  • I'm with the two comments above... Any way to read the output of one command before all commands are finished? – dmranck Oct 29 '15 at 15:13
  • @Mike Pennington Hi, man! thank's for the answer, It actually works well, however, there're some problems with sudo comands, could you look at my question please? http://stackoverflow.com/questions/34068218/how-to-run-su-command-in-paramiko – Leo Dec 03 '15 at 14:25
  • Hi,I have query here.I am also facing the same issue and want to run multiple commands. How can I pass the value dynamically in stdin.write(''' ''')? I want to do like: cmd="mkdir something" stdin.write(''' ''') thanks in advance. – fresher Dec 24 '15 at 14:38
  • After `stdout.read()`, it hangs indefinitely for me. Any idea why? – Matthew Moisen Jan 26 '16 at 01:49
  • 3
    @MatthewMoisen: you should use the exit command as last command. – Q Caron Jun 16 '16 at 11:30
  • Does this work for the case of say a 'sudo -u user -i', then retain those privileges to run another command which execute a bash script. I tried it and it seemed not to work. Can someone help? – Surya Sekhar Mondal Dec 03 '19 at 19:43
  • @SuryaSekharMondal, you can't run a `sudo` command (or otherwise change the shell) inside `stdin.write()` because you wind up disconnecting both stdin and stdout with the new sudo or `/bin/sh`. This technique relies on a constant stdout and stdin throughout the script. – Mike Pennington Dec 03 '19 at 19:58
  • @MikePennington Okay. So is there no way of doing a sudo and then executing a bash script with the same privileges in a same channel? – Surya Sekhar Mondal Dec 03 '19 at 21:32
  • @SuryaSekharMondal, please consider using [`pexpect`](https://github.com/pexpect/pexpect) for your use-case. `sudo` with [`pexpect`](https://github.com/pexpect/pexpect) is substantially easier. FWIW, I have a [`pexpect tutorial`](http://www.pennington.net/tutorial/pexpect_001/pexpect_tutorial.pdf) on my personal website – Mike Pennington Dec 03 '19 at 21:53
  • Why isn't there a more standard way of doing this with paramiko? Isn't sending multiple requests a super obvious feature? – Jan Berndt Oct 12 '22 at 10:57
  • @CheeseCrustery, to use paramiko interactively, please refer to **interactive use cases**, in my answer... I edited to list several other libraries that depend on [`paramiko`](https://pypi.org/project/paramiko/) to drive interactive ssh sessions. – Mike Pennington Oct 12 '22 at 12:29
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")

user193130
  • 8,009
  • 4
  • 36
  • 64
Sandeep
  • 331
  • 2
  • 2
20

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)

Steven
  • 28,002
  • 5
  • 61
  • 51
  • 1
    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. – Sujal Sheth Oct 29 '14 at 11:34
5

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()
Nagabhushan S N
  • 6,407
  • 8
  • 44
  • 87
  • 1
    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 Apr 19 '18 at 17:30
  • 1
    @Fields, good catch. I have updated the code. I had forgotten to rename sendShell to send_shell in the ssh class. Thanks! – Nagabhushan S N Apr 21 '18 at 09:20
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()
user2661518
  • 2,677
  • 9
  • 42
  • 79
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")
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
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")
Niv Cohen
  • 1,078
  • 2
  • 11
  • 21
0

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.

rockikz
  • 586
  • 1
  • 6
  • 17
  • 2
    It's not really a *bash script*. You are executing the contents of the file as a command in the *user's default shell*. – Martin Prikryl Nov 11 '19 at 15:34
  • Also by using `AutoAddPolicy` this way, you are losing a protection against MITM attacks. – Martin Prikryl Nov 11 '19 at 15:35
  • @MartinPrikryl Can you point out the difference between reading the file content like that or executing a BASH script ? – rockikz Nov 12 '19 at 12:37
  • Also, can you please tell me how `AutoAddPolicy` is causing that? – rockikz Nov 12 '19 at 12:39
  • 1
    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). – Martin Prikryl Nov 12 '19 at 12:39
  • 1
    Your code will blindly accept any host key, and what's worse any **changed** host key. So your code is vulnerable to [MITM attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). – Martin Prikryl Nov 12 '19 at 12:42