-
Notifications
You must be signed in to change notification settings - Fork 152
-
help wanted issue.
i use paramiko to connect network device and send commands. for performance and stability reasons, i'm trying to use pssh instead of paramiko but face some trouble in output parsing. the exact same display as the device terminal will help me locate the problem. (for network device, command prompt contains resource info and command mode)
In [96]: ssh = paramiko.SSHClient()
In [97]: ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
In [98]: ssh.connect(hostname=ip, port=port, username=user, password=password)
In [99]: shell = ssh.invoke_shell()
In [100]: shell.send("ls -al\n")
Out[100]: 7
In [101]: shell.recv(65535)
Out[101]: b'Last login: Thu Aug 25 08:29:09 2022 from 172.17.0.3\r\r\n[test@73dc979760a2 ~]$ ls -al\r\ntotal 24\r\ndrwx------ 2 test test 4096 Aug 24 16:26 \x1b[0m\x1b[01;34m.\x1b[0m\r\ndrwxr-xr-x 1 root root 4096 Aug 24 15:04 \x1b[01;34m..\x1b[0m\r\n-rw------- 1 test test 5 Aug 24 16:26 .bash_history\r\n-rw-r--r-- 1 test test 18 Nov 24 2021 .bash_logout\r\n-rw-r--r-- 1 test test 193 Nov 24 2021 .bash_profile\r\n-rw-r--r-- 1 test test 231 Nov 24 2021 .bashrc\r\n[test@73dc979760a2 ~]$ '
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment 2 replies
-
Hi there,
Thanks for the interest. For questions and instructions on how to use please use the discussions page. Issue tracker is for actionable bugs and feature requests.
Have a look at Interactive Shell documentation for the above.
Eg for the above command:
client = SSHClient(ip, port=port, user=user, password=password)
cmd = 'ls -al'
with client.open_shell() as shell:
shell.run(cmd)
print(list(shell.stdout))
print(shell.exit_code)
Prints:
'Last login: <..>'
0
Beta Was this translation helpful? Give feedback.
All reactions
-
sorry for replying so much late.
Linux is different with network device system. the command sending to device will not replay a finish state code to session, so the action will be blocked at the time trying to read the output. the code example in Ipython will show this:
In [22]: client = SSHClient("xxxxx", user="admin", password="xxxxx")
In [23]: channel = client.open_shell()
In [24]: channel.run("screen-length disable\n")
In [25]: client = SSHClient("xxxxxx", user="admin", password="xxxxx")
In [26]: channel = client.open_shell(read_timeout=5)
In [27]: channel.run("screen-length disable\n")
In [28]: for l in channel.stdout:
...: print(l)
...:
******************************************************************************
* Copyright (c) 2004-2015 Hangzhou H3C Tech. Co., Ltd. All rights reserved. *
* Without the owner's prior written consent, *
* no decompiling or reverse-engineering shall be allowed. *
******************************************************************************
<H3C>screen-length disable
% Screen-length configuration is disabled for current user.
<H3C>
---------------------------------------------------------------------------
Timeout Traceback (most recent call last)
Cell In [28], line 1
----> 1 for l in channel.stdout:
2 print(l)
the command 'screen-length disable' has finished and wait next command, but blocked.
for paramiko, i using non-blocking read getting all stdout and using flashtext(a third party module) to match keywords or command prompt to make sure when can I send the next command.
here is my interaction code for network device based on paramiko.
global_state, buffers = True, ""
try:
while elapsed <= timeout:
time.sleep(defaults.net.CHANNEL_WAIT)
elapsed = time.time() - start_time
buffer = self.connection.channel.recv() # < -- paramiko interaction channel
if not buffer:
time.sleep(defaults.net.CHANNEL_WAIT)
continue
buffers += buffer
state, errmsg = command_failed(self.keywords.error, self.keywords.exclude, buffer) # < -- match failed keywords
if not state:
global_state = False
state, command_prompt = find_prompt(buffers, self.connection.return_string)
if state:
self.set_prompt(command_prompt)
self.terminal_display += buffers
raise CommandException(errmsg)
state, choosing_string = predict_interaction(
command, self.keywords.interactive, self.command_choosing, buffer) # < -- match interaction keywords
if state:
self.write(choosing_string)
continue
state, command_prompt = find_prompt(buffers, self.connection.return_string) # < -- match command prompt pattern
if state:
self.set_prompt(command_prompt)
break
self.terminal_display += buffers
state, errmsg = elapsed <= timeout, None
if not state:
global_state = False
raise NetmikoTimeoutException
except ReadException:
errmsg = f"str(exc). command: {command}"
except WriteException:
errmsg = f"str(exc). command: {command}"
except CommandException as exc:
errmsg = f"command failed: {command}. {str(exc)}"
except NetmikoTimeoutException:
errmsg = f"elapsed over {elapsed}s. max wait {int(timeout)}s"
except Exception as exc:
errmsg = f"un-expected error. {str(exc)}"
channel read and write:
def recv(self) -> str:
if not self.is_alive():
raise ReadException("attempt to receive data, but there is no active channel.")
output = ""
if self._channel.recv_ready():
buf = self._channel.recv(defaults.net.MAX_BUFFER)
if not buf:
raise ReadException("channel stream closed by peer.")
output += buf.decode(self.encoding, "ignore")
output = strip_ansi_escape_codes(output, return_string=self.return_string)
output = normalize_linefeed(output, return_string=self.return_string)
return output
def write(self, strings: Optional[str] = None) -> None:
if not self.is_alive():
raise WriteException(f"attempt to write data, but there is no active channel.")
if not strings:
strings = self.return_string
if strings[-1] != self.return_string:
strings = strings + self.return_string
self._channel.sendall(strings.encode(self.encoding))
find_prompt:
def find_prompt(buffers: str, return_string: str) -> Tuple[bool, Optional[str]]:
"""
:proxy:
1. first char: [ expect last char: ]
2. first char: [ expect endswith: ]: or ]# or ]$
3. first char: < expect last char: >
4. first char: < expect endswith: >: or ># or >$
"""
def get_pairs(char):
"""
:param char: str. one character.
:rtype: Tuple[str, Set[str]]
"""
pair_char, last_chars = {"[": "]", "<": ">"}, {":", "$", "#"}
if char in pair_char:
return pair_char[char], last_chars
return None, last_chars
last_line = string.rsplit(buffers, delimiter=return_string, keep_index=-1)
raw_line = last_line
if last_line[-1] in {"\n", "\r", " "}:
last_line = last_line.strip() # output may contain backspace at the end of the string
if not last_line or len(last_line) < 3:
return False, None
head, last, last2 = last_line[0], last_line[-1], last_line[-2]
pair, last_char = get_pairs(head)
if pair and (pair == last or (last in last_char and last2 == pair)):
return True, raw_line
return False, None
Beta Was this translation helpful? Give feedback.
All reactions
-
Have a look at the documentation. The shell needs to be closed before reading output or it will block forever. This is noted in docs.
Use the context manager as above to auto-close after run. See reading partial output section on reading partial output without closing/joining the shell.
with client.open_shell() as shell:
shell.run(cmd)
print(list(shell.stdout))
Or explicitly:
shell = client.open_shell()
shell.run(<..>)
shell.close()
print(list(shell.stdout))
All I/O done in parallel-ssh is non-blocking.
Beta Was this translation helpful? Give feedback.