【问题标题】:Retrieve %ERRORLEVEL% with complicated call in FOR loop通过 FOR 循环中的复杂调用检索 %ERRORLEVEL%
【发布时间】:2016-03-20 07:40:25
【问题描述】:

在 Windows 批处理文件中,当程序调用包含空格和多个选项时,我需要将程序执行的结果检索到变量中。经过多次讨论,使用CALL找到了解决方法:

FOR /F "delims=" %%G IN ('CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah"') do set foo=%%G

请参阅以下问题以了解更多详细信息并了解上下文:

Retrieve command output to variable when command has spaces

实际上,批处理文件调用 PostgreSQL 9.3,如下所示:

SET PSQL_EXE=C:\Program Files\Foo\Bar\PostgreSQL\9.3\bin\psql.exe
SET FOO=1
REM psql query should result in a 0 or 1 based on the mydbproperty table value
FOR /F %%G IN ('call "%PSQL_EXE%" -U pguser -d MyDB -p %PG_PORT% -c "select string_value from mydb.uri as uri, mydb.property as prop where uri.id = prop.property_uriid and uri.namespace_uri='http://example.com/foo/bar/' and uri.simplename = 'fooBar'" -q -t -w') DO SET FOO=%%G
REM next line doesn't have ERRORLEVEL set
IF !ERRORLEVEL! NEQ 0 EXIT !ERRORLEVEL!

不幸的是,这种格式似乎会产生一个单独的cmd 实例,因此调用 pgsql 时发生的任何错误(例如缺少密码文件)都不会被传回,%ERRORLEVEL% 也不会被设置。具体是 pgsql 打印出来:

psql: fe_sendauth: no password supplied

然而%ERRORLEVEL%(和!ERRORLEVEL!)仍然是0

有关更多信息,请参阅以下问题:

PSQL Error Level in Batch For Loop

所以现在的问题是如何找出 %ERRORLEVEL%,因为我已经成功获得了psql 的响应。我不想写一些临时文件——我想在内存中做所有事情。

(请注意,是的,我尝试从数据库中查询并存储在FOO 中的值将是01;这并不重要,但它确实使事情变得更多令人困惑。)

我试图通过简单地查看%%G 中返回的内容来测试 Aacini 提出的解决方案:

FOR /F "delims=" %%G IN ('"CMD /V:ON /C CALL "%PSQL_EXE%" -U user -d MyDB -p %PG_PORT% -c "select string_value from mydb.uri as uri, mydb.database_property as prop where uri.id = prop.property_uriid and uri.namespace_uri='http://example.com/foo/bar/' and uri.simplename = 'fooBar'" -q -t -w ^& ECHO !ERRORLEVEL!"') DO (
  ECHO %%G
)

因为 psql 找不到密码文件,这将向 stderr 打印一个错误(如预期的那样)并且ECHO 输出0---所以ERRORLEVEL 没有被发送回DO 子句.但是,如果我从FOR 循环中分离出命令并直接运行它,它会显示ERRORLEVEL (2) 就好了!

"%PSQL_EXE%" -U user -d MyDB -p %PG_PORT% -c "select string_value from mydb.uri as uri, mydb.database_property as prop where uri.id = prop.property_uriid and uri.namespace_uri='http://example.com/foo/bar/' and uri.simplename = 'fooBar'" -q -t -w
ECHO !ERRORLEVEL!

更新:在被告知如果我已经打开延迟扩展,我需要逃避转义后,我接受了给定的答案并对其进行了更新,以考虑到我正在调用的程序不会返回一个值,如果它会产生错误。所以这就是我所做的;首先我确保两个变量是未定义的:

SET "var1="
SET "var2="

然后我使用@Aacini 和@jeb 给出的循环,但在循环内我这样做:

if NOT DEFINED var1 (
  SET "var1=%%G"
) else (
  SET "var2=%%G"
)

然后在循环结束后在循环外:

if DEFINED var2 (
  ECHO received value: !var1!
  ECHO ERRORLEVEL: !var2!
) ELSE (
  ECHO ERRORLEVEL: !var1!
)

这似乎行得通。 (难以置信——什么体操!)

【问题讨论】:

  • 在您的原始问题中,我发布了使用短路径的答案。您是否尝试过这个而不是 call 命令?此外,您可能希望看到这个问题:stackoverflow.com/questions/34226971/…
  • 请把foo.bat的代码也贴出来。
  • @Squashman,我已经改进了示例以使用我正在使用的实际可执行文件,以及几乎相同的参数。您可以通过将%PSQL_EXE% 更改为指向您选择的某个批处理文件来重现此问题,但请注意路径应包含空格以准确反映问题。
  • 我想您在 FOR /F 之前启用了延迟扩展,那么您必须将行修改为 FOR /F ... ('"cmd /V:on .... ^^^& echo ^!ERRORLEVEL^!"') DO (
  • 是的,延迟扩展已经开启。我将进一步试验@jeb 的附加信息。

标签: windows batch-file for-loop psql errorlevel


【解决方案1】:

为了做到这一点,必须结合几个技巧。 for /F 集合中的批处理文件在新的 cmd.exe 上下文中执行,因此我们需要一种方法来在此类命令结束时报告其错误级别。唯一的方法是首先通过显式放置的cmd.exe /C 执行所需的批处理文件,然后在它之后插入一个报告其错误级别的命令。但是,放在for /F 集合中的命令是在命令行上下文中执行的,而不是在批处理文件上下文中执行的,所以需要在显式 cmd.exe。之后,一个简单的echo !errorlevel! 显示批处理文件返回的错误级别。

@echo off
setlocal

set "foo="
FOR /F "delims=" %%G IN ('"CMD /V:ON /C CALL "path with spaces\foo.bat" "blah blah='foobar' blah" ^& echo !errorlevel!"') do (
   if not defined foo (
      set "foo=%%G"
   ) else (
      set "errlevel=%%G"
   )
)

echo String output from foo.bat: "%foo%"
echo Errorlevel returned by foo.bat: %errlevel%

这是“带空格的路径\foo.bat”:

@echo off
echo String from foo.bat
exit /B 12345

这是之前代码的输出:

String output from foo.bat: "String from foo.bat"
Errorlevel returned by foo.bat: 12345

如果之前在主批处理文件中已启用延迟扩展,则必须使用完全不同的语法。
^!ERRORLEVEL^! 有必要避免!ERRORLEVEL!在执行 FOR 行之前,在批处理文件的上下文中进行评估。
^^^& 是将单个 ^& 带入内部 cmd /V:ON 表达式所必需的

@echo off
setlocal EnableDelayedExpansion

set "foo="
REM *** More carets must be used in the next line ****
FOR /F "delims=" %%G IN ('"CMD /V:ON /C CALL "path with spaces\foo.bat" "blah blah='foobar' blah" ^^^& echo ^!errorlevel^!"') do (
   if not defined foo (
      set "foo=%%G"
   ) else (
      set "errlevel=%%G"
   )
)

编辑回复 OP 的 cmets

我的第一个解决方案假设程序总是输出一行,所以它从第二个输出行获取错​​误级别。但是,新规范表明当程序因为错误而结束时,第一行输出不会被发送。修复这个细节很容易,更正的代码如下。

@echo off
setlocal EnableDelayedExpansion

set "foo="
FOR /F "delims=" %%G IN ('"CMD /V:ON /C CALL "path with spaces\foo.bat" "blah blah='foobar' blah" ^^^& echo errlevel=^!errorlevel^!"') do (
   set "str=%%G"
   if "!str:~0,8!" equ "errlevel" (
      set "errlevel=!str:~9!"
   ) else (
      set "foo=%%G"
   )
)

echo String output from foo.bat: "%foo%"
echo Errorlevel returned by foo.bat: %errlevel%

但是,原始规范表明执行的程序是一个名为"C:\path with spaces\foo.bat" 的BATCH 文件。当执行的程序是所请求的批处理 .BAT 文件时,此代码可以正常工作,但如果执行的程序是 .exe 文件,则它不起作用,正如我在第一条评论中明确指出的那样:“注意 C:\path 带有空格\foo.bat 必须是 .bat 文件!如果此类文件是 .exe",则此方法不起作用。这样,您只需要创建“foo.bat”文件并执行 .exe 和下一行中的 exit /B %errorlevel% 命令。当然,如果你直接通过直接执行.exe程序来测试这个方法,它永远都行不通……

2ND EDIT添加了一些解释

此解决方案的目的是提供一个批处理文件"C:\path with spaces\foo.bat",该文件显示一些输出并返回错误级别值,如下所示:

@echo off
echo String from foo.bat
exit /B 12345

...它可能被另一个同时获得输出和错误级别的批处理文件调用。这可以使用辅助文件以非常简单的方式完成:

@echo off

call "C:\path with spaces\foo.bat" > foo.txt
set errlevel=%errorlevel%
set /P "foo=" < foo.txt

echo String output from foo.bat: "%foo%"
echo Errorlevel returned by foo.bat: %errlevel%

但是,请求表明:“不要写入某个临时文件---我想在内存中做所有事情”。

从另一个命令获取输出的方法是通过FOR /F,像这样:

FOR /F "delims=" %%G in ('CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah"') do set foo=%%G

但是,放置在 FOR /F 集中的批处理文件在新的 cmd.exe 上下文中执行。更准确地说,在前面的 FOR 命令中执行 foo.bat 与下一个代码完全等效

CMD /C CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah" > tempFile & TYPE tempFile & DEL tempFile

tempFile 中的每一行都分配给%%G 可替换参数并执行set foo=%%G 命令。请注意,上一行的执行就像是在命令提示符下输入的一样(命令行上下文),因此有几个命令在此上下文中不起作用(例如 goto/call to a label, setlocal/ endlocal 等)和延迟扩展设置为 cmd.exe 的初始值,通常禁用。

为了简化下面的描述,我们使用了一个更短但相似的FOR /F 命令,下面显示了执行foo.bat 的等效内部代码:

FOR /F %%G in ('CALL "foo.bat"') do set foo=%%G
-> CMD /C CALL "foo.bat"                <- we also omit the "tempFile" parts

这样,当这样的命令结束时,我们需要一个方法来报告foo.bat的错误级别。唯一的方法是首先通过显式放置的cmd.exe /C 执行所需的批处理文件,然后在它之后插入一个报告其错误级别的命令。那就是:

FOR /F %%G IN ('"CMD /C CALL foo.bat ^& echo !errorlevel!"') do set foo=%%G
-> CMD /C "CMD /C CALL foo.bat ^& echo !errorlevel!"

请注意,& 符号必须以这种方式转义:^&amp;;否则它将错误地拆分放置在FOR /F 命令集中的命令。在上一行中,foo.batecho !errorlevel! 命令都是通过嵌套的 CMD /C 执行的。

但是,FOR /F 集合中的命令如前所述在命令行上下文中执行,因此需要在显式 cmd.exe 中启用延迟扩展(/V:ON 开关);否则,echo !errorlevel! 将不起作用:

FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat ^& echo !errorlevel!"') do set foo=%%G
-> CMD /C "CMD /V:ON /C CALL foo.bat ^& echo !errorlevel!"

好的。请注意,前面的描述假定执行 FOR /F 命令时延迟扩展已禁用。如果启用,会有哪些变化? 1. !errorlevel! 被当前错误级别值替换,2. 任何用于转义字符的插入符号都被删除。这些更改在解析行时完成, FOR /F 命令执行之前。让我们详细了解这一点:

SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat ^& echo !errorlevel!"') do set foo=%%G
-> After parsed:
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat & echo 0"') do set foo=%%G

上一行发出错误,因为未转义的&amp;,像往常一样。我们需要同时保留感叹号和 & 符号的 插入符号。保留感叹号很容易:^!errorlevel^!,但我们如何保留 ^&amp; 中的插入符号?如果我们使用^^&amp;,第一个插入符号保留第二个插入符号,但下一个要解析的字符是&amp;,并且出现通常的错误!所以,正确的做法是:^^^&amp;;第一个插入符号保留第二个插入符号并给出^^&amp; 保留与符号:

SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat ^^^& echo ^!errorlevel^!"') do set foo=%%G
-> After parsed:
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat ^& echo !errorlevel!"') do set foo=%%G

【讨论】:

  • Aacini,恐怕我无法让它工作。整个FOR 正文被跳过。我更新了问题中的示例,以更好地反映现实生活中发生的事情。您可能会更改 %PSQL_EXE% 以指向您选择的某个批处理文件,但请注意路径应包含空格以准确反映问题。
  • 我建议您复制我完全相同的代码并运行它以检查它是否正常工作。之后,逐一插入修改,直到确定导致问题的步骤。注意C:\path with spaces\foo.bat必须是一个.bat文件!如果此类文件是 .exe,则此方法不起作用
  • 为什么!errorlevel!会在for /F创建的cmd实例中展开,虽然!s没有转义?
  • @aschipfl:请注意,批处理文件启用延迟扩展,因此!s 只是作为文本传递,直到内部CMD /V:ON /C ... 执行它们跨度>
  • 啊,是的,当然,对不起;我刚在开头看到setlocal 并立即发表评论——显然太仓促了......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-14
  • 1970-01-01
  • 2019-11-29
  • 2019-01-23
  • 1970-01-01
相关资源
最近更新 更多