完美地批量转发参数

时间:2018-08-09 05:19:58

标签: batch-file escaping

我有一个小的python脚本:

# args.py
import sys; print(sys.argv)

如何编写一个.bat包装文件,将所有参数转发到此脚本?

要从测试中删除我的shell,我将以以下方式调用它:

import subprocess
import sys
def test_bat(*args):
    return subprocess.check_output(['args.bat'] + list(args), encoding='ascii')

obvious choice of batch file

@echo off
python args.py %*

适用于简单情况:

>>> test_bat('a', 'b', 'c')
"['args.py', 'a', 'b', 'c']\n"
>>> test_bat('a', 'b c')
"['args.py', 'a', 'b c']\n"

但是在尝试任意字符串时会迅速崩溃:

>>> test_bat('a b', 'c\n d')
"['args.py', 'a b', 'c']\n"  # missing d
>>> test_bat('a', 'b^^^^^c')
"['args.py', 'a', 'b^c']\n"  # missing ^^^^

是否甚至可以使bat文件的参数未经修改地传递?


要证明不是subprocess引起的问题,请尝试使用

def test_py(*args):
    return subprocess.check_output([sys.executable, 'args.py'] + list(args), encoding='ascii')

所有测试的行为均符合预期


类似的问题:

2 个答案:

答案 0 :(得分:1)

简而言之:没有{em>可靠的方法通过批处理文件按原样传递参数,因为cmd.exe解释参数的方式;请注意,cmd.exe总是涉及 ,即使执行不要求任何外壳程序参与的API调用批处理文件,也正是执行批处理文件所需的解释器。

问题简而言之:

  • 在Windows上,出于技术原因,调用外部程序需要使用命令行作为单个字符串 。因此,即使使用基于数组的,无外壳程序的方式来调用外部程序,也需要自动编写命令行,在该命令行中,嵌入了各个参数

    • 例如,Python的subprocess.check_output()接受目标可执行文件及其参数作为数组的元素,如问题所示。

    • 目标可执行文件是使用自动在幕后自动编写的命令行直接调用 的,而无需使用平台的外壳作为中介(这种方式例如,Python的os.system()调用就可以了)-除非发生这种情况,否则目标可执行文件本身要求将外壳程序作为执行解释器,就像批处理文件的cmd.exe一样

  • 编写命令行需要选择性双引号嵌入 "个字符的转义。个人论点;通常涉及:

    • 使用双引号("..."),但仅在包含空格(空格)的参数周围。
    • 将嵌入式双引号转义为\"
    • 值得注意的是,没有其他字符触发双引号或个别转义,即使这些字符对于给定的 shell 可能具有特殊含义。
  • 尽管这种方法对大多数外部程序都有效,但批处理文件 却不可靠:

    • 不幸的是, cmd.exe不会将参数视为 literal ,但会将其解释为您已在中提交了批处理文件交互式控制台(命令提示符)。

    • 结合了命令行的构成方式(如上所述),这导致了很多方式,导致参数可能被误解并完全破坏了调用。

      • 主要问题是,cmd.exe看到的在命令行中以不带引号结尾的自变量可能会中断该调用,即它们是否包含诸如&|><之类的字符。 即使调用不中断,诸如^之类的字符也可能会被误解

        • 有关有争议的论点的具体示例,请参见下文。
      • 尝试使用嵌入引用 解决调用方上的问题-例如,使用'"^^^^^"作为Python中的一个参数-不起作用 ,因为大多数语言(包括Python)都使用\"来使"字符躲在幕后,{ {1}}不能 识别(它只能识别cmd.exe)。

        • 从理论上讲,您可以在无空格的参数中使用""逃逸单个字符,但这样做不仅麻烦,而且仍然无法解决所有问题-参见下文。
      • Jeb's answer值得称赞的是在批处理文件内解决了其中的一些问题 ,但这非常复杂,也无法解决所有问题-请参阅下一点。

    • 无法解决以下基本限制

      • ^基本上不能处理带有嵌入式换行符(换行符)的参数

        • 只需在遇到的第一个换行符处 stops 即可解析参数列表。

        • CR(cmd.exe)个字符。孤立地将其删除。

      • 不能禁止将0xD作为环境变量引用(例如%)的一部分的解释

        • %OS%无济于事,因为奇怪的是,不幸的是,交互式 cmd.exe会话的解析规则适用于(!),这是抑制扩展的唯一方法是采用“变量名破坏者技巧”,例如%^ OS%,它仅在 unquoted 自变量中起作用-在 double-quoted 自变量中,您根本无法避免扩展。

        • 如果您的环境很幸运,变量可能不存在;然后,令牌将被单独保留(例如%%%NoSuchVar%(请注意,%No Such Var% 确实支持带空格的变量名)。

无空格参数的示例,这些参数会中断批处理文件的调用或导致值的不必要更改

  • cmd.exe

      {li> ^^^^^(无引号的字符串)是^的转义字符,用于转义 next 字符,即将其视为 literal ;因此,cmd.exe代表一个文字上的单个^^,因此上面的结果为^,最后一个^^被丢弃
  • ^

    • a|b分隔管道中的命令,因此|将尝试将cmd.exe之前的命令行部分通过管道传递给名为|的命令,并且调用很可能会 break ,或者更糟糕的是,将无法按预期的方式运行 并执行不应执行的操作。

      • 要执行此操作,您需要在Python端将参数定义为b(原文如此)。
    • 请注意,'a^^^|b'不会受到影响,因为嵌入的空格会在Python端触发双引号,并在{{内使用a & b 1}}是安全的。

    • 存在类似问题的其他字符是&

答案 1 :(得分:0)

有趣的问题,但这很棘手。

主要问题是%*不能在此使用,因为它修改了内容或完全依赖于内容而失败。

要获取未修改的argv,应使用类似Get list of passed arguments in Windows batch script (.bat)的技术。

@echo off
SETLOCAL DisableDelayedExpansion

SETLOCAL
for %%a in (1) do (
    set "prompt=$_"
    echo on
    for %%b in (1) do rem * #%*#
    @echo off
) > argv.txt
ENDLOCAL

for /F "delims=" %%L in (argv.txt) do (
  set "argv=%%L"
)
SETLOCAL EnableDelayedExpansion
set "argv=!argv:*#=!"
set "argv=!argv:~0,-2!"
REM argv now contains the unmodified content of %* .

c:\dev\Python35-32\python.exe args.py !argv!

这可用于构建有限制的包装器。
根本无法获得回车费。
当前无法以安全的方式获取换行符