Почему этот пакетный скрипт неожиданно завершает работу?

2

Этот пакетный скрипт завершается, когда %CHECKCONTINUE% получает нулевое значение, не вводя ничего в строке 13 ( SET /p CHECKCONTINUE=Okay to continue? (y/n): ), почему это?

@ECHO OFF
SETLOCAL
TITLE Registry restore script
REM Restores registry settings and disables the cloud

SET %CHECKCONTINUE%=

:listaction
ECHO I'm about to...
ECHO 1.) Remove the registry data that specifies settings for TF2
ECHO 2.) Forcibly disable Steam Cloud.
ECHO.
SET /p CHECKCONTINUE=Okay to continue? (y/n): 

REM No?
IF %CHECKCONTINUE%==n GOTO exit
IF %CHECKCONTINUE%==no GOTO exit

REM Yes?
IF %CHECKCONTINUE%==y GOTO start
IF %CHECKCONTINUE%==yes GOTO start

REM Did they put something else?
IF DEFINED %CHECKCONTINUE% GOTO loop-notvalid

REM Did they not put anything at all?
IF NOT DEFINED %CHECKCONTINUE% GOTO loop-noreply

:start
REM Delete application specific data
REG DELETE HKEY_CURRENT_USER\Software\Valve\Source\tf\Settings /f
REG DELETE HKEY_CURRENT_USER\Software\Valve\Steam\Apps\440 /f

REM Disable Steam Cloud for TF2
REG ADD HKEY_CURRENT_USER\Software\Valve\Steam\Apps\440 /v Cloud /t REG_DWORD /d "0x0" /f

:exit
ENDLOCAL
EXIT

:loop-notvalid
ECHO.
ECHO That's not a valid reply. Try again.
ECHO.
SET %CHECKCONTINUE%=
GOTO listaction

:loop-noreply
ECHO.
ECHO You must enter a reply.
ECHO.
SET %CHECKCONTINUE%=
GOTO listaction
Matthieu Cartier
источник
Что за люди пишут пакетные сценарии в верхнем регистре? (Вопрос стиля кодирования, я знаю, но ...)
grawity
1
Привычка писать сценарии оболочки.
Matthieu Cartier

Ответы:

3

Во-первых, на нескольких строках у вас есть:

SET %CHECKCONTINUE%=

Это не меняет CHECKCONTINUE но использует его значение в качестве имени переменной.

Измените это на:

SET CHECKCONTINUE=

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


if defined также принимает только имя переменной, поэтому вместо

IF DEFINED %CHECKCONTINUE% GOTO loop-notvalid

вы должны использовать:

IF DEFINED CHECKCONTINUE GOTO loop-notvalid

То же относится и к этой строке:

IF NOT DEFINED %CHECKCONTINUE% GOTO loop-noreply

Однако его можно сократить до:

GOTO loop-noreply

Если переменная была определена, выполнение так или иначе никогда не достигнет этой строки ( if defined ... выше)


Вот как бы я написал это:

@echo off & setlocal
title Registry restore script
:: Restores registry settings and disables the Cloud

:menu
echo I'm about to...
echo 1) Remove the registry data that specifies settings for TF2
echo 2) Forcibly disable Steam Cloud.
echo.
set check=
set /p check=Okay to continue? (y/n)
:: /i means case-insensitive comparison
if /i %check%==y goto :start
if /i %check%==yes goto :start
if /i %check%==n goto :EOF
if /i %check%==no goto :EOF
:: On empty response, pick the safest option as default
if not defined check goto :EOF

goto :loop-invalid

:start
:: Delete application specific data
reg delete HKCU\Software\Valve\Source\tf\Settings /f
reg delete HKCU\Software\Valve\Steam\Apps\440 /f

:: Disable Steam Cloud for TF2
reg add HKCU\Software\Valve\Steam\Apps\440 /v Cloud /t REG_DWORD /d "0x0" /f

