Zmienne w pliku wsadowym nie są ustawiane, gdy są w środku IF?

69

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

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

Jeśli jednak umieszczę te same dwie linie w bloku IF NOT DEFINED:

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

Ten nieoczekiwanie skutkuje:

FOO: 
Press any key to continue . . .

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

Co daje?


Dalsze pytanie: Czy jest jakiś sposób, aby włączyć opóźnioną ekspansję bez setlocal?

Gdybym miał wywołać ten prosty przykładowy plik wsadowy z 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! 
) 

Wyświetla to:

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

W tym przypadku wydaje się, że muszę włączyć opóźnione rozszerzenie w CALLER, co może być kłopotliwe.

brązowy
źródło

Odpowiedzi:

84

Zmienne środowiskowe w plikach wsadowych są rozwijane podczas analizowania wiersza. W przypadku bloków ograniczonych nawiasami (jako twoje if defined) cały blok liczy się jako „linia” lub polecenie.

Oznacza to, że wszystkie wystąpienia% FOO% są zastępowane ich wartościami przed uruchomieniem bloku. W twoim przypadku nic, ponieważ zmienna nie ma jeszcze wartości.

Aby rozwiązać ten problem, możesz włączyć opóźnioną ekspansję :

setlocal enabledelayedexpansion

Opóźnione rozwinięcie powoduje, że zmienne rozdzielane wykrzyknikami ( !) są oceniane podczas wykonywania zamiast parsowania, co zapewni prawidłowe zachowanie w twoim przypadku:

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

help set szczegółowo to również:

Na koniec dodano obsługę opóźnionego rozszerzania zmiennych środowiskowych. Ta obsługa jest zawsze domyślnie wyłączona, ale można ją włączyć / wyłączyć za pomocą /Vprzełącznika wiersza polecenia na CMD.EXE. WidziećCMD /?

Opóźnione rozwinięcie zmiennej środowiskowej jest przydatne w celu obejścia ograniczeń bieżącego rozszerzenia, które dzieje się, gdy czytany jest wiersz tekstu, a nie podczas jego wykonywania. Poniższy przykład pokazuje problem z natychmiastowym rozszerzaniem zmiennych:

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

nigdy nie wyświetli komunikatu, ponieważ %VAR%w obu IF instrukcjach jest podstawiana, gdy czytana jest pierwsza instrukcja JEŻELI, ponieważ logicznie zawiera ona treść instrukcji IF, która jest instrukcją złożoną. Zatem IF wewnątrz instrukcji złożonej naprawdę porównuje „przed” z „po”, który nigdy nie będzie równy. Podobnie poniższy przykład nie będzie działał zgodnie z oczekiwaniami:

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

w tym sensie, że nie utworzy listy plików w bieżącym katalogu, ale zamiast tego po prostu ustawi LIST zmienną na ostatnio znaleziony plik. Ponownie dzieje się tak, ponieważ zmienna jest %LIST%rozszerzana tylko raz, gdy FOR instrukcja jest czytana, i wówczas LISTzmienna jest pusta. Zatem faktyczna pętla FOR, którą wykonujemy, to:

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

który po prostu zachowuje ustawienie LISTdla ostatniego znalezionego pliku.

Opóźnione rozszerzanie zmiennych środowiskowych umożliwia użycie innego znaku (wykrzyknika) do rozszerzenia zmiennych środowiskowych w czasie wykonywania. Jeśli włączone jest opóźnione rozszerzanie zmiennych, powyższe przykłady można zapisać następująco:

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%
Joey
źródło
17
A jeśli potrzebujesz echa !, użyj ^^^!(dwa razy ucieknij). W przeciwnym razie funkcja „opóźnionego rozszerzenia” zje to.
grawity
4

To samo zachowanie występuje również, gdy polecenia znajdują się w jednym wierszu ( &jest to separator poleceń):

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

Wyjaśnienie Joeya jest moim ulubionym. Należy jednak pamiętać, że enabledelayedexpansionto nie działa w systemie Windows NT 4.0 (i nie jestem pewien co do systemu Windows 2000).

Jeśli chodzi o twoje pytanie uzupełniające, nie, nie można tego zrobić EnableDelayedExpansionbez setlocal. Jednak oryginalne zachowanie, które było przeciwko tobie, może zostać wykorzystane do obejścia tego drugiego problemu: sztuczka polega endlocalna tym, że w tym samym wierszu, w którym ponownie ustawiasz wartości zmiennych, których potrzebujesz.

Oto twoje test.batzmodyfikowane:

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

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

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
dolmen
źródło
„sztuczka polega na tym, by dotrzeć do tej samej linii” - DZIĘKUJĘ za to!
zmusza
3

Jeśli to nie działa w ten sposób, prawdopodobnie opóźnione jest rozszerzanie zmiennych środowiskowych. Możesz to wyłączyć za pomocą cmd /V:OFFlub użyć wykrzykników, jeśli:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
John T.
źródło
3
Włączenie opóźnionego rozwijania zmienia tylko semantykę wykrzyknika. Nie ma to żadnego wpływu na normalne rozszerzanie zmiennych. Poza tym przypadkowe włączenie jest bardzo mało prawdopodobne; ludzie, którzy mają to włączone, zwykle robią to celowo.
Joey,
1

Dzieje się tak, ponieważ twoja linia z FORpoleceniem ocenia tylko raz. Potrzebujesz sposobu, aby to ponownie ocenić. Można symulować opóźnione rozszerzenie za pomocą CALLpolecenia:

for /l %%I in (0,1,5) do call echo %%RANDOM%%
Bezpłatne konsultacje
źródło
Opóźnione rozszerzenie nie działało, ale ten / do call / działał na win7, aby mój 3-liniowy skrypt cmd znalazł prawdziwy hardlink: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') wykonaj zestaw wywołań CWD_REAL = %% echo cd / d% CWD_REAL% cd / d% CWD_REAL%
mosh
0

Warto zauważyć, że działa, jeśli jest to pojedyncze polecenie w tej samej linii.

na przykład

   if not defined BAR set FOO=1

ustawi się FOOna 1. Możesz wtedy wykonać kolejne sprawdzenie, takie jak:

   if not defined BAR echo FOO: %FOO%

Hej, nigdy nie mówiłem, że to było ładne. Ale jeśli nie chcesz zadzierać z firmą setlocal lub firmą z opóźnioną ekspansją, jest to szybkie obejście.

demoncodemonkey
źródło
0

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

W przypadku korzystania z Opóźnionego rozszerzenia zaleca się również, aby przynajmniej zameldować partię:

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

Jeśli chcesz tylko bardziej czytelne i proste podejście, oto 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óry działa w ten sposób:

>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%

to działa 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, JEŚLI TO INNE rozwiązanie składniowe:

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%

to działa 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
Arunas Bartisius
źródło