Змінні в пакетному файлі не встановлюються, якщо всередині IF?


69

У мене є два приклади дуже простих пакетних файлів:

Призначення значення змінній:

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

Що, як очікується, призводить до:

FOO: 1 
Press any key to continue . . .

Однак якщо я розміщую ці ж два рядки всередині блоку, ЯКЩО НЕ ВИЗНАЧЕНО:

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

Це несподівано призводить до:

FOO: 
Press any key to continue . . .

Це не повинно мати нічого спільного з ІФ, очевидно, що блок виконується. Якщо я задаю BAR вище значення if, відображається лише текст команди PAUSE, як очікувалося.

Що дає?


Подальше запитання: Чи є спосіб увімкнути затримку розширення без встановлення місцевих?

Якби я назвав цей простий прикладний пакетний файл з іншого, приклад встановлює 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, яке є складовим. Тож, якщо ІФ у складеному твердженні дійсно порівнює "до" із "після", яке ніколи не буде рівним. Аналогічно, наступний приклад не буде працювати, як очікувалося:

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
А якщо вам потрібно повторити !, скористайтеся ^^^!(уникнути цього двічі). Інакше функція "затримки розширення" з'їсть її.
grawity

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

"фокус у тому, щоб закінчити місце на одній лінії" - ДЯКУЮ за це!
dforce

3

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

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

3
Увімкнення затримки розширення лише змінює семантику знака оклику. Це не робить ніякого впливу на нормальне розширення змінної. Крім того, випадкове включення це малоймовірно; люди, які це ввімкнули, зазвичай роблять це спеціально.
Joey

1

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

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

Затримка розширення не спрацювала, але цей / зробити виклик / працював на win7, для мого 3-х рядкового 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

буде фактично встановлено FOO1. Ви можете зробити ще одну перевірку, наприклад:

   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

і це є повним, АКОГА ІЗ синтаксисом рішення 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.