web-dev-qa-db-fra.com

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

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

Ceci de manière inattendue se traduit par:

FOO: 
Press any key to continue . . .

Ceci ne devrait pas avoir quelque chose à 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 seulement LOCALEMENT.

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’expansion différée dans l’appelant, ce qui peut être fastidieux.

66
Brown

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 (en tant que if defined), le bloc entier compte pour une "ligne" ou une commande.

Cela signifie que toutes les occurrences de% FOO% sont remplacées par leurs valeurs before le bloc est exécuté. Dans votre cas, rien, que la variable n'a pas encore une valeur.

Pour résoudre ce problème, vous pouvez activer expansion retardée :

setlocal enabledelayedexpansion

L'extension retardée entraîne l'évaluation des variables délimitées par des points d'exclamation (!) 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étaille 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 commutateur de ligne de commande /V en CMD.EXE. Voir CMD /?

Une extension de variable d'environnement retardée est utile pour contourner les limitations de l'expansion 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, puisque les instructions %VAR% dans both IF sont remplacées lors de la lecture de la première instruction IF, puisqu’elle inclut logiquement le corps de la variable 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 que not construit une liste de fichiers dans le répertoire en cours, mais définit simplement la variable LIST sur le dernier fichier trouvé. Encore une fois, cela est parce que le %LIST% est étendu juste une fois lorsque l'instruction FOR est lu, et à ce moment la variable LIST est vide. La boucle FOR que nous exécutons est la suivante:

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

qui ne fait que garder LIST au 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 l'extension de variable retardée est activée, les exemples ci-dessus pourraient ê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%
81
Joey

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

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

L'explication de Joey est mon préféré. Notez cependant que enabledelayedexpansion 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 de EnableDelayedExpansion sans setlocal. Cependant, le comportement initial qui vous opposait peut être utilisé pour résoudre ce deuxième problème: l'astuce consiste à endlocal sur la même ligne où vous définissez à nouveau les valeurs des variables dont vous avez besoin.

Voici votre test.bat modifié:

@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
4
dolmen

Si cela ne fonctionne pas de cette façon, l'expansion des variables d'environnement est probablement retardée. Vous pouvez soit le désactiver avec cmd /V:OFF, soit 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
3
John T

Cela se produit car votre ligne avec la commande FOR n'évalue qu'une seule fois. Vous avez besoin d'un moyen de le réévaluer. Vous pouvez simuler une expansion différée avec la commande CALL:

for /l %%I in (0,1,5) do call echo %%RANDOM%%
1
Free Consulting

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

va effectivement mettre 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.

0
demoncodemonkey

Parfois, comme je l’ai fait dans mon cas, lorsque vous devez ê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
0
Arunas Bartisius