批处理 - 在带有竖线(“|”)的脚本中调用脚本会导致错误

Batch - Call script inside script with vertical bar ("|") causes error

如果一个脚本在调用 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. 我该如何解决?

我不同意link中的一些描述。 请参阅 exit /? 准确的帮助说明。

  • exit 退出解释器。
  • exit 1 以退出代码 1 退出解释器。
  • exit /bgoto :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的工作原理就像走钢丝一样。 就在你认为你知道的时候,从绳子上掉下来。

我认为根本没有必要将参数附加到您的 %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

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

我发现对于嵌套脚本,如果您在 EXIT /B 1 之前不 ENDLOCAL,您将不会在下一级脚本。

如果您只 EXIT /B 0,那么这无关紧要,因为默认的 return 代码是 0