Переменные в пакетном файле не устанавливаются внутри IF?

69

У меня есть два примера очень простых командных файлов:

Присвоение значения переменной:

@echo off
set FOO=1
echo FOO: %FOO%
pause
echo on

Что, как и ожидалось, приводит к:

FOO: 1 
Press any key to continue . . .

Однако, если я помещу те же две строки в блок IF NOT DEFINED:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: %FOO%
)
pause
echo on

Это неожиданно приводит к:

FOO: 
Press any key to continue . . .

Это не должно иметь ничего общего с IF, ясно, что блок выполняется. Если я определяю BAR над if, то, как и ожидалось, отображается только текст из команды PAUSE.

Что дает?


Дополнительный вопрос: есть ли способ включить отложенное расширение без setlocal?

Если бы мне нужно было вызвать этот простой пример командного файла из другого, в примере был указан FOO, но только ЛОКАЛЬНО.

Например:

testcaller.bat

@call test.bat 
@echo FOO: %FOO% 
@pause 

test.bat

@setlocal EnableDelayedExpansion 
@IF NOT DEFINED BAR ( 
    @set FOO=1 
    @echo FOO: !FOO! 
) 

Это отображает:

FOO: 1 
FOO: 
Press any key to continue . . . 

В этом случае кажется, что я должен включить отложенное расширение в CALLER, что может быть хлопотно.

коричневый
источник

Ответы:

84

Переменные среды в пакетных файлах раскрываются при разборе строки. В случае блоков, разделенных круглыми скобками (как ваш if defined), весь блок считается «строкой» или командой.

Это означает, что все вхождения% FOO% заменяются своими значениями до запуска блока. В вашем случае ни с чем, так как переменная еще не имеет значения.

Чтобы решить эту проблему, вы можете включить отложенное расширение :

setlocal enabledelayedexpansion

Отложенное расширение приводит к тому, что переменные, разделенные восклицательными знаками ( !), оцениваются при выполнении вместо анализа, что обеспечит правильное поведение в вашем случае:

if not defined BAR (
    set FOO=1
    echo Foo: !FOO!
)

help set Подробности это тоже:

Наконец, добавлена ​​поддержка отложенного расширения переменных среды. Эта поддержка всегда отключена по умолчанию, но ее можно включить / отключить с помощью /Vпереключателя командной строки CMD.EXE. ВидетьCMD /?

Задержка раскрытия переменных среды полезна для преодоления ограничений текущего раскрытия, которое происходит при чтении строки текста, а не при ее выполнении. В следующем примере демонстрируется проблема с немедленным расширением переменной:

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "%VAR%" == "after" @echo If you see this, it worked
)

никогда не будет отображать сообщение, так как %VAR%в обоих IF операторах подставляется при чтении первого оператора IF, так как он логически включает в себя тело IFоператора, который является составным оператором. Таким образом, IF внутри составного оператора действительно сравнивает «до» с «после», которое никогда не будет равным. Аналогично, следующий пример не будет работать должным образом:

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

в том смысле, что он не будет создавать список файлов в текущем каталоге, а просто установит LIST переменную для последнего найденного файла. Опять же, это происходит потому, %LIST%что, когда FOR оператор читается, он раскрывается только один раз , и в это время LISTпеременная пуста. Итак, фактический цикл FOR, который мы выполняем:

for %i in (*) do set LIST= %i

который просто сохраняет настройки LISTдо последнего найденного файла.

Задержка раскрытия переменных среды позволяет использовать другой символ (восклицательный знак) для раскрытия переменных среды во время выполнения. Если расширение отложенной переменной включено, приведенные выше примеры могут быть написаны следующим образом, чтобы работать как задумано:

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "!VAR!" == "after" @echo If you see this, it worked
)

set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
детеныш
источник
17
И если вам нужно повторить !, используйте ^^^!(избегайте дважды). В противном случае функция «отложенного расширения» сожрет ее.
Гравитация
4

Такое же поведение также происходит, когда команды находятся в одной строке ( &это разделитель команд):

if not defined BAR set FOO=1& echo FOO: %FOO%

Объяснение Джои мое любимое. Однако обратите внимание, что enabledelayedexpansionэто не работает в Windows NT 4.0 (и я не уверен в Windows 2000).

