为什么 Python 的子进程调用不能与 Windows 上的 sh 一起正常工作?
Why does Python's subprocess call not work correctly with sh on Windows?
以下代码打印 hi
:
import subprocess
subprocess.call(['sh', '-c', ' "$@" ', '-', 'echo', 'hi'])
然而,在原生 Windows Python 上,稍微修改一下代码:
import subprocess
subprocess.call(['sh', '-c', '"$@"' , '-', 'echo', 'hi'])
产生以下错误:
sh: -c: line 0: unexpected EOF while looking for matching `"'
sh: -c: line 1: syntax error: unexpected end of file
这是为什么?
这似乎是 MSYS2(可能来自 Cygwin)中一个 令人惊讶的长期存在的错误 ,由 Windows 的 idiosyncratic quoting rules 引起。
发生的事情是 MSYS2 期望
subprocess.list2cmdline(['sh', '-c', '"$@"' , '-', 'echo', 'hi'])
翻译成
sh -c "\"$@\"" - echo hi
但实际上它会产生以下内容:
sh -c \"$@\" - echo hi
很难理解为什么会这样,直到您意识到 MSYS2 认为 Windows 命令行引用规则是这样的,反斜杠在外部被视为 文字 双引号。
所以最终发生的是 \"$@\"
被解释为单个文字反斜杠,后跟带引号的字符串 $@\"
,其结尾引号是 missing。如果我们添加结束引号,它实际上看起来像 \"$@\""
, 似乎 不平衡,但实际上对 MSYS2 是平衡的。 (!)
但是,当参数包含 space 时,整件事都会被天真地引用,不小心掩盖了问题。
为什么它会这样解释?可能是因为文档,says:
CommandLineToArgvW
has a special interpretation of backslash characters when they are followed by a quotation mark character ("
). This interpretation assumes that any preceding argument is a valid file system path, or else it may behave unpredictably.
This special interpretation controls the "in quotes" mode tracked by the parser.
当解析器在“引号内”模式下 not 时,很容易误读它并认为反斜杠失去了它们的特殊含义,这就是 MSYS2 解析器说。但是,如果您仔细阅读接下来的两句话,它会准确解释 "in-quotes" 模式 的含义:
When this mode is off, whitespace terminates the current argument. When on, whitespace is added to the argument like all other characters.
即全部。反斜杠 don't 突然变成逐字外引号。他们仍然可以像在内部一样转义引号,当然除了内部规则甚至比外部规则 更复杂。
你是如何解决这个问题的?值得庆幸的是,本机 Windows Python 允许将整个命令行作为单个字符串文字传递,因此您实际上可以使用辅助方法解决此错误:
import subprocess
def list2cmdline(args): return ' '.join(map(
lambda a: a if a.lstrip().startswith('"') or '"' not in a else '"' + a + '"',
map(lambda a: subprocess.list2cmdline([a]), args)))
subprocess.call(list2cmdline(['sh', '-c', '"$@"', '-', 'echo', 'hi']))
或者,您可以直接对其进行 monkeypatch:
import subprocess
subprocess.list2cmdline = (lambda old: lambda args: ' '.join(map(
lambda a: a if a.lstrip().startswith('"') or '"' not in a else '"' + a + '"',
map(lambda a: old([a]), args))))(subprocess.list2cmdline)
subprocess.call(['sh', '-c', '"$@"', '-', 'echo', 'hi'])
这应该不会影响任何正常运行的程序,因为有多种引用方式,但它应该可以解决 MSYS2 的问题。
以下代码打印 hi
:
import subprocess
subprocess.call(['sh', '-c', ' "$@" ', '-', 'echo', 'hi'])
然而,在原生 Windows Python 上,稍微修改一下代码:
import subprocess
subprocess.call(['sh', '-c', '"$@"' , '-', 'echo', 'hi'])
产生以下错误:
sh: -c: line 0: unexpected EOF while looking for matching `"'
sh: -c: line 1: syntax error: unexpected end of file
这是为什么?
这似乎是 MSYS2(可能来自 Cygwin)中一个 令人惊讶的长期存在的错误 ,由 Windows 的 idiosyncratic quoting rules 引起。 发生的事情是 MSYS2 期望
subprocess.list2cmdline(['sh', '-c', '"$@"' , '-', 'echo', 'hi'])
翻译成
sh -c "\"$@\"" - echo hi
但实际上它会产生以下内容:
sh -c \"$@\" - echo hi
很难理解为什么会这样,直到您意识到 MSYS2 认为 Windows 命令行引用规则是这样的,反斜杠在外部被视为 文字 双引号。
所以最终发生的是 \"$@\"
被解释为单个文字反斜杠,后跟带引号的字符串 $@\"
,其结尾引号是 missing。如果我们添加结束引号,它实际上看起来像 \"$@\""
, 似乎 不平衡,但实际上对 MSYS2 是平衡的。 (!)
但是,当参数包含 space 时,整件事都会被天真地引用,不小心掩盖了问题。
为什么它会这样解释?可能是因为文档,says:
CommandLineToArgvW
has a special interpretation of backslash characters when they are followed by a quotation mark character ("
). This interpretation assumes that any preceding argument is a valid file system path, or else it may behave unpredictably.This special interpretation controls the "in quotes" mode tracked by the parser.
当解析器在“引号内”模式下 not 时,很容易误读它并认为反斜杠失去了它们的特殊含义,这就是 MSYS2 解析器说。但是,如果您仔细阅读接下来的两句话,它会准确解释 "in-quotes" 模式 的含义:
When this mode is off, whitespace terminates the current argument. When on, whitespace is added to the argument like all other characters.
即全部。反斜杠 don't 突然变成逐字外引号。他们仍然可以像在内部一样转义引号,当然除了内部规则甚至比外部规则 更复杂。
你是如何解决这个问题的?值得庆幸的是,本机 Windows Python 允许将整个命令行作为单个字符串文字传递,因此您实际上可以使用辅助方法解决此错误:
import subprocess
def list2cmdline(args): return ' '.join(map(
lambda a: a if a.lstrip().startswith('"') or '"' not in a else '"' + a + '"',
map(lambda a: subprocess.list2cmdline([a]), args)))
subprocess.call(list2cmdline(['sh', '-c', '"$@"', '-', 'echo', 'hi']))
或者,您可以直接对其进行 monkeypatch:
import subprocess
subprocess.list2cmdline = (lambda old: lambda args: ' '.join(map(
lambda a: a if a.lstrip().startswith('"') or '"' not in a else '"' + a + '"',
map(lambda a: old([a]), args))))(subprocess.list2cmdline)
subprocess.call(['sh', '-c', '"$@"', '-', 'echo', 'hi'])
这应该不会影响任何正常运行的程序,因为有多种引用方式,但它应该可以解决 MSYS2 的问题。