diff --git a/setup.py b/setup.py index cab15aa8..52308c8b 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ elif (3, 0) < version < (3, 3): VERSION = '3.11' -install_requires = ['psutil', 'colorama', 'six', 'decorator'] +install_requires = ['psutil', 'colorama', 'six', 'decorator', 'bashlex'] extras_require = {':python_version<"3.4"': ['pathlib2'], ":sys_platform=='win32'": ['win_unicode_console']} diff --git a/tests/shells/test_bash.py b/tests/shells/test_bash.py index c3738f44..c4268c04 100644 --- a/tests/shells/test_bash.py +++ b/tests/shells/test_bash.py @@ -56,3 +56,13 @@ class TestBash(object): def test_get_history(self, history_lines, shell): history_lines(['ls', 'rm']) assert list(shell.get_history()) == ['ls', 'rm'] + + def test_split_command(self, shell): + command = 'git log $(git ls-files thefuck | grep python_command) -p' + command_parts = ['git', 'log', '$(git ls-files thefuck | grep python_command)', '-p'] + assert shell.split_command(command) == command_parts + + # bashlex doesn't support parsing arithmetic expressions, so make sure + # shlex is used a fallback + # See https://github.com/idank/bashlex#limitations + assert shell.split_command('$((1 + 2))') == ['$((1', '+', '2))'] diff --git a/thefuck/shells/bash.py b/thefuck/shells/bash.py index d6f4cdff..f914163f 100644 --- a/thefuck/shells/bash.py +++ b/thefuck/shells/bash.py @@ -1,4 +1,5 @@ import os +import bashlex from ..conf import settings from ..utils import memoize from .generic import Generic @@ -45,3 +46,13 @@ class Bash(Generic): else: config = 'bash config' return 'eval $(thefuck --alias)', config + + def split_command(self, command): + generic = Generic() + + # If bashlex fails for some reason, fallback to shlex + # See https://github.com/idank/bashlex#limitations + try: + return generic.decode_utf8(list(bashlex.split(generic.encode_utf8(command)))) + except: + return generic.split_command(command) diff --git a/thefuck/shells/generic.py b/thefuck/shells/generic.py index e20d7ec3..ec38df1c 100644 --- a/thefuck/shells/generic.py +++ b/thefuck/shells/generic.py @@ -65,9 +65,17 @@ class Generic(object): def split_command(self, command): """Split the command using shell-like syntax.""" + return self.decode_utf8(shlex.split(self.encode_utf8(command))) + + def encode_utf8(self, command): if six.PY2: - return [s.decode('utf8') for s in shlex.split(command.encode('utf8'))] - return shlex.split(command) + return command.encode('utf8') + return command + + def decode_utf8(self, command_parts): + if six.PY2: + return [s.decode('utf8') for s in command_parts] + return command_parts def quote(self, s): """Return a shell-escaped version of the string s."""