【问题标题】:Batch - Call script inside script with vertical bar ("|") causes error批处理 - 使用竖线(“|”)在脚本内调用脚本会导致错误
【发布时间】:2018-06-16 04:35:07
【问题描述】:

如果一个脚本在调用EXIT 1 时应该在子程序中退出而不关闭终端。我在那里使用this if 再次调用脚本。

这一直很好,直到我现在发现带引号的竖线作为参数"!" 存在一些问题。我收到一条错误消息,指出命令拼写错误。

下面是失败的脚本部分:

@ECHO OFF
    SETLOCAL DISABLEDELAYEDEXPANSION

    IF "%selfWrapped%"=="" (
        REM this is necessary so that we can use "exit" to terminate the batch file,
        REM and all subroutines, but not the original cmd.exe
        SET selfWrapped=true
        %ComSpec% /s /c ""%~0" %*"
        GOTO :EOF
    ) 

    echo %*

    ENDLOCAL
EXIT /B 0

呼叫:

test.cmd "hello world" "|"

预期输出:

"hello world" "|"

我检查了IF%* 的值,但使用竖线以及任何其他带引号的字符串似乎完全合法。

所以...

  1. 为什么脚本会失败?
  2. 我该如何解决?

【问题讨论】:

  • 如果您直接运行 bat 或使用调用,则可以正常工作:"%~0" %*CALL "%~0" %*
  • 你试过这个吗:"%ComSpec%" /S /C "%~0" %*
  • @Squashman,如备注中所述,新的cmd 实例需要能够使用exit 终止批处理脚本及其所有子例程,因此当前实例不会退出...
  • 这个呢:"%ComSpec%" /S /C ^^^""%~0" %*^^^",甚至"%ComSpec%" /S /C ^^^""%~0" %

标签: batch-file cmd scripting windows-scripting


【解决方案1】:

我不同意链接中的某些描述。 见exit /?准确的帮助说明。

  • exit 退出解释器。
  • exit 1 以 exitcode 1 退出解释器。
  • exit /b 具有与退出的 goto :eof 类似的行为 脚本或称为标签。 Errorlevel 未重置,因此允许 errorlevel 来自上一个命令,之后可以访问 脚本或被调用标签的退出。
  • exit /b 1errorlevel 1 退出脚本或调用的标签。

如果您在 CMD 提示符下奇怪地使用 exit /b,它将退出解释器。


主要代码:

@ECHO OFF
    SETLOCAL DISABLEDELAYEDEXPANSION

    SET args=%*
    SET "self=%~f0"

    IF "%selfWrapped%"=="" (
        @REM this is necessary so that we can use "exit" to terminate the batch file,
        @REM and all subroutines, but not the original cmd.exe
        SET "selfWrapped=true"

        SETLOCAL ENABLEDELAYEDEXPANSION
        ECHO !ComSpec! /s /c ""!self!" !args!"
        "!ComSpec!" /s /c ""!self!" !args!"
        GOTO :EOF
    )

    ECHO(%*
EXIT /B 0

同时使用GOTO :EOFEXIT /B 0 将退出脚本。 ENDLOCAL 隐含在脚本退出时。 显式使用 ENDLOCAL 是为了当你想结束 当前本地范围并继续执行脚本。与往常一样,成为 始终明确是一种选择。

%* 设置为args 可以保持双引号配对。 引用即set "args=%*" 有时会导致问题 虽然不使用引号允许代码注入,即 参数"arg1" ^& del *.*。如果del *.* 不去 在set 行执行,那么它可能会发生 在ComSpec 行。对于这个例子,我选择了不引用。 所以,这是一种选择。

您在脚本开始时使用了禁用扩展。那 保存 ! 参数,这很好。在你执行之前 ComSpec 不过,启用延迟扩展并使用 !args! 现在受到解释器的保护,现在看不到| 或任何其他可能引发错误的特殊字符。

您的脚本因| 参数被暴露而失败。

C:\Windows\system32\cmd.exe /s /c ""test.cmd" "  | ""

以上是ComSpec 行的回显评估 设置@ECHO ON。注意引号的配对 即""" """。注意插入的额外间距 | 字符周围,因为解释器不考虑 它作为带引号的字符串的一部分。

与回显评估的更新代码更改相比...:

"!ComSpec!" /s /c ""!self!" !args!"

引号之间的字符串保持不变。没有额外的间距 插入到字符串中。回声评估看起来不错, 执行得很好。

免责声明:

表达 CMD 的工作原理就像走一条紧绳。 就在你认为你知道的时候,从绳索上掉下来。

【讨论】:

  • 看看 Jeb 和 Dbenham 对这个问题的看法会很有趣。他们非常了解解析规则。
  • 很好,但请注意延迟扩展仅对父 cmd 实例有帮助,而对子实例没有帮助!无论如何,仍有改进的空间: 1. 您在启用延迟扩展的情况下扩展%-variables,这可能会导致问题,尤其是! 字符;将所有% 扩展更改为! 以确保安全(例如!ComSpec!;然后在启用延迟扩展之前执行set "self=%~0F" 并在以后使用!self! 而不是%~f0)。 2. 将!ComSpec! 放在"" 之间,以允许自定义系统目录包含空格。 3.使用安全的echo语法echo(%*
  • 4.我会转义子cmd 实例化中的外引号,如下所示:"!ComSpec!" /S /C ^""!self!" !args!^",以避免cmd 尝试删除外引号时出现语法错误。 5. 我会删除set args=%* 处的引号,因为在正常情况下,参数已经被正确引用(例如,在进行拖放时,带有空格和特殊字符的参数已经被引用,所以另一对引号无意中将特殊字符暴露给cmd)。在不平衡引号的情况下,不带引号和带引号的分配变体无论如何都可能失败。
  • @aschipf。 1. 完成。 2. 完成。 3. 完成。 4. 如果/c/k 规则#2 适用于cmd /?,CMD 无论如何都会去掉外部引号,因为/s 会强制执行它,而旧的CMD 行为会确保它。我只是看不出在参数外引号上使用^" 是一个更安全的选择。如果它最初通过脚本 OK,那么延迟扩展应该保护它,直到它执行 ComSpec 并再次通过。在应用之前需要进一步说服。 5. 完成。 #. 感谢您的建议,让我保持紧绷状态。
  • @aschipfl 5. 拖放仅引用带有空格的参数,但不适用于 Cat&dog.txt 中的特殊字符
【解决方案2】:

我认为没有必要将参数附加到您的%ComSpec% /s /c ""%~0" %*"

由于您已经使用变量 (selfWrapped) 来检测,如果需要包装器调用,您也可以将参数放入变量中。

set args=%*

然后你可以简单地在你的子实例中使用!args!

@ECHO OFF
    setlocal DisableDelayedExpansion

    IF "%selfWrapped%"=="" (
        @REM this is necessary so that we can use "exit" to terminate the batch file,
        @REM and all subroutines, but not the original cmd.exe
        SET "selfWrapped=true"

        SET ^"args=%*"
        "%ComSpec%" /s /c ""%~f0""
        GOTO :EOF
    )

:Main

    setlocal EnableDelayedExpansion
    ECHO(!args!
EXIT /B 0

现在剩下的唯一问题是set args=%*
如果您无法控制内容,则无法以简单安全的方式访问%*
想想这个批处理调用

myBatch.bat "abc|"
myBatch.bat abc^|
myBatch.bat abc^|--"|"

但你可以使用How to receive even the strangest command line parameters?
Get arguments without temporary file

顺便说一句。您可以保留子进程,也可以退出函数
Exit batch script from inside a function

【讨论】:

    【解决方案3】:

    对上述答案的一个更正。 是的,ENDLOCAL 隐含在脚本末尾,但有一个问题。

    我发现对于嵌套脚本,如果您在 EXIT /B 1 之前没有 ENDLOCAL,您将不会在下一级输出脚本中获得 1 的返回码。

    如果您只使用过EXIT /B 0,那么这无关紧要,因为默认返回码是0

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-11
      • 2015-05-17
      • 2014-12-03
      • 2014-11-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多