Les variables du fichier de commandes ne sont pas définies quand elles sont dans l'IF?

69

J'ai deux exemples de fichiers de commandes très simples:

Assigner une valeur à une variable:

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

Ce qui, comme prévu, aboutit à:

FOO: 1 
Press any key to continue . . .

Cependant, si je place les deux mêmes lignes dans un bloc IF NOT DEFINED:

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

Cela a pour résultat inattendu :

FOO: 
Press any key to continue . . .

Cela ne devrait avoir rien à voir avec le SI, il est clair que le bloc est en cours d'exécution. Si je définis BAR au-dessus du if, seul le texte de la commande PAUSE est affiché, comme prévu.

Ce qui donne?


Question de suivi: Existe-t-il un moyen d'activer l'expansion retardée sans setlocal?

Si je devais appeler cet exemple de fichier de commandes depuis un autre, l’exemple définit FOO, mais uniquement LOCAL.

Par exemple:

testcaller.bat

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

test.bat

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

Cela affiche:

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

Dans ce cas, il apparaît que je dois activer l’extension retardée dans l’appelant, ce qui peut être fastidieux.

marron
la source

Réponses:

84

Les variables d'environnement dans les fichiers de commandes sont développées lors de l'analyse d'une ligne. Dans le cas de blocs délimités par des parenthèses (comme votre if defined), tout le bloc compte comme une "ligne" ou une commande.

Cela signifie que toutes les occurrences de% FOO% sont remplacées par leurs valeurs avant l'exécution du bloc. Dans votre cas avec rien, comme la variable n'a pas encore de valeur.

Pour résoudre ce problème, vous pouvez activer l’ extension retardée :

setlocal enabledelayedexpansion

Le développement retardé entraîne l' !évaluation des variables délimitées par des points d'exclamation ( ) lors de l'exécution au lieu de l'analyse, ce qui garantit le comportement correct dans votre cas:

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

help set Détails ceci aussi:

Enfin, la prise en charge de l’expansion différée des variables d’environnement a été ajoutée. Cette prise en charge est toujours désactivée par défaut, mais peut être activée / désactivée via le /Vcommutateur de ligne de commande sur CMD.EXE. VoirCMD /?

Une extension de variable d'environnement retardée est utile pour contourner les limitations de l'extension actuelle, qui se produisent lorsqu'une ligne de texte est lue, et non lorsqu'elle est exécutée. L'exemple suivant montre le problème de l'expansion immédiate des variables:

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

n’afficherait jamais le message, car %VAR%dans les deux IF instructions, la première instruction IF est lue, car elle inclut logiquement le corps de la IF, qui est une instruction composée. Ainsi, le SI dans la déclaration composée compare réellement "avant" avec "après", ce qui ne sera jamais égal. De même, l'exemple suivant ne fonctionnera pas comme prévu:

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

en ce sens qu'il ne construira pas de liste de fichiers dans le répertoire actuel, mais simplement définira la LIST variable sur le dernier fichier trouvé. Encore une fois, cela est dû au %LIST%fait FOR que le LISTparamètre n'est développé qu'une seule fois lors de la lecture de l' instruction et que, à ce moment, la variable est vide. La boucle FOR que nous exécutons est la suivante:

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

qui ne fait que garder LISTle dernier fichier trouvé.

L'expansion retardée des variables d'environnement vous permet d'utiliser un caractère différent (le point d'exclamation) pour développer les variables d'environnement au moment de l'exécution. Si le développement retardé de variable est activé, les exemples ci-dessus peuvent être écrits comme suit pour fonctionner comme prévu:

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
la source
17
Et si vous avez besoin d'un écho !, utilisez ^^^!(évitez-le deux fois). Sinon, la fonction "expansion retardée" le mangera.
grawity
4

Le même comportement se produit également lorsque les commandes se trouvent sur une seule ligne ( &séparateur de commandes):

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

L'explication de Joey est mon préféré. Notez cependant que enabledelayedexpansioncela ne fonctionne pas sous Windows NT 4.0 (et je ne suis pas sûr de Windows 2000).

Concernant votre question de suivi, non, il n’est pas possible d’en EnableDelayedExpansionpasser setlocal. Cependant, le comportement initial qui vous a été opposé peut être utilisé pour résoudre ce deuxième problème: l’astuce consiste à endlocalplacer sur la même ligne les valeurs des variables dont vous avez besoin.

Voici votre test.batmodifié:

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

Mais voici une autre solution à ce problème: utilisez une procédure dans le même fichier au lieu d’un bloc en ligne ou d’un fichier externe.

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
dolmen
la source
"Le truc est de finir sur la même ligne" - MERCI pour ça!
dforce le
3

Si cela ne fonctionne pas de cette façon, l'expansion des variables d'environnement est probablement retardée. Vous pouvez l'éteindre cmd /V:OFFou utiliser des points d'exclamation à l'intérieur de votre if:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
John T
la source
3
L'activation de l' expansion retardée modifie uniquement la sémantique du point d'exclamation. Cela n'a aucun effet sur l'expansion des variables normales. De plus, l'activer accidentellement est très improbable; les personnes qui l'ont activé le font généralement exprès.
Joey
1

Cela se produit parce que votre ligne avec FORcommande n’évalue qu’une fois. Vous avez besoin d'un moyen de le réévaluer. Vous pouvez simuler une expansion retardée avec la CALLcommande:

for /l %%I in (0,1,5) do call echo %%RANDOM%%
Consultation gratuite
la source
L’extension retardée ne fonctionnait pas, mais cet appel / do / travaillait sur win7, car mon script cmd à 3 lignes cherchait le lien réel: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') appelle le groupe CWD_REAL = %% echo cd / d% CWD_REAL% cd / d% CWD_REAL%
mosh
0

Il est à noter que cela fonctionne si c'est une seule commande sur la même ligne.

par exemple

   if not defined BAR set FOO=1

sera effectivement mis FOOà 1. Vous pouvez alors faire une autre vérification telle que:

   if not defined BAR echo FOO: %FOO%

Hey, je n'ai jamais dit que c'était joli. Mais si vous ne voulez pas vous mêler de la problématique de setlocal ou de l'expansion retardée, la solution de contournement est rapide.

demoncodemonkey
la source
0

Parfois, comme je l’ai fait dans mon cas, lorsque vous avez besoin d’être compatible avec les systèmes existants, tels que les anciens serveurs NT4 où l’extension retardée ne fonctionne pas ou pour une raison quelconque ne fonctionne pas, vous aurez peut-être besoin d’une autre solution.

Si vous utilisez Delayd Expansion, il est également recommandé d’inclure au moins le lot d’enregistrement:

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

Si vous souhaitez simplement une approche plus lisible et simple, voici d'autres solutions:

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

qui fonctionne comme ceci:

>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

et encore une solution pure de cmd:

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

cela fonctionne comme ceci:

>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

et ceci est complet SI ALORS ELSE solution de syntaxe:

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%

cela fonctionne comme ceci:

>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
la source