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

Zmienne w pliku wsadowym nie są ustawiane wewnątrz IF?

Mam dwa przykłady bardzo prostych plików wsadowych:

Przypisanie wartości do zmiennej:

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

Co zgodnie z oczekiwaniami skutkuje:

FOO: 1 
Press any key to continue . . .

Jednak jeśli umieszczę te same dwie linie wewnątrz bloku IF NOT DEFINED:

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

To nieoczekiwanie skutkuje:

FOO: 
Press any key to continue . . .

To nie powinno mieć nic wspólnego z IF, widocznie blok jest wykonywany. Jeśli zdefiniuję BAR powyżej if, wyświetlany jest tylko tekst z polecenia PAUSE, zgodnie z oczekiwaniami.

Co to daje?


Pytanie uzupełniające: Czy istnieje sposób, aby włączyć opóźnione rozszerzanie bez setlocal?

Jeśli miałbym wywołać ten prosty przykładowy plik wsadowy z wnętrza innego, przykład ustawia FOO, ale tylko LOKALNIE.

Na przykład:

testcaller.bat

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

test.bat

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

To wyświetla:

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

W tym przypadku wygląda na to, że muszę włączyć delayed expansion w CALLER, co może być kłopotliwe.

Odpowiedzi (6)

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

Zmienne środowiskowe w plikach wsadowych są interpretowane podczas parsowania linii. W przypadku bloków ograniczonych nawiasami (jak twój if defined), cały blok liczy się jako “linia” lub polecenie.

To oznacza, że wszystkie wystąpienia %FOO% są zastępowane przez ich wartości przed uruchomieniem bloku. W twoim przypadku z niczym, ponieważ zmienna nie ma jeszcze wartości.

Aby to rozwiązać możesz włączyć delayed expansion:

setlocal enabledelayedexpansion

Delayed expansion powoduje, że zmienne ograniczone wykrzyknikami (!) są analizowane podczas wykonywania zamiast parsowania, co zapewni poprawne zachowanie w twoim przypadku:

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

help set wyszczególnia to również:

Wreszcie, dodano wsparcie dla opóźnionej interpretacji zmiennych środowiskowych. Obsługa ta jest domyślnie zawsze wyłączona, ale może być włączona/wyłączona za pomocą przełącznika linii komend /V na CMD.EXE. Zobacz CMD /?

Opóźniona interpretacja zmiennych środowiskowych jest przydatna do obejścia ograniczeń obecnej interpretacji, która ma miejsce gdy linia tekstu jest czytana, a nie gdy jest wykonywana. Poniższy przykład demonstruje problem z natychmiastową interpretacją zmiennych:

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

nigdy nie wyświetliłoby komunikatu, ponieważ %VAR% w obu instrukcjach IF jest zastępowane, gdy odczytywana jest pierwsza instrukcja IF, ponieważ logicznie zawiera ona ciało IF, które jest instrukcją złożoną. Tak więc IF wewnątrz instrukcji złożonej jest naprawdę porównywaniem “przed” z “po”, które nigdy nie będą równe. Podobnie, następujący przykład nie będzie działał tak, jak oczekiwano:

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

w tym, że nie zbuduje listy plików w bieżącym katalogu, ale zamiast tego po prostu ustawi zmienną LIST na ostatni znaleziony plik. Ponownie, dzieje się tak dlatego, że zmienna %LIST% jest interpretowana tylko raz, gdy instrukcja FOR jest odczytywana, a w tym czasie zmienna LIST jest pusta. Tak więc rzeczywista pętla FOR, którą wykonujemy to:

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

która po prostu ustawia LIST na ostatni znaleziony plik.

Opóźnione rozwijanie zmiennych środowiskowych pozwala na użycie innego znaku (wykrzyknika) do rozwijania zmiennych środowiskowych w czasie wykonywania. Jeśli opóźnione interpretowanie zmiennych jest włączone, to powyższe przykłady można by zapisać następująco, by działały zgodnie z przeznaczeniem:

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

To samo zachowanie ma miejsce, gdy polecenia są w jednej linii (& jest separatorem poleceń):

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

Wyjaśnienie Joey'a jest moim ulubionym. Zauważ jednak, że enabledelayedexpansion nie działa na Windows NT 4.0 (i nie jestem pewien co do Windows 2000).

O twoim pytaniu uzupełniającym, nie, nie jest możliwe EnableDelayedExpansion bez setlocal. Jednak oryginalne zachowanie, które było przeciwko tobie, może być użyte do obejścia tego drugiego problemu: sztuczka polega na endlocal w tej samej linii, w której ponownie ustawiasz wartości zmiennych, których potrzebujesz.

Tutaj jest twoje zmodyfikowane test.bat:

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

Ale tutaj jest inne obejście tego problemu: użyj procedury w tym samym pliku zamiast bloku inline lub zewnętrznego pliku.

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
3
3
3
2009-12-03 21:02:01 +0000

Jeśli to nie działa w ten sposób, prawdopodobnie masz włączone opóźnione rozszerzenie zmiennej środowiskowej. Możesz ją wyłączyć za pomocą cmd /V:OFF lub użyć wykrzykników wewnątrz instrukcji 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

Dzieje się tak, ponieważ twoja linia z komendą FOR ocenia tylko raz. Potrzebujesz jakiegoś sposobu na jej ponowną ewaluację. Mógłbyś zasymulować opóźnioną interpretację komendą CALL:

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

Czasami, jak to miało miejsce w moim przypadku, kiedy musisz być kompatybilny ze starszymi systemami, takimi jak stare serwery NT4, gdzie opóźnione rozszerzenie nie działa lub z jakiegoś powodu nie działa, możesz potrzebować alternatywnego rozwiązania.

W przypadku, gdy będziesz używał Delayd Expansion, zalecane jest również, aby przynajmniej check in batch był włączony:

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

W przypadku, gdy chcesz po prostu bardziej czytelne i proste podejście, tutaj są alternatywne rozwiązania:

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

które działa tak:

>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

i jeszcze jedno czyste rozwiązanie cmd:

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

działa to tak:

>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

i to jest pełne rozwiązanie składni 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%

działa to tak:

>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

Warto zauważyć, że to DZIAŁA, jeśli jest to pojedyncza komenda w tej samej linii.

np.

if not defined BAR set FOO=1

w rzeczywistości ustawi FOO na 1. Można by wtedy wykonać kolejne sprawdzenie, takie jak:

if not defined BAR echo FOO: %FOO%

Hej, nigdy nie powiedziałem, że to było ładne. Ale jeśli nie chcesz zadzierać z setlocal lub biznesem opóźnionego rozszerzenia, jest to szybkie obejście.