2009-12-03 20:51:48 +0000 2009-12-03 20:51:48 +0000
72
72

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

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

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

@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 . . .

Это shouldn’t имеет никакого отношения к ПЧ, ясно, что блок выполняется. Если я задам BAR над if, то, как и ожидалось, будет отображен только текст команды PAUSE.

Что дает?


Следующий вопрос: Можно ли включить отложенное расширение без setlocal?

Если я буду вызывать этот простой пример пакетного файла из другого примера, то пример устанавливает FOO, но только LOCALLY.

Например:

testtcaller.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 . . .

В данном случае, кажется, что я должен включить задержку расширения в ЗВОНОКЕ, что может быть проблемой.

Ответы (6)

86
86
86
2009-12-03 21:35:26 +0000

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

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

Для решения этой проблемы можно включить delayed расширение:

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% в операторе both IF заменяются при чтении первого оператора ПЧ, поскольку логически он включает тело оператора IF, который является составным оператором. Таким образом, IF внутри составного оператора действительно сравнивает “до” с “после”, что никогда не будет равнозначно. Аналогично, следующий пример не будет работать, как ожидалось:

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

в том, что он not создаст список файлов в текущем каталоге, а вместо этого просто установит переменную 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%
4
4
4
2011-03-26 16:26:14 +0000
3
3
3
2009-12-03 21:02:01 +0000

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

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
1
1
1
2013-11-27 11:52:14 +0000

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

for /l %%I in (0,1,5) do call echo %%RANDOM%%
0
0
0
2016-12-28 21:46:34 +0000

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

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

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

и это полное решение синтаксиса IF THEN ELSE:

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
0
0
0
2012-06-26 15:36:32 +0000

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

, например,

if not defined BAR set FOO=1

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

if not defined BAR echo FOO: %FOO%

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

Похожие вопросы

3
19
10
28
4