1

Тема: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Адекватное повердение команды: завершение скрипта и возврат кода ошибки 100


call nonexistent_command || (
    echo error
    exit /b 100
)

Неадекватное поведение команды: завершение скрипта и интерпретатора CMD.EXE


goto nonexistent_label || (
    echo error
    exit /b 200
)

Так работает, но неожиданное поведение предыдущего примера разочаровывает:


goto nonexistent_label
if errorlevel 1 (
    echo error
    exit /b 200
)
( 2 * b ) || ! ( 2 * b )

2

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Rumata пишет:

Так работает<...>:


goto nonexistent_label
if errorlevel 1 (
    echo error
    exit /b 200
)

Не работает. Можно проверить, вставив перед "exit" паузу, которая не выполнится.

Похоже, что команда "goto" вовсе не устанавливает "ERRORLEVEL". При отсутствии метки выполнение командного файла прекращается во избежание ошибок (если, конечно, нет команд, связанных с "goto" в строке при помощи "&" или "||", но перехода на выполнение команд новой строки не будет, даже если приписать "& goto existent_label" или "|| goto existent_label").

Предложу разве что такой вариант (если, конечно, не писать комментарии через пробел после метки):


findstr /X :nonexistent_label "%~f0" >nul &&^
goto nonexistent_label || (
echo error
exit /b 200
)

3 (изменено: Rumata, 2013-12-22 13:09:19)

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Действительно, не работает. Видимо я что-то напутал. К сожалению я не сохранил резуьтатов тех своих экспериментов.

Судя по всему, команда goto очень хитрая, вернее ее обработка интерпретатором - cледующая за ней команда exit безусловно завершает и выполняющийся скрипт и сам интерпретатор. Два очередных примера:

Переход на несуществующую метку: вторая строка не выполнится, хотя сообщение FAILED! выведено будет.


goto bogus || echo FAILED!
echo Can you see me?

Выполнение несуществуюшей команды: вторая строка выполняется тоже


bogus || echo FAILED!
echo Can you see me?

Я предполагаю, что это такой баг. Следующий пример объясняет проблему:


goto bogus || (
    echo FAILED!
    call :error
)

echo OK
goto :EOF

:error
echo Exiting
exit /b 100

Для объяснения проблемы используем понятие контекст. Назовем состояние ожидания интерпретаторлм ввода команд в консоли контекстом консоли. А состояние исполнения запущенного скрипта - контекстом скрипта.

Интерпретатор, находясь в контексте скрипта, считывается в свой буфер очеденую команду (составную в данном случае), пытается выполнить ее в контексте же скрипта, не находит метку bogus и закрывает скрипт. При этом он переходит в контекст консоли, но в буфере сохранились команды, которые выпоняются, но уже в контексте консоли.

Пользы от этого никакой. К сожалению.

( 2 * b ) || ! ( 2 * b )

4 (изменено: wisgest, 2013-12-22 23:15:48)

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

По-моему, логично: в отличии от CALL или вызова отсутствующей команды, GOTO при успешном выполнении заведомо предполагает, что следующая по порядку строка не должна быть следующей командой, так почему ей быть при неудаче?

Код ошибки, однако, устанавливается:

call :SUB
echo %errorlevel%
exit /b

:SUB
goto xxx
echo xxx


UPD:  Как видим, контекстом может быть не обязательно состояние ожидания интерпретатором ввода команд и в определённом контексте (вспомогательный промежуточный CALL) пример

Rumata пишет:
goto nonexistent_label || (
    echo error
    exit /b 200
)

вполне мог бы работать.

5

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Если интерес представляет именно установка собственного кода ошибки, а через EXIT/B это сделать нельзя, т.к.  exit получается двойной, то код ошибки можно задать так:

cmd /c exit 200

6 (изменено: wisgest, 2015-06-03 15:28:57)

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Хотя все заинтересовавшиеся поднятым вопросом сами уже во всём разобрались, вернусь к нему, чтобы попытаться подвести итоги и сделать кое-какие уточнения.

wisgest пишет:

Код ошибки, однако, устанавливается…

— уточню: код 1 устанавливается, если GOTO является одиночной комадой или последней командой внутри составной, или непосредственно за ними следуют оператор «||» или оператор «&&», после которого нет команд;
в противном случае, даже если следующая за GOTO (или за «(GOTO && что-то)») команда — лишь комментарий или пустая команда (ничего нет после «&»), текущее значение ERRORLEVEL не меняется.

