【问题标题】:Perfectly forwarding arguments in batch完美地批量转发参数
【发布时间】:2019-01-16 11:09:51
【问题描述】:

我有一个小的 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')

所有测试都按预期运行


类似问题:

【问题讨论】:

    标签: batch-file escaping


    【解决方案1】:

    简而言之:没有可靠的方法通过批处理文件按原样传递参数,因为 cmd.exe 解释参数的方式;请注意,cmd.exe总是参与其中,因为它是执行批处理文件所需的解释器,即使您使用不请求 shell 参与的 API 调用批处理文件也是如此。

    问题简述:

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

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

      • 直接调用目标可执行文件,使用在幕后自动编写的命令行,而不使用平台的 shell 作为中介(方式例如,Python 的 os.system() 调用确实如此)——除非发生这种情况,否则目标可执行文件本身需要该 shell 作为执行解释器,就像 cmd.exe 用于批处理文件一样。

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

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

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

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

        • 主要问题是在cmd.exe 看到的命令行中以未引用结尾的参数可能中断调用,即如果它们包含诸如&|>< 等字符。 即使调用没有中断,^ 等字符也可能被误解

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

          • 假设,您可以煞费苦心地^-escape individual 个字符在无空格的参数中,但这不仅很麻烦,而且还不能解决所有问题 - 见下文。李>
        • Jeb's answer 值得称道地解决了部分这些问题在批处理文件中,但它相当复杂,也无法解决所有问题 - 请参阅下一点.

      • 无法绕过以下基本限制

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

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

          • CR (0xD) 字符。孤立地被悄悄地移除。

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

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

          • 如果 env.变量恰好不存在;然后单独留下令牌(例如,%NoSuchVar%%No Such Var%(请注意,cmd.exe 确实支持带空格的变量名)。

    中断批处理文件调用或导致值发生意外更改的无空格参数示例

    • ^^^^^

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

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

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

      • 其他有类似问题的字符是& < >

    【讨论】:

      【解决方案2】:

      有趣的问题,但很棘手。

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

      要获得未修改的 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!
      

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

      【讨论】:

      • 这对于与上述相同的测试用例失败,尽管方式不同。 ^^^^^ 现在变为'^^' 而不是'^'
      • @Eric 插入符号不会失败,问题是插入符号在调用批处理之前已删除,因此必须在调用之前引用或转义插入符号
      • 被什么删除了? Python 并没有删除它们,python 和批处理之间唯一的东西就是操作系统
      • @Eric cmd.exe 在它们之间。这将删除插入符号
      • @Eric 批处理文件将由 cmd.exe 解释,它们不能单独运行。 cmd.exe 有其他解释器以外的规则
      猜你喜欢
      • 1970-01-01
      • 2015-08-26
      • 1970-01-01
      • 2013-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-03
      • 1970-01-01
      相关资源
      最近更新 更多