О вашей последующий вопрос, нет, это не представляется возможным EnableDelayedExpansionбез setlocal. Однако исходное поведение, которое шло против вас, может быть использовано для решения этой второй проблемы: хитрость заключается в том, чтобы endlocalна той же строке, где вы снова устанавливаете значения нужных вам переменных.

Вот ваш test.batмодифицированный:

@echo off
setlocal EnableDelayedExpansion 
IF NOT DEFINED BAR ( 
    set FOO=1 
    echo FOO: !FOO! 
)
endlocal & set FOO=%FOO%

Но вот еще одно решение этой проблемы: используйте процедуру в том же файле вместо встроенного блока или внешнего файла.

@echo off
if not defined BAR call :NotDefined
pause
goto :EOF

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
дольмен
источник
«Хитрость заключается в том, чтобы endlocal на одной линии» - СПАСИБО за это!
Dforce
3

Если это не работает таким образом, вы, вероятно, отложили расширение переменной среды. Вы можете отключить его cmd /V:OFFили использовать восклицательные знаки внутри, если:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
Джон Т
источник
3
Включение отложенного расширения изменяет только семантику восклицательного знака. Это никак не влияет на нормальное расширение переменной. Кроме того, случайное включение очень маловероятно; люди, у которых это включено, обычно делают это нарочно.
Джои
1

Это происходит потому, что ваша строка с FORкомандой оценивается только один раз. Вам нужен какой-то способ переоценить его. Вы можете смоделировать отложенное расширение с помощью CALLкоманды:

for /l %%I in (0,1,5) do call echo %%RANDOM%%
Бесплатная консультация
источник
Отложенное расширение не сработало, но этот вызов / do / сработал на win7, чтобы мой трехстрочный сценарий cmd нашел настоящую жесткую ссылку: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') зовите установить CWD_REAL = %% следующий эхо кд / d% CWD_REAL% кд / d% CWD_REAL%
мош
0

Стоит отметить, что он работает, если это одна команда на одной строке.

например

   if not defined BAR set FOO=1

на самом деле FOOбудет 1. Вы можете сделать еще одну проверку, такую ​​как:

   if not defined BAR echo FOO: %FOO%

Эй, я никогда не говорил, что это красиво. Но если вы не хотите связываться с setlocal или с отложенным расширением бизнеса, это быстрый обходной путь.

demoncodemonkey
источник
0

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

В случае, если вы будете использовать Delayd Expansion, также рекомендуется включить как минимум регистрацию в пакете:

IF NOT %Comspec%==!Comspec! ECHO Delayed Expansion is NOT enabled. 

Если вам просто нужен более читабельный и простой подход, вот альтернативные решения:

REM Option 2: use `call` inside block statement
IF NOT DEFINED BAR2 ( 
  set FOO2=2 
  call echo FOO2: %%FOO2%%
)
echo FOO2: %FOO2%

который работает так:

>REM Option 2: use `call` inside block statement

>IF NOT DEFINED BAR2 (
 set FOO2=2
 call echo FOO2: %FOO2%
)
FOO2: 2

>echo FOO2: 2
FOO2: 2

и еще одно чистое решение cmd:

REM Option 3: use `goto` logic blocks
IF DEFINED BAR3 goto endbar3
  set FOO3=3 
  echo FOO3: %FOO3%
:endbar3
echo FOO3: %FOO3%

это работает так:

>REM Option 3: use `goto` logic blocks

>IF DEFINED BAR3 goto endbar3

>set FOO3=3

>echo FOO3: 3
FOO3: 3

>echo FOO3: 3
FOO3: 3

и это полное, ЕСЛИ ТОЛЬКО Синтаксическое решение:

REM Full IF THEN ELSE solution
IF NOT DEFINED BAR4 goto elsebar4
REM this is TRUE condition, normal batch flow
  set FOO4=6
  echo FOO4: %FOO4%
  goto endbar4
:elsebar4
  set FOO4=4
  echo FOO4: %FOO4%
  set BAR4=4
:endbar4
echo FOO4: %FOO4%
echo BAR4: %BAR4%

это работает так:

>REM Full IF THEN ELSE solution

>IF NOT DEFINED BAR4 goto elsebar4

>set FOO4=4

>echo FOO4: 4
FOO4: 4

>set BAR4=4

>echo FOO4: 4
FOO4: 4

>echo BAR4: 4
BAR4: 4
Арунас Бартисиус
источник