In a Windows batch file I needed to retrieve the result of a program execution into a variable, when the program call was complicated with spaces and several options. After much discussion, a workaround was found by using CALL
:
FOR /F "delims=" %%G IN ('CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah"') do set foo=%%G
Please see the following question for more details and to understand the context:
Retrieve command output to variable when command has spaces
In reality the batch file calls PostgreSQL 9.3, like this:
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!
Unfortunately it appears that this format results in a separate cmd
instance, so any error that occurred in calling pgsql (e.g. the lack of a password file) does not get passed back and the %ERRORLEVEL%
does not get set. Specifically pgsql prints out:
psql: fe_sendauth: no password supplied
Yet %ERRORLEVEL%
(and !ERRORLEVEL!
) is still 0
.
See the following question for more info:
PSQL Error Level in Batch For Loop
So now the question is how to find out the %ERRORLEVEL%, now that I've succeeding in getting the response of psql
. I'd prefer not to write to some temporary file---I want to do everything in memory.
(Note that, yes, the value I'm trying to query from the database and store in FOO
will be either a 0
or a 1
; not that it matters, but it does make things more confusing.)
I tried to test Aacini's proposed solution by simply seeing what would be returned in %%G
:
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
)
Because psql could not find a password file, this prints an error to stderr (as expected) and the ECHO
outputs 0
---so the ERRORLEVEL
is not getting sent back to the DO
clause. But if I split out the command from the FOR
loop and just run it directly, it shows the ERRORLEVEL
(2
) just fine!
"%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!
Update: After being advised that I need to escape the escapes if I have delayed expansion already turned on, I took the given answer and updated it to take into account that the program I'm calling won't return a value if it generates an error. So here's what I do; first I make sure two variables are undefined:
SET "var1="
SET "var2="
Then I use the loop that @Aacini and @jeb gave, but inside the loop I do this:
if NOT DEFINED var1 (
SET "var1=%%G"
) else (
SET "var2=%%G"
)
Then outside the loop after it has finished:
if DEFINED var2 (
ECHO received value: !var1!
ECHO ERRORLEVEL: !var2!
) ELSE (
ECHO ERRORLEVEL: !var1!
)
That seems to work. (Unbelievably---what gymnastics!)
答案 0 :(得分:3)
为了做到这一点,必须结合几个技巧。放置在for /F
集中的批处理文件在新的cmd.exe上下文中执行,因此当这样的命令结束时,我们需要一种方法来报告其错误级别。唯一的方法是首先通过显式放置cmd.exe /C
执行所需的批处理文件,然后 插入报告其错误级别的命令。但是,放置在for /F
集中的命令在命令行上下文中执行,而不是在批处理文件上下文中执行,因此必须启用延迟扩展(/V:ON
在显式的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%
这是#34;路径,包含空格\ 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"
)
)
@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%
编辑:回复OP的评论
我的第一个解决方案假设程序总是输出一行,所以它从第二个输出行获取了errorlevel。但是,新规范表示当程序因错误而结束时,不会发送第一个输出行。修复此细节非常容易,修正后的代码如下所示。
"C:\path with spaces\foo.bat"
但是,原始规范表明执行的程序是一个名为exit /B %errorlevel%
的BATCH文件。当执行的程序是请求的Batch .BAT文件时,此代码正常工作,但如果执行的程序是.exe文件则不起作用,正如我在第一条评论中明确指出的那样:"注意C:\ space \ foo.bat的路径必须是.bat文件!如果此类文件是.exe",则此方法不起作用。这样,你只需要创建" foo.bat"在下一行执行.exe和"C:\path with spaces\foo.bat"
命令的文件。当然,如果你通过直接执行.exe程序直接测试这个方法,它永远不会工作......
2ND EDIT :添加了一些解释
此解决方案的目的是提供显示某些输出的批处理文件@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
然而,请求表明:&#34;不写入某个临时文件---我想在内存中做所有事情&#34;。
从另一个命令获取输出的方法是通过FOR /F "delims=" %%G in ('CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah"') do set foo=%%G
,如下所示:
foo.bat
但是,放置在FOR / F集中的批处理文件在新的cmd.exe上下文中执行。更准确地说,先前FOR命令中CMD /C CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah" > tempFile & TYPE tempFile & DEL tempFile
的执行完全等同于到下一个代码:
tempFile
%%G
中的每一行都分配给set foo=%%G
可替换参数,并执行FOR /F
命令。请注意,前一行的执行就像在命令提示符处输入的那样(命令行上下文),因此在此上下文中有几个命令不起作用(例如goto / call to a label,setlocal / endlocal等)和延迟扩展设置为cmd.exe的初始值,通常是禁用的。
为了简化以下描述,我们使用一个更短但相似的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!"
执行所需的批处理文件,然后 插入报告其错误级别的命令。那就是:
^&
请注意,&符号必须以这种方式转义:FOR /F
;否则会错误地拆分放置在foo.bat
命令集中的命令。在前一行中,echo !errorlevel!
和CMD /C
命令都是通过嵌套的/V:ON
执行的。
但是,FOR / F集中的命令是在命令行上下文中执行的,如前所述,因此必须在显式cmd.exe中启用延迟扩展(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!"
不起作用:
!errorlevel!
确定。请注意,前面的描述假设执行FOR / F命令时延迟扩展为DISABLED。如果启用了哪些更改? 1。 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
将替换为当前的错误级别值, 2。将删除用于转义字符的所有插入符号。这些更改在解析行时完成,在执行FOR / F命令之前完成。让我们详细了解这一点:
&
上一行发出错误,因为未转义的^!errorlevel^!
像往常一样。我们需要保留&符号的感叹号和插入符号。保留感叹号很简单:^&
,但我们如何保留^^&
中的插入符?如果我们使用&
第一个插入符号保留第二个插入符号,但要解析的下一个字符仅为^^^&
,并且会出现常见错误!所以,正确的方法是:^
;第一个插入符号保留第二个插入符号并提供^&
,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
保留&符号:
{{1}}