63

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

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

@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, что может быть хлопотно.

6 ответов6

77

Переменные среды в пакетных файлах раскрываются при разборе строки. В случае блоков, разделенных скобками (как 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%
3

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

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

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

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
1

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

for /l %%I in (0,1,5) do call echo %%RANDOM%%
0

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

например

   if not defined BAR set FOO=1

фактически установит FOO в 1. Затем вы можете сделать еще одну проверку, такую как:

   if not defined BAR echo FOO: %FOO%

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

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .