summaryrefslogtreecommitdiff
path: root/cmd2.py
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2017-02-01 01:56:41 -0500
committerTodd Leonhardt <todd.leonhardt@gmail.com>2017-02-01 01:56:41 -0500
commitcb5759c47bf5760233b8367258a5878eab340c37 (patch)
tree900e23917318cfed5f5cb047f50c501a91f7224d /cmd2.py
parent1c2983833cd7ae42c99be69fcdcd6d644e264bac (diff)
downloadcmd2-git-cb5759c47bf5760233b8367258a5878eab340c37.tar.gz
Fixed pipe to shell bug on Python 3.x.
Fixed implementation of how command output can get piped to OS shell commands so that it works in Python 3 as well as Python 2. Also added a unit test to verify that this functionality is behaving as intended. All I can say is that the subprocess module was horrible for Python 3 prior to 3.5. Getting it to play nicely with unicode strings for stdin/stdout isn't easy.
Diffstat (limited to 'cmd2.py')
-rwxr-xr-xcmd2.py43
1 files changed, 29 insertions, 14 deletions
diff --git a/cmd2.py b/cmd2.py
index 1ab53b60..f7eb7ead 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -33,6 +33,7 @@ import optparse
import os
import platform
import re
+import six
import subprocess
import sys
import tempfile
@@ -58,6 +59,9 @@ from six.moves import zip
# Python 2 urllib2.urlopen() or Python3 urllib.request.urlopen()
from six.moves.urllib.request import urlopen
+# This is a fake file object for textual data. It’s an alias for StringIO.StringIO in Py 2 and io.StringIO in Py 3
+from six import StringIO
+
# Python 3 compatability hack due to no built-in file keyword in Python 3
# Due to one occurence of isinstance(<foo>, file) checking to see if something is of file type
try:
@@ -899,9 +903,9 @@ class Cmd(cmd.Cmd):
if statement.parsed.pipeTo:
self.kept_state = Statekeeper(self, ('stdout',))
self.kept_sys = Statekeeper(sys, ('stdout',))
- self.redirect = subprocess.Popen(statement.parsed.pipeTo, shell=True, stdout=subprocess.PIPE,
- stdin=subprocess.PIPE)
- sys.stdout = self.stdout = self.redirect.stdin
+ sys.stdout = self.stdout
+ # Redirect stdout to a fake file object which is an in-memory text stream
+ self.stdout = StringIO()
elif statement.parsed.output:
if (not statement.parsed.outputTo) and (not can_clip):
raise EnvironmentError('Cannot redirect to paste buffer; install ``xclip`` and re-run to enable')
@@ -919,17 +923,28 @@ class Cmd(cmd.Cmd):
def restore_output(self, statement):
if self.kept_state:
- if statement.parsed.output:
- if not statement.parsed.outputTo:
- self.stdout.seek(0)
- write_to_paste_buffer(self.stdout.read())
- elif statement.parsed.pipeTo:
- for result in self.redirect.communicate():
- self.kept_state.stdout.write(result or '')
- self.stdout.close()
- self.kept_state.restore()
- self.kept_sys.restore()
- self.kept_state = None
+ try:
+ if statement.parsed.output:
+ if not statement.parsed.outputTo:
+ self.stdout.seek(0)
+ write_to_paste_buffer(self.stdout.read())
+ elif statement.parsed.pipeTo:
+ # Retreive the output from our internal command
+ command_output = self.stdout.getvalue()
+ finally:
+ self.stdout.close()
+ self.kept_state.restore()
+ self.kept_sys.restore()
+ self.kept_state = None
+
+ if statement.parsed.pipeTo:
+ # Pipe output from the command to the shell command via echo
+ command_line = 'echo "{}" | {}'.format(command_output.rstrip(), statement.parsed.pipeTo)
+ result = subprocess.check_output(command_line, shell=True)
+ if six.PY3:
+ self.stdout.write(result.decode())
+ else:
+ self.stdout.write(result)
def onecmd(self, line):
"""Interpret the argument as though it had been typed in response