:loop-invalid
echo.
echo Not a valid answer.
goto :menu
grawity
источник
Если скрипт запускался из интерактивной оболочки командной строки, exit будет завершать оболочку тоже, а не только сценарий. В скрипте следует использовать либо goto :EOF или же exit /b, ( endlocal не обязательно.)
grawity
Он по-прежнему завершается, если вы нажмете Enter без ввода на set /p check=Okay to continue? (y/n) :(
Matthieu Cartier
@neurolysis: Если вы говорите о переписанной версии из моего поста, то это потому, что я написал ее. IMO, поскольку есть разумная опция по умолчанию («нет, не восстанавливать»), нажатие Enter должно просто выбрать эту опцию. Если вы не согласны, измените на if not defined check goto :loop-noreply,
grawity
Ну, это проблема под рукой. Даже с if not defined check goto :loop-noreply как проверка, это заканчивается ...
Matthieu Cartier
@neurolysis: ... ты (ре) добавил :loop-noreply раздел? Кроме того, попробуйте переместить not defined проверьте сразу после set /p, И запустить все из окна командной строки, сообщения об ошибках являются полезно.
grawity
1

Разъяснение великого ответа Гравити:

Сначала ответим на ваш вопрос: Почему пакетный скрипт завершается, когда% CHECKCONTINUE% получает нулевое значение?

Проблема в строке 16, вы делаете это:

if %CHECKCONTINUE%==n GOTO exit

поскольку CHECKCONTINUE имеет значение "undefined", оно оценивается как "пустая" строка, поэтому оператор в строке 16 фактически выполняет

if ==n GOTO exit

Это неверное утверждение, потому что в левой части "==", Итак, пакетный скрипт завершается, когда он пытается выполнить неправильно отформатированный оператор:

C:\>script.cmd
I'm about to...
1.) Remove the registry data that specifies settings for TF2
2.) Forcibly disable Steam Cloud.

Okay to continue? (y/n): <ENTER key pressed>
GOTO was unexpected at this time.

C:\>

У вас будет похожая проблема, если кто-то напечатает что-то, в котором есть пробел:

C:\>script.cmd
I'm about to...
1.) Remove the registry data that specifies settings for TF2
2.) Forcibly disable Steam Cloud.

Okay to continue? (y/n): Yes please
please==n was unexpected at this time.

C:\>

Чтобы это исправить, вы должны использовать двойные кавычки вокруг таких слов:

if "%CHECKCONTINUE%"=="n" GOTO :exit

Это необходимо, если используемые переменные могут быть «пустыми» или иметь встроенные пробелы, но это просто хорошая идея всегда использовать двойные кавычки при оценке с "==",

Примечание. Некоторые ошибки (например, приведенная выше с "if" а также "==" являются «фатальными» ошибками, которые приведут к немедленному прекращению выполнения пакетного скрипта. Другие ошибки (как показано ниже с "set" ), являются «несмертельными» ошибками. Для «нефатальных» ошибок оператор с ошибкой НЕ выполняется, отображается сообщение об ошибке, и пакетный сценарий продолжает выполняться, начиная со следующего оператора

Далее, как Grawity указал об этой строке:

set %CHECKCONTINUE%=

Это не изменяет CHECKCONTINUE, но использует его значение в качестве имени переменной.

Опять же, если CHECKCONTINUE был "неопределен", он будет оцениваться как "пустая" строка, поэтому оператор фактически делает:

set =

Это также неверное утверждение, потому что в левой части "=",

И эти строки:

if defined %CHECKCONTINUE% GOTO loop-notvalid
if not defined %CHECKCONTINUE% GOTO loop-noreply

"if defined" (а также "if not defined" ) ожидает имя переменной, а не значение переменной. Если CHECKCONTINUE был неопределен, %CHECKCONTINUE% будет вычислять пустую строку, и эти операторы будут:

if defined  GOTO loop-notvalid
if not defined  GOTO loop-noreply

Вот, "if defined" (а также "if not defined" ) собирается проверить, если переменная с именем GOTO определяется или нет.

Кроме того, для этих 3 строк, если CHECKCONTINUE был на самом деле определен, "set" а также "if defined" будет работать на "value" переменной, а не "name" самой переменной. Так что если CHECKCONTINUE уже имел значение "y", затем:

set %CHECKCONTINUE%=
if defined %CHECKCONTINUE% goto loop-notvalid
if not defined %CHECKCONTINUE% goto loop-noreply

будет выглядеть так:

set y=
if defined y goto loop-notvalid
if not defined y goto loop-noreply

Пример "script.cmd":

@set "CHECKCONTINUE="

@rem ## CHECKCONTINUE="%CHECKCONTINUE%" (undefined/empty). 
@rem ## 05: set %CHECKCONTINUE%=
set %CHECKCONTINUE%=

@echo This doesn't set the value of of the variable named "CHECKCONTINUE". 
@echo Since no variable name is actually specified, it is an error. 



@set "CHECKCONTINUE=yes"
@set "yes=something"

@rem ## CHECKCONTINUE="%CHECKCONTINUE%" and the value of the variable named "yes"="%yes%" 
@rem ## 17: set %CHECKCONTINUE%=
set %CHECKCONTINUE%=

@echo This doesn't set the value of the variable named "CHECKCONTINUE". 
@echo Since CHECKCONTINUE="%CHECKCONTINUE%", it sets the value of the variable named 
@echo "%CHECKCONTINUE%". No error is shown because the statement is valid. 
@echo It could have been a problem (well, at least a big annoyance) if 
@echo CHECKCONTINUE had the value: "path". The statement 
@echo should be: set "CHECKCONTINUE=" 

@rem ## 27: echo CHECKCONTINUE still has the value: "%CHECKCONTINUE%"
@echo CHECKCONTINUE still has the value: "%CHECKCONTINUE%"

@rem ## 30: echo and the variable named "%CHECKCONTINUE%" is now empty="%yes%"
@echo and the variable named "%CHECKCONTINUE%" is now empty="%yes%"



@set "yes="
@set "CHECKCONTINUE="
@set "echo=something"

@rem ## CHECKCONTINUE="%CHECKCONTINUE%" (undefined) and the value of the variable 
@rem ## named "echo"="%echo%". 
@rem ## 41: if defined %CHECKCONTINUE% echo Variable is defined.
if defined %CHECKCONTINUE% echo Variable is defined.

@echo This doesn't check if the variable named "CHECKCONTINUE" is defined. 
@echo Since it's "empty", it is skipped (well, there is nothing there to 
@echo "skip") and "if defined" is checking the next word (which is "echo"). 
@echo What's left is: if defined echo Variable is defined. 
@echo So, it checks if a variable named "echo" is defined (which it is). 
@echo Since "if defined" has checked a variable named "echo", it then tries 
@echo to execute the rest of the line starting with the word "Variable", 
@echo as a command. This fails and is an error. The statement 
@echo should be: if defined CHECKCONTINUE echo Variable is defined. 