wisgest пишет:

Как видим, контекстом может быть не обязательно состояние ожидания интерпретатором ввода команд…

Вот совсем небольшой пример:

call :SUB
exit /b
:SUB
goto NOWHERE& echo %0& call echo %%0
echo Эта строка не будет выполнена.

— хотя первое ECHO и выведет «:SUB», т.к. подстановка %0 произведена заранее, команда с отложенным раскрытием покажет истинный контекст. Сказанное остаётся верным и при выносе подпрограммы в отдельный файл.

Rumata пишет:

Пользы от этого никакой. К сожалению.

Не знаю, какая предпологалась польза, но если внешним контекстом является «голый» CMD.EXE, выполняющий команды из своего входного потока или ключей «/C» или «/K», то пользы я пока тоже не вижу;
если же «неправильное» GOTO встретится внутри подпрогаммы пакетного файла (находящейся в том же файле или вынесенной в отдельный), то возможны, по крайней мере, две небольшие пользы:
1) возврат не только из текущей, но и из вызвавшей её (под)программы с установкой кода возврата вызвавшей

goto NOWHERE 2>nul& exit /b 100

2) возврат из подпрограммы не в точку вызова, а на заданную метку вызвавшего пакетного файла (того же или отдельного)

goto NOWHERE 2>nul& goto LABEL

(в обоих случаях, если использовать несколько GOTO в никуда, то возможно поднятие на несколько уровней).

Второй приём можно было бы использовать, например, в теме «CMD/BAT: Возможность создания heredoc» для устранения избыточности из построения

call :heredoc LABEL & goto LABEL

Наконец, следует заметить, что, при преднамеренном использовании GOTO в никуда, лучше всего после GOTO не употреблять вообще ничего:
1) это нагляднее;
2) нет опасности попасть в существующую метку;
3) нет необходимости интерпретатору просматривать командный файл в поиске несуществующего.

При этом возможны два случая, отличающиеся лишь сообщением об ошибке (чтобы его увидеть замените NUL на CON):
1) «Не удается найти указанную метку пакетного файла - », если после GOTO есть хоть один пробел или приравненный к нему символ, например,

goto 2>nul

2) «Для команды GOTO в пакетном файле не указана метка перехода.», если лишних символов нет,

(goto) 2>nul
2>nul goto

7 (изменено: wisgest, 2015-06-06 19:10:45)

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Определение, вызван ли командный файл как подпрограмма из другого и из какого именно:

if not "%~1"=="/r" (goto)2>nul& call "%~f0" /r "%%~f0" %*
if %2=="%%~f0" (
    echo CMD.EXE
) else (
    echo %2
)

(или воспользоваться переменной среды во избежание сдвига аргументов командной строки:

+ code
if not defined caller (
    (goto)2>nul
    set "caller=."& call "%~f0" "%%~f0"
    call "%~f0" %*
)
if "%caller%" == "." set "caller=%~1"& exit /b

if "%caller%"=="%%~f0" (
    echo CMD.EXE
) else (
    echo "%caller%"
)

set caller=
exit /b

).

Зачем:
1) при вынесении в отдельный файл подпрограммы, использующей вызывающий командный файл как источник данных. В качестве примера такой подпрограммы ещё раз сошлюсь на тему «CMD/BAT: Возможность создания heredoc». При этом из CALL будет достаточно просто убрать двоеточие;
2) для выбора поведения при завершении работы: если управление возвращается в другой командный файл — ничего не предпринимаем, иначе проверяем %CMDCMDLINE% на ключ /C и, если необходимо, вызываем PAUSE для просмотра пользователем итога работы или вообще перезапускаем CMD.EXE (с помощью START/B, чтобы не плодить процессы).

Предлагаю переименовать эту тему, например, сократив до «Поведение goto nonexistent_label».

8

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Зачем

Мыслим параллельно. Замечал некие странности при прерывании скрипта по CTRL-C.

( 2 * b ) || ! ( 2 * b )

9 (изменено: wisgest, 2015-06-07 00:56:33)

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Rumata пишет:

Замечал некие странности при прерывании скрипта по CTRL-C.

А можно чуть подробнее?


Сам заметил вот что: в вызове

(goto) & команды_1 & call sub1 & команды_2 & call sub2 & команды_3

