Paramiko中SSHClient.exec_command与invoke_shell.send的差异及设备适配问题
Key Differences Between
exec_command() and invoke_shell() + send() in Paramiko, Explained for Your MikroTik/Cisco Issue Great question! I’ve run into this exact behavior with network devices before, so let’s break down what’s going on here.
Core Differences Between the Two Methods
1. How They Execute Commands
exec_command(): Think of this as running a single, standalone command over SSH. It creates a dedicated "exec" channel for each command, runs it, captures the output, and then closes the channel. No persistent session state is kept between calls—each command starts fresh, like opening a new SSH window, running one command, and closing it immediately.invoke_shell()+send(): This spins up an interactive shell session, just like when you manually SSH into a device and type commands one after another. All commands yousend()run in the same session, so context (like being in a specific configuration mode) is preserved. You have to handle the back-and-forth: sending commands, waiting for prompts, parsing mixed output, and dealing with any interactive prompts (like "Are you sure?").
2. Why They Behave Differently on MikroTik vs. Cisco
The root issue here is how each device’s CLI is designed:
MikroTik RouterOS
MikroTik’s CLI is flat by default—most configuration commands (like ip route add, interface vlan add) can be run directly from the main login prompt, no need to switch into a special configuration mode.
exec_command()works perfectly because it sends a single command, the device executes it immediately, and returns the output.- When using
invoke_shell()+send(), you’re probably missing a critical detail: you need to end each command with a newline (\r\n) so the device knows to process it. Also, MikroTik’s shell can be picky about timing—if you send commands too fast without waiting for the prompt to appear, the device might not register them. For example, sendingip route add ...without\r\nor before the[admin@MikroTik] >prompt shows up will leave the command hanging unexecuted.
Cisco IOS/IOS-XE
Cisco’s CLI is mode-based, which is the opposite of MikroTik. To run configuration commands (like ip route, vlan database), you first need to:
- Enter privileged EXEC mode with
enable - Switch to global configuration mode with
configure terminal
exec_command()fails here because each call is a fresh session. If you sendip route ...directly, it runs in user EXEC mode (which doesn’t allow configuration commands), and you’ll get an error like "% Invalid input detected at '^' marker." Even if you try to chain commands (e.g.,enable; configure terminal; ip route ...), Cisco’s CLI doesn’t handle this well across separate exec channels, and you’ll hit issues with interactive prompts (like entering the enable password).invoke_shell()works because you can build the session step-by-step: sendenable, wait for the password prompt, send the password, wait for#, sendconfigure terminal, wait for(config)#, then send your configuration commands. The session keeps track of which mode you’re in, so commands execute as expected.
3. Output Handling
exec_command(): Output is clean and separated per command—you can readstdoutandstderrdirectly to get the exact result of that one command. No mixing of prompts or previous command outputs.invoke_shell(): All output (commands you sent, prompts, error messages, results) comes through a single stream. You have to write code to parse this output, identify when the device is ready for the next command, and extract the actual result from the noise of prompts and echoed commands.
Quick Fixes for Your Use Case
- For MikroTik with
invoke_shell(): Always append\r\nto your commands, and add logic to wait for the device’s prompt before sending the next command. Example snippet:import time from paramiko import SSHClient, AutoAddPolicy ssh = SSHClient() ssh.set_missing_host_key_policy(AutoAddPolicy()) ssh.connect("mikrotik-ip", username="admin", password="your-pass") shell = ssh.invoke_shell() # Wait for initial prompt to appear while not shell.recv_ready(): time.sleep(0.1) # Send command with newline trigger shell.send("ip route add dst-address=192.168.1.0/24 gateway=10.0.0.1\r\n") # Wait for command to execute and prompt to return while not shell.recv_ready(): time.sleep(0.1) output = shell.recv(65535).decode() print(output) ssh.close() - For Cisco with
exec_command(): If you really want to use it, you’ll need to enable a pseudo-terminal withget_pty=Trueand handle interactive prompts. But honestly,invoke_shell()(or a dedicated library like Netmiko, which wraps Paramiko for network devices) is way more reliable for Cisco’s mode-based CLI.
内容的提问来源于stack exchange,提问作者Adhy
相关产品推荐
相关产品推荐

