Skip to content

Commit 8f4d7c4

Browse files
authored
Fixed encoding not used for cmd string. Updated changelog, documentat… (#258)
* Fixed encoding not used for cmd string, updated tests. * Updated changelog, documentation, docstrings.
1 parent ae932bc commit 8f4d7c4

File tree

7 files changed

+62
-37
lines changed

7 files changed

+62
-37
lines changed

Changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Fixes
1717
-----
1818

1919
* ``SSHClient`` with proxy enabled could not be used without setting port - #248
20+
* Encoding would not be applied to command string on ``run_command`` and interactive shells, `utf-8` used instead - #174.
2021

2122

2223
2.3.2

doc/advanced.rst

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -448,19 +448,36 @@ Shell to use is configurable:
448448
Commands will be run under the ``zsh`` shell in the above example. The command string syntax of the shell must be used, typically ``<shell> -c``.
449449

450450

451-
Output encoding
452-
===============
451+
Output And Command Encoding
452+
===========================
453453

454-
By default, output is encoded as ``UTF-8``. This can be configured with the ``encoding`` keyword argument.
454+
By default, command string and output are encoded as ``UTF-8``. This can be configured with the ``encoding`` keyword argument to ``run_command`` and ``open_shell``.
455455

456456
.. code-block:: python
457457
458-
client = <..>
458+
client = ParallelSSHClient(<..>)
459459
460-
client.run_command(<..>, encoding='utf-16')
460+
cmd = b"echo \xbc".decode('latin-1')
461+
output = client.run_command(cmd, encoding='latin-1')
461462
stdout = list(output[0].stdout)
462463
463-
Contents of ``stdout`` are `UTF-16` encoded.
464+
465+
Contents of ``stdout`` are `latin-1` decoded.
466+
467+
``cmd`` string is also `latin-1` encoded when running command or writing to interactive shell.
468+
469+
Output encoding can also be changed by adjusting ``HostOutput.encoding``.
470+
471+
.. code-block:: python
472+
473+
client = ParallelSSHClient(<..>)
474+
475+
output = client.run_command('echo me')
476+
output[0].encoding = 'utf-16'
477+
stdout = list(output[0].stdout)
478+
479+
Contents of ``stdout`` are `utf-16` decoded.
480+
464481

465482
.. note::
466483

@@ -480,12 +497,12 @@ All output, including stderr, is sent to the ``stdout`` channel with PTY enabled
480497
481498
client = <..>
482499
483-
client.run_command("echo 'asdf' >&2", use_pty=True)
500+
output = client.run_command("echo 'asdf' >&2", use_pty=True)
484501
for line in output[0].stdout:
485502
print(line)
486503
487504
488-
Note output is from the ``stdout`` channel while it was writeen to ``stderr``.
505+
Note output is from the ``stdout`` channel while it was written to ``stderr``.
489506

490507
:Output:
491508
.. code-block:: shell

pssh/clients/base/parallel.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def _open_shell(self, host_i, host,
115115
def open_shell(self, encoding='utf-8', read_timeout=None):
116116
"""Open interactive shells on all hosts.
117117
118-
:param encoding: Encoding to use for shell output.
118+
:param encoding: Encoding to use for command string and shell output.
119119
:type encoding: str
120120
:param read_timeout: Seconds before reading from output times out.
121121
:type read_timeout: float
@@ -331,8 +331,8 @@ def join(self, output=None, consume_output=False, timeout=None,
331331
Since self.timeout is passed onto each individual SSH session it is
332332
**not** used for any parallel functions like `run_command` or `join`.
333333
:type timeout: int
334-
:param encoding: Encoding to use for output. Must be valid
335-
`Python codec <https://docs.python.org/library/codecs.html>`_
334+
:param encoding: Unused - encoding from each ``HostOutput`` is used instead.
335+
To be removed in future releases.
336336
:type encoding: str
337337
338338
:raises: :py:class:`pssh.exceptions.Timeout` on timeout requested and

pssh/clients/base/single.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,22 @@ class InteractiveShell(object):
8585
8686
``InteractiveShell.output`` is a :py:class:`pssh.output.HostOutput` object.
8787
"""
88-
__slots__ = ('_chan', '_client', 'output')
89-
_EOL = '\n'
88+
__slots__ = ('_chan', '_client', 'output', '_encoding')
89+
_EOL = b'\n'
9090

9191
def __init__(self, channel, client, encoding='utf-8', read_timeout=None):
9292
"""
9393
:param channel: The channel to open shell on.
9494
:type channel: ``ssh2.channel.Channel`` or similar.
9595
:param client: The SSHClient that opened the channel.
9696
:type client: :py:class:`BaseSSHClient`
97+
:param encoding: Encoding to use for command string when calling ``run`` and shell output.
98+
:type encoding: str
9799
"""
98100
self._chan = channel
99101
self._client = client
100102
self._client._shell(self._chan)
103+
self._encoding = encoding
101104
self.output = self._client._make_host_output(
102105
self._chan, encoding=encoding, read_timeout=read_timeout)
103106

@@ -142,7 +145,7 @@ def run(self, cmd):
142145
Note that ``\\n`` is appended to every string.
143146
:type cmd: str
144147
"""
145-
cmd += self._EOL
148+
cmd = cmd.encode(self._encoding) + self._EOL
146149
self._client._eagain_write(self._chan.write, cmd)
147150

148151

@@ -227,7 +230,7 @@ def open_shell(self, encoding='utf-8', read_timeout=None):
227230
228231
Can be used as context manager - ``with open_shell() as shell``.
229232
230-
:param encoding: Encoding to use for output from shell.
233+
:param encoding: Encoding to use for command string and shell output.
231234
:type encoding: str
232235
:param read_timeout: Timeout in seconds for reading from output.
233236
:type read_timeout: float
@@ -473,10 +476,10 @@ def run_command(self, command, sudo=False, user=None,
473476
_command = 'sudo -u %s -S ' % (user,)
474477
_shell = shell if shell else '$SHELL -c'
475478
_command += "%s '%s'" % (_shell, command,)
479+
_command = _command.encode(encoding)
476480
with GTimeout(seconds=self.timeout):
477481
channel = self.execute(_command, use_pty=use_pty)
478482
_timeout = read_timeout if read_timeout else timeout
479-
channel = self.execute(_command, use_pty=use_pty)
480483
host_out = self._make_host_output(channel, encoding, _timeout)
481484
return host_out
482485

pssh/clients/native/parallel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def run_command(self, command, sudo=False, user=None, stop_on_errors=True,
173173
host list - :py:class:`pssh.exceptions.HostArgumentError` is
174174
raised otherwise
175175
:type host_args: tuple or list
176-
:param encoding: Encoding to use for output. Must be valid
176+
:param encoding: Encoding to use for command string and output. Must be valid
177177
`Python codec <https://docs.python.org/library/codecs.html>`_
178178
:type encoding: str
179179
:param read_timeout: (Optional) Timeout in seconds for reading from stdout

pssh/clients/ssh/parallel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def run_command(self, command, sudo=False, user=None, stop_on_errors=True,
185185
host list - :py:class:`pssh.exceptions.HostArgumentError` is
186186
raised otherwise
187187
:type host_args: tuple or list
188-
:param encoding: Encoding to use for output. Must be valid
188+
:param encoding: Encoding to use for command string and output. Must be valid
189189
`Python codec <https://docs.python.org/library/codecs.html>`_
190190
:type encoding: str
191191
:param read_timeout: (Optional) Timeout in seconds for reading from stdout

tests/native/test_parallel_client.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,27 +1151,31 @@ def test_per_host_dict_args_invalid(self):
11511151
self.assertRaises(
11521152
KeyError, self.client.run_command, cmd, host_args=host_args)
11531153

1154-
def test_ssh_client_utf_encoding(self):
1155-
"""Test that unicode output works"""
1156-
expected = [u'é']
1157-
_utf16 = u'é'.encode('utf-8').decode('utf-16')
1158-
cmd = u"echo 'é'"
1159-
output = self.client.run_command(cmd)
1154+
def test_run_command_encoding(self):
1155+
"""Test that unicode command works"""
1156+
exp = b"\xbc"
1157+
_cmd = b"echo " + exp
1158+
cmd = _cmd.decode('latin-1')
1159+
expected = [exp.decode('latin-1')]
1160+
output = self.client.run_command(cmd, encoding='latin-1')
11601161
stdout = list(output[0].stdout)
1161-
self.assertEqual(expected, stdout,
1162-
msg="Got unexpected unicode output %s - expected %s" % (
1163-
stdout, expected,))
1164-
output = self.client.run_command(cmd, encoding='utf-16')
1165-
_stdout = list(output[0].stdout)
1166-
self.assertEqual([_utf16], _stdout)
1167-
1168-
def test_ssh_client_utf_encoding_join(self):
1169-
_utf16 = u'é'.encode('utf-8').decode('utf-16')
1170-
cmd = u"echo 'é'"
1171-
output = self.client.run_command(cmd, encoding='utf-16')
1172-
self.client.join(output, encoding='utf-16')
1162+
self.assertEqual(expected, stdout)
1163+
# With join
1164+
output = self.client.run_command(cmd, encoding='latin-1')
1165+
self.client.join(output)
11731166
stdout = list(output[0].stdout)
1174-
self.assertEqual([_utf16], stdout)
1167+
self.assertEqual(expected, stdout)
1168+
1169+
def test_shell_encoding(self):
1170+
exp = b"\xbc"
1171+
_cmd = b"echo " + exp
1172+
cmd = _cmd.decode('latin-1')
1173+
expected = [exp.decode('latin-1')]
1174+
shells = self.client.open_shell(encoding='latin-1')
1175+
self.client.run_shell_commands(shells, cmd)
1176+
self.client.join_shells(shells)
1177+
stdout = list(shells[0].stdout)
1178+
self.assertEqual(expected, stdout)
11751179

11761180
def test_pty(self):
11771181
cmd = "echo 'asdf' >&2"

0 commit comments

Comments
 (0)