Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

interactive shell and display same as terminal #362

lafrinte started this conversation in General
Discussion options

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 ~]$ '
You must be logged in to vote

Replies: 1 comment 2 replies

Comment options

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
You must be logged in to vote
2 replies
Comment options

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
Comment options

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Converted from issue

This discussion was converted from issue #359 on September 15, 2022 10:06.

AltStyle によって変換されたページ (->オリジナル) /