Ctrl-C не работает внутри оставшейся после (GOTO) части составной команды, т.е. в «команды_1», «команды_2», «команды_3» (независимо от внешнего контекста);
но работает (влияя на скрипт в целом, что есть хорошо) в вызываемых из неё подпрограммах («sub1» и «sub2»);

а также может сработать при возврате на внешний уровень (но не самый последний уровень «голого» CMD.EXE), если Ctrl-C было нажато в оставшейся части составной команды после (GOTO) и всех последующих вызовах подпрограмм из этой части, т.е в «команды_3»,
но это, похоже, зависит от того, в какой последовательность в «команды_3» (и сразу после возврата из них?) сочетаются PAUSE и SET/P... (если есть одни лишь PAUSE, то срабатывает).


Как мне пока представляется, всё это совершенно не критично, т.к. внутри оставшейся после (GOTO) части составной команды никаких задержек в виде PAUSE и SET/P использовать не следует.

10

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

А можно чуть подробнее?

Запустите голый скрипт из спойлера. У командного интепретатора сносит крышу и он перестает показывать стандарное приглашение командной строки. Причем это никак не исправляется.

( 2 * b ) || ! ( 2 * b )

11 (изменено: Rumata, 2015-06-07 17:44:04)

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Магия с goto может быть сокращена до:


@echo off

if not defined caller (
    (goto) 2>nul
    call set "caller=%%~f0"

    call "%~f0" %*
)

:: Полезный код
echo:%*
( 2 * b ) || ! ( 2 * b )

12 (изменено: Rumata, 2015-06-07 18:46:11)

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

И так, я нашел способ избежать проблем с "крышесносящим" поведением cmd.exe.


if not defined CMDCALLER (
    (echo on & goto) 2>nul
    call set "CMDCALLER=%%~f0"
    call "%~f0" %*
    set "CMDCALLER="
)
if /i "%CMDCALLER%" == "%%~f0" set "CMDCALLER="

:: полезный код начинается здесь
echo:%*
( 2 * b ) || ! ( 2 * b )

13

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Rumata, спасибо за интересное наблюдение. Я в основном проверял без ECHO OFF, но по крайней мере один раз такое встретил, не придав никакого внимания: просто ввёл в ответ ECHO ON, не задумавшись, что обычно же при возврате из пакетного файла в командную строку восстанавливается прежний режим.

Rumata пишет:

И так, я нашел способ избежать проблем с "крышесносящим" поведением cmd.exe.

Это исходя из предположения, что режим ECHO был включен.
Для общего случая лучше перенести @ECHO OFF в «полезный код», а в префиксе использовать «@».

14

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Для общего случая лучше перенести @ECHO OFF в «полезный код», а в префиксе использовать «@».

Согласен, так будет лучше.


:: http://forum.script-coding.com/viewtopic.php?pid=94390#p94390
@if not defined CMDCALLER @(
    (echo on & goto) 2>nul
    call set "CMDCALLER=%%~f0"
    call "%~f0" %*
    set "CMDCALLER="
)
@if /i "%CMDCALLER%" == "%%~f0" set "CMDCALLER="


:: полезный код начинается здесь
@echo off

echo:%*
( 2 * b ) || ! ( 2 * b )

15

Re: CMD/BAT: Неадекватное поведение goto nonexistent_label || exit /b

Rumata пишет:

Согласен, так будет лучше.

    (echo on & goto) 2>nul

— ECHO ON осталась на месте.

Rumata пишет:

Магия с goto может быть сокращена до…

Хотя в примере, предназначенном для показа общей идеи

+ (quote)

Вот совсем небольшой пример <…> — хотя первое ECHO и выведет «:SUB», т.к. подстановка %0 произведена заранее, команда с отложенным раскрытием покажет истинный контекст.

я использовал CALL для отложенного раскрытия,
но во в меньшей степени абстрактном примере, могущем служить каркасом конкретному, я преднамеренно избежал использования CALL SET.

+ Несбывшаяся надежда

Возможность сохранения состояния ECHO после выхода из пакетного файла, вызвало у меня предположение, что подобное возможно и с SETLOCAL — не столько для создания локальной среды, сколько для переключения состояния DelayedExpansion без запуска нового процесса CMD.EXE с другими ключами. Похоже, нельзя. Может кто другой осилит.