@set "echo="

@rem ## CHECKCONTINUE="%CHECKCONTINUE%" (undefined) and "echo"="%echo%" (undefined). 
@rem ## 59: if not defined %CHECKCONTINUE% echo The-variable-is-not-defined.
if not defined %CHECKCONTINUE% echo The-variable-is-not-defined.

@echo Similar: Since "if not defined" has checked a variable named "echo" 
@echo (which is "undefined"), it then tries to execute the rest of the 
@echo line: "The-variable-is-not-defined." as a command. This fails and is 
@echo an error. The statement 
@echo should be: if not defined CHECKCONTINUE echo The-variable-is-not-defined. 



@set "echo=something"

@rem ## CHECKCONTINUE="%CHECKCONTINUE%" (undefined) and "echo"="%echo%". 
@rem ## 73: if defined %CHECKCONTINUE% echo Verify this.
if defined %CHECKCONTINUE% echo Verify this.

@echo Again, similar: Since "if defined" has checked a variable named 
@echo "echo", it then tries to execute the rest of the line starting with 
@echo the word: "Verify" as a command. This happens to be a valid command 
@echo but it also fails because of an incorrect parameter for the command. 
@echo The statement should be: if defined CHECKCONTINUE echo Verify this. 



@set "echo="

@set "CHECKCONTINUE=yes"
@set "yes="

@rem ## CHECKCONTINUE="%CHECKCONTINUE%" and the variable named "yes"="%yes%" (undefined). 
@rem ## 90: if not defined %CHECKCONTINUE% echo CHECKCONTINUE is not defined.
if not defined %CHECKCONTINUE% echo CHECKCONTINUE is not defined.

@echo Here "CHECKCONTINUE" is defined, but "if not defined" still doesn't 
@echo check if the variable named "CHECKCONTINUE" is defined. Since 
@echo CHECKCONTINUE has a value of "%CHECKCONTINUE%", "if not defined" is 
@echo checking if a variable named "%CHECKCONTINUE%" is defined (which it isn't). 
@echo This causes "if not defined" to proceed and echo the message when 
@echo that's probably not what was intended. The statement 
@echo should be: if not defined CHECKCONTINUE echo CHECKCONTINUE is not defined.

Запустив "script.cmd", вы получите:

## CHECKCONTINUE="" (undefined/empty). 
## 05: set %CHECKCONTINUE%=

    C:\>set =
    The syntax of the command is incorrect.

This doesn't set the value of of the variable named "CHECKCONTINUE". 
Since no variable name is actually specified, it is an error. 



## CHECKCONTINUE="yes" and the value of the variable named "yes"="something" 
## 17: set %CHECKCONTINUE%=

    C:\>set yes=

This doesn't set the value of the variable named "CHECKCONTINUE". 
Since CHECKCONTINUE="yes", it sets the value of the variable named 
"yes". No error is shown because the statement is valid. 
It could have been a problem (well, at least a big annoyance) if 
CHECKCONTINUE had the value: "path". The statement 
should be: set "CHECKCONTINUE=" 

## 27: echo CHECKCONTINUE still has the value: "%CHECKCONTINUE%"

    CHECKCONTINUE still has the value: "yes"

## 30: echo and the variable named "yes" is now empty="%yes%"

    and the variable named "yes" is now empty=""



## CHECKCONTINUE="" (undefined) and the value of the variable 
## named "echo"="something". 
## 41: if defined %CHECKCONTINUE% echo Variable is defined.

    C:\>if defined echo Variable is defined.
    'Variable' is not recognized as an internal or external command,
    operable program or batch file.

This doesn't check if the variable named "CHECKCONTINUE" is defined. 
Since it's "empty", it is skipped (well, there is nothing there to 
"skip") and "if defined" is checking the next word (which is "echo"). 
What's left is: if defined echo Variable is defined. 
So, it checks if a variable named "echo" is defined (which it is). 
Since "if defined" has checked a variable named "echo", it then tries 
to execute the rest of the line starting with the word "Variable", 
as a command. This fails and is an error. The statement 
should be: if defined CHECKCONTINUE echo Variable is defined. 



