完美批量转发参数

Eri*_*ric 4 escaping batch-file

我有一个小的 python 脚本:

# args.py
import sys; print(sys.argv)
Run Code Online (Sandbox Code Playgroud)

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

为了从测试中消除我的 shell,我将调用它:

# args.py
import sys; print(sys.argv)
Run Code Online (Sandbox Code Playgroud)

批处理文件的明显选择

@echo off
python args.py %*
Run Code Online (Sandbox Code Playgroud)

适用于简单情况:

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

但当在任意字符串上尝试时,很快就会崩溃:

@echo off
python args.py %*
Run Code Online (Sandbox Code Playgroud)

是否有可能使bat文件不加修改地传递其参数?


为了证明它不会subprocess导致问题 - 尝试运行上面的命令

>>> test_bat('a', 'b', 'c')
"['args.py', 'a', 'b', 'c']\n"
>>> test_bat('a', 'b c')
"['args.py', 'a', 'b c']\n"
Run Code Online (Sandbox Code Playgroud)

所有测试均按预期运行


类似问题:

mkl*_*nt0 5

简而言之:由于如何解释参数,没有可靠的方法可以通过批处理文件按原样传递参数cmd.exe;请注意,鉴于它cmd.exe是执行批处理文件所需的解释器,因此总是会涉及到,即使您使用不要求 shell 参与的 API 调用批处理文件。

简而言之,问题是:

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

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

    • 使用在幕后自动组成的命令行直接调用目标可执行文件,而不使用平台的 shell 作为中介(例如,Python 的调用方式) - 除非目标可执行文件本身需要该 shell作为执行解释器,就像批处理文件的情况一样。os.system()cmd.exe

  • 编写命令行需要有选择的双引号嵌入字符的转义。 "嵌入各个参数时;通常涉及:

    • 使用封闭双引号 ( "..."),但仅限于包含空格(空格)的参数。
    • 将嵌入的双引号转义为\"
    • 值得注意的是,没有其他字符会触发双引号或单独转义,即使这些字符对于给定的shell可能具有特殊含义。
  • 虽然这种方法适用于大多数外部程序,但它不能可靠地处理批处理文件

    • 不幸的是,cmd.exe它不会将参数视为文字,而是将它们解释为就像您在交互式控制台(命令提示符)中提交了批处理文件调用一样。

    • 结合命令行的组成方式(如上所述),这会导致参数在很多方面被误解并完全破坏调用

      • 主要问题是,在命令行中最终未加引号的cmd.exe参数可能会破坏调用,即如果它们包含诸如&|>之类的字符<。即使调用没有中断,诸如 之类的字符也^可能会被误解

        • 有关有问题的论点的具体示例,请参阅下文。
      • 尝试使用嵌入式引用来解决调用方的问题(例如,'"^^^^^"在 Python 中用作参数)行不通,因为大多数语言(包括 Python)都使用在幕后\"转义字符,而这些字符无法识别(它只识别)。"cmd.exe""

        • 假设,您可以煞费苦心地在无空格的参数中^转义单个字符,但这不仅非常麻烦,而且仍然无法解决所有问题 - 见下文。
      • Jeb 的答案值得称赞的是解决了批处理文件中的一些问题,但它非常复杂,而且也无法解决所有问题 - 请参阅下一点。

    • 无法解决以下基本限制

      • cmd.exe从根本上无法处理带有嵌入换行符(换行符)的参数

        • 解析参数列表只是在遇到的第一个换行符处停止。

        • CR ( 0xD) 字符。被隔离地悄悄移除。

      • 作为环境变量引用(例如, )的一部分的解释%%OS%不能被抑制

        • %%没有帮助,因为,奇怪且不幸的是,交互式cmd.exe 会话的解析规则适用(!),其中抑制扩展的唯一方法是采用“变量名破坏者技巧”,例如 %^OS% ,它只适用于不带引号的参数 - 在双引号参数中,你从根本上无法阻止扩展。

        • 如果环境你很幸运。变量恰好不存在;然后将令牌单独保留(例如,%NoSuchVar%%No Such Var%(请注意,cmd.exe 支持带空格的变量名称)。

无空格参数的示例会破坏批处理文件调用或导致值发生不需要的更改

  • ^^^^^

    • ^在不带引号的字符串中是cmd.exe的转义字符,它转义下一个字符,即,将其视为文字^^因此代表一个文字, single ^,所以上面的结果是^^,最后一个^被丢弃
  • a|b

    • |分隔管道中的命令,因此cmd.exe将尝试将命令行之前的部分传输|到指定的命令b,并且调用很可能会中断,或者更糟糕的是,不会按预期工作执行不应该执行的操作。

      • 为了使其工作,您需要'a^^^|b'在 Python 端将参数定义为 (sic)。
    • 请注意,这a & b不会受到影响,因为嵌入的空格会在 Python 端触发双引号,并且使用&inside"..."是安全的。

    • 其他造成类似问题的角色是& < >