2008-11-20 15 views
17

Piszę prosty plik .bat i mam dziwne zachowanie. Jest kilka miejsc, w których muszę zrobić proste, jeśli/inaczej, ale kod wewnątrz bloków nie działa poprawnie.Problem z dziwnym zasięgiem w pliku .bat

Oto prosty przypadek, który pokazuje błąd:

@echo off 

set MODE=FOOBAR 

if "%~1"=="" (
    set MODE=all 
    echo mode: %MODE% 
) else (
    set MODE=%~1 
    echo mode: %MODE% 
) 
echo mode: %MODE% 

Wyjście Dostaję jest:

C:\>test.bat test 
mode: FOOBAR 
mode: test 

Dlaczego echo wewnątrz bloku kodu nie otrzymuję nową wartość zmienna? W aktualnym kodzie, który piszę, potrzebuję zbudować kilka zmiennych i wskazać je w zakresie if/else. Mogłabym to zmienić, aby używać etykiet i znaków zamiast znaku "/ else", ale to nie wydaje się prawie tak czyste.

Co powoduje takie zachowanie? Czy istnieje jakiś limit zmiennych w blokach kodu?

Odpowiedz

26

Występuje problem statycznej ekspansji zmiennych cmd. Zmienna MODE jest oceniana tylko raz. Możesz to zobaczyć, jeśli nie używasz @echo off line.

Z zestawu /? dokumentacja:

Finally, support for delayed environment variable expansion has been added. This support is always disabled by default, but may be enabled/disabled via the /V command line switch to CMD.EXE. See CMD /?

Delayed environment variable expansion is useful for getting around the limitations of the current expansion which happens when a line of text is read, not when it is executed. The following example demonstrates the problem with immediate variable expansion:

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

would never display the message, since the %VAR% in BOTH IF statements is substituted when the first IF statement is read, since it logically includes the body of the IF, which is a compound statement. So the IF inside the compound statement is really comparing "before" with "after" which will never be equal. Similarly, the following example will not work as expected:

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

in that it will NOT build up a list of files in the current directory, but instead will just set the LIST variable to the last file found. Again, this is because the %LIST% is expanded just once when the FOR statement is read, and at that time the LIST variable is empty. So the actual FOR loop we are executing is:

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

which just keeps setting LIST to the last file found.

Delayed environment variable expansion allows you to use a different character (the exclamation mark) to expand environment variables at execution time. If delayed variable expansion is enabled, the above examples could be written as follows to work as intended:

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

Czy istnieje sposób, aby ustawić tę flagę programowo w górnej części pliku bat? W większości przypadków będzie on uruchamiany przez inny plik nietoperza, a środowisko, w którym działa, nie jest naprawione (może być dwukrotne kliknięcie, może być uruchamiane z powłoki cygwin, może pochodzić z cmd itp.). – Herms

+3

@Herms, "setlocal enabledelayedexpansion" na początku, "endlocal" na końcu. – paxdiablo

+0

Dzięki. To uratowało mój tyłek paxdiablo. –

-2

Wygląda jak odczyt i zapis przy użyciu różnych reguł ustalania zakresu.

Jeśli wyeliminować ten wiersz

set MODE=FOOBAR 

będzie działać zgodnie z oczekiwaniami. Prawdopodobnie będziesz potrzebować złożonej serii, jeśli/elses, aby zmienne zostały wypełnione tak, jak chcesz.

+0

Właściwie nie, nie ma. Ten zestaw na górze nie był tam pierwotnie. Jeśli pominę to, to po pierwszym uruchomieniu pliku nietoperza pierwsze echo jest puste. Przy drugim uruchomieniu (z tej samej instancji cmd) pierwsze echo pokazuje ostatnio używaną wartość. – Herms

+0

Masz rację, uruchomiłbym skrypt po raz drugi ... dobrze zauważony! –

+0

Czy usunąć odpowiedź? –

2

setlocal EnableDelayedExpansion

pozwoli/v flagę