## CHECKCONTINUE="" (undefined) and "echo"="" (undefined). 
## 59: if not defined %CHECKCONTINUE% echo The-variable-is-not-defined.

    C:\>if not defined echo The-variable-is-not-defined.
    'The-variable-is-not-defined.' is not recognized as an internal or external command,
    operable program or batch file.

Similar: Since "if not defined" has checked a variable named "echo" 
(which is "undefined"), it then tries to execute the rest of the 
line: "The-variable-is-not-defined." as a command. This fails and is 
an error. The statement 
should be: if not defined CHECKCONTINUE echo The-variable-is-not-defined. 



## CHECKCONTINUE="" (undefined) and "echo"="something". 
## 73: if defined %CHECKCONTINUE% echo Verify this.

    C:\>if defined echo Verify this.
    An incorrect parameter was
    entered for the command.

Again, similar: Since "if defined" has checked a variable named 
"echo", it then tries to execute the rest of the line starting with 
the word: "Verify" as a command. This happens to be a valid command 
but it also fails because of an incorrect parameter for the command. 
The statement should be: if defined CHECKCONTINUE echo Verify this. 



## CHECKCONTINUE="yes" and the variable named "yes"="" (undefined). 
## 90: if not defined %CHECKCONTINUE% echo CHECKCONTINUE is not defined.

    C:\>if not defined yes echo CHECKCONTINUE is not defined.
    CHECKCONTINUE is not defined.

Here "CHECKCONTINUE" is defined, but "if not defined" still doesn't 
check if the variable named "CHECKCONTINUE" is defined. Since 
CHECKCONTINUE has a value of "yes", "if not defined" is 
checking if a variable named "yes" is defined (which it isn't). 
This causes "if not defined" to proceed and echo the message when 
that's probably not what was intended. The statement 
should be: if not defined CHECKCONTINUE echo CHECKCONTINUE is not defined.

Кроме того, в качестве альтернативы "set /p", вы могли бы использовать "choice":

@echo off
title Registry restore script
rem Restores registry settings and disables the cloud

rem "quotes" around variable name and value for set visibly shows what 
rem the variable is being set to and prevents accidentally including  
rem trailing whitespace in the variable's value.
    set "CHECKCONTINUE="

:listaction
echo I'm about to...
echo 1.) Remove the registry data that specifies settings for TF2
echo 2.) Forcibly disable Steam Cloud.
echo.
choice /c yn /M "Okay to continue"

set "CHECKCONTINUE=%errorlevel%"
if %CHECKCONTINUE% EQU 1 @echo Pressed Y && goto :start
if %CHECKCONTINUE% EQU 2 @echo Pressed N && goto :exit
if %CHECKCONTINUE% EQU 0 @echo Pressed Ctrl-C+n
@echo.

@echo Terminate batch job cancelled. You must enter a reply. Press n to exit.
@echo.
goto :listaction

rem The remainder of your code goes here ...

Примечание: код на этикетке: "loop-notvalid" не является необходимым, потому что «выбор» не будет принимать неопределенные ответы (да / нет).

Кроме того, единственный способ получить «пустой» ответ от «выбора» - это если пользователь нажимает «Ctrl-C» для завершения пакетного задания, а затем вводит N (Нет) в «Завершить пакетное задание» (Y / N)?» подсказка, указывающая, что они НЕ хотят выходить. Приведенный выше код ловит это и печатает сообщение, затем переходит (переход) к метке «: listaction», чтобы повторно запросить пользователя, поэтому вам также не нужен код с меткой «loop-noreply».

Нет необходимости «сбрасывать» уровень ошибки, поскольку команда выбора позаботится об этом. И нет необходимости очищать CHECKCONTINUE переменная, потому что она всегда установлена ​​равной %errorlevel% до значения CHECKCONTINUE рассматривается

По умолчанию выбор «нечувствителен к регистру», поэтому нажатие «Y» или «N» аналогично нажатию «y» или «n». Это поведение можно изменить, указав /cs на выбор командной строки.

Kevin Fegan
источник