Для этого нужно совместить несколько трюков. Пакетный файл, помещенный в набор 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%
Это "путь с пробелами\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"
)
)
EDIT: Ответить на комментарии OP
Мое первое решение предполагает, что программа всегда выводит одну строку, поэтому она берет уровень ошибки из второй строки вывода. Однако новая спецификация указывает, что когда программа завершается из-за ошибки, первая строка вывода не отправляется. Исправить эту деталь очень просто, исправленный код ниже.
@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%
Однако исходная спецификация указывает, что исполняемая программа представляет собой BATCH-файл с именем "C:\path with spaces\foo.bat"
. Этот код правильно работает, когда исполняемая программа представляет собой пакетный файл .BAT, как запрошено, но не работает, если исполняемая программа представляет собой файл .exe, как я ясно указал в своем первом комментарии: «Обратите внимание, что C:\path с пробелами \foo.bat должен быть файлом .bat! Этот метод не работает, если такой файл является .exe". Таким образом, вам просто нужно создать файл «foo.bat» с выполнением .exe и команды exit /B %errorlevel%
в следующей строке. Конечно, если вы напрямую протестируете этот метод, напрямую запустив программу .exe, он никогда не будет работать...
ВТОРОЕ ИЗМЕНЕНИЕ: добавлены некоторые пояснения
Цель этого решения состоит в том, чтобы предоставить пакетный файл "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. Точнее, выполнение foo.bat
в предыдущей команде FOR полностью эквивалентно следующему коду:
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!"
Обратите внимание, что знак амперсанда необходимо экранировать следующим образом: ^&
; в противном случае он будет неправильно разделять команды, помещенные в набор команды FOR /F
. В предыдущей строке обе команды foo.bat
и echo !errorlevel!
выполняются через вложенный CMD /C
.
Однако команды, помещенные в набор FOR /F, выполняются в контексте командной строки, как объяснялось ранее, поэтому необходимо включить отложенное развертывание (переключатель /V:ON
) в явном 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!"
В порядке. Обратите внимание, что в предыдущем описании предполагалось, что отложенное расширение было ОТКЛЮЧЕНО при выполнении команды 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
Предыдущая строка выдает ошибку из-за неэкранированного &
, как обычно. Нам нужно сохранить как восклицательные знаки, так и знак вставки амперсанда. Сохранить восклицательные знаки несложно: ^!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
person
Aacini
schedule
14.12.2015
%PSQL_EXE%
так, чтобы он указывал на какой-либо пакетный файл по вашему выбору, хотя обратите внимание, что путь должен содержать пробелы, чтобы точно отразить проблему. - person Garret Wilson   schedule 15.12.2015FOR /F ... ('"cmd /V:on .... ^^^& echo ^!ERRORLEVEL^!"') DO (
- person jeb   schedule 17.12.2015do those need to be escaped, too?
да. - person Stephan   schedule 17.12.2015