2008-11-20 12 views
17

J'écris un fichier .bat simple et j'ai rencontré un comportement étrange. Il y a quelques endroits où je dois faire un simple if/else, mais le code à l'intérieur des blocs ne semble pas fonctionner correctement.Problème de portée étrange dans le fichier .bat

est ici un cas simple qui démontre l'erreur:

@echo off 

set MODE=FOOBAR 

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

La sortie que je reçois est:

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

Pourquoi l'écho à l'intérieur du bloc de code ne pas obtenir la nouvelle valeur de la variable? Dans le code que j'écris, j'ai besoin de construire quelques variables et de les référencer dans le cadre de l'if/else. Je pourrais changer cela pour utiliser des labels et gotos au lieu d'un if/else, mais cela ne semble pas aussi propre.

Quelles sont les causes de ce problème? Existe-t-il une sorte de limite sur les variables dans les blocs de code?

Répondre

26

Vous rencontrez le problème de l'expansion de variable statique de cmd. La variable MODE est seulement évaluée une fois. Vous pouvez le voir si vous omettez la ligne hors ligne @echo.

De l'ensemble /? documentation:

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

Y at-il un moyen de mettre le drapeau programatically au sommet d'un fichier de chauve-souris? La plupart du temps, ceci sera exécuté soit par un autre fichier bat, et l'environnement dans lequel il est exécuté n'est pas corrigé (il peut être double-cliqué, peut être exécuté à partir d'un shell cygwin, peut-être à partir de cmd, etc.). – Herms

+3

@Herms, "setlocal enabledelayedexpansion" au début, "endlocal" à la fin. – paxdiablo

+0

Merci. Cela a sauvé mes fesses paxdiablo. –

-2

Il semble que la lecture et l'écriture utilisent des règles de portée différentes.

Si vous supprimez cette ligne

set MODE=FOOBAR 

il fonctionnera comme prévu. Donc, vous aurez probablement besoin d'une série complexe si if/elses pour obtenir les variables peuplées comme vous le souhaitez.

+0

En fait non, ce n'est pas le cas. Cet ensemble au sommet n'était pas à l'origine là. Si je l'omets, la première fois que je lance le fichier chauve-souris, le premier écho est vide. La deuxième fois que je l'exécute (à partir de la même instance cmd), le premier écho affiche la dernière valeur utilisée. – Herms

+0

Vous avez raison, je courrais le script une deuxième fois .. bien repéré! –

+0

Supprimer votre réponse –

2

ENABLEDELAYEDEXPANSION setlocal

permettra au/v drapeau