variáveis no arquivo de lote não sendo definido quando dentro IF?

62

Eu tenho dois exemplos de arquivos em lote muito simples:

Atribuindo um valor a uma variável:

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

O que, como esperado, resulta em:

FOO: 1 
Press any key to continue . . .

No entanto, se eu colocar as mesmas duas linhas dentro de um bloco IF NOT DEFINED:

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

Isso inesperadamente resulta em:

FOO: 
Press any key to continue . . .

Isso não deve ter nada a ver com o IF, claramente o bloco está sendo executado. Se eu definir BAR acima do if, somente o texto do comando PAUSE será exibido, conforme esperado.

O que dá?

Pergunta de seguimento: Existe alguma maneira de ativar a expansão atrasada sem setlocal?

Se eu fosse chamar esse arquivo de lote de exemplo simples de dentro de outro, o exemplo definiria FOO, mas apenas LOCALMENTE.

Por exemplo:

testcaller.bat

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

test.bat

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

Isso exibe:

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

Neste caso, parece que tenho que ativar a expansão atrasada no CALLER, o que pode ser um problema.

    
por Brown 03.12.2009 / 21:51

6 respostas

73

Variáveis de ambiente em arquivos em lote são expandidas quando uma linha é analisada. No caso de blocos delimitados por parênteses (como if defined ), o bloco inteiro conta como uma "linha" ou comando.

Isso significa que todas as ocorrências de% FOO% são substituídas por seus valores antes de o bloco ser executado. No seu caso com nada, já que a variável ainda não tem valor.

Para resolver isso, você pode ativar expansão atrasada :

setlocal enabledelayedexpansion

A expansão atrasada faz com que as variáveis delimitadas por pontos de exclamação ( ! ) sejam avaliadas na execução em vez da análise, o que garantirá o comportamento correto no seu caso:

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

help set detalha isso também:

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%
    
por 03.12.2009 / 22:35
3

Se não estiver funcionando dessa maneira, é provável que você tenha ativado a expansão de variável de ambiente atrasada. Você pode desativá-lo com cmd /V:OFF ou usar pontos de exclamação dentro do seu if:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
    
por 03.12.2009 / 22:02
2

O mesmo comportamento também acontece quando os comandos estão em uma única linha ( & é o separador de comando):

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

A explicação de Joey é a minha favorita. No entanto, observe que enabledelayedexpansion não funciona no Windows NT 4.0 (e não tenho certeza sobre o Windows 2000).

Sobre sua pergunta de acompanhamento, não, não é possível EnableDelayedExpansion sem setlocal . No entanto, o comportamento original que estava indo contra você pode ser usado para workharoud esse segundo problema: o truque é endlocal na mesma linha onde você define novamente os valores das variáveis que você precisa.

Aqui está seu test.bat modificado:

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

Mas aqui está outra solução para esse problema: use um procedimento no mesmo arquivo em vez de um bloco embutido ou um arquivo externo.

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
    
por 26.03.2011 / 17:26
1

Isso acontece porque sua linha com o comando FOR é avaliada apenas uma vez. Você precisa de alguma maneira de reavaliar isso. Você pode simular uma expansão atrasada com o comando CALL :

for /l %%I in (0,1,5) do call echo %%RANDOM%%
    
por 27.11.2013 / 12:52
0

Vale a pena notar que funciona se é um único comando na mesma linha.

por exemplo,

   if not defined BAR set FOO=1

irá realmente definir FOO para 1. Você pode então fazer outra verificação como:

   if not defined BAR echo FOO: %FOO%

Ei, eu nunca disse que era bonito. Mas se você não quiser mexer no setlocal ou no negócio de expansão atrasada, é uma solução rápida.

    
por 26.06.2012 / 17:36
0

Às vezes, como no meu caso, quando você precisa ser compatível com sistemas legados, como servidores NT4 antigos, em que a expansão atrasada não está funcionando ou, por algum motivo, não está funcionando, talvez seja necessária uma solução alternativa.

Caso você use a Delayd Expansion, também recomendamos que pelo menos o check-in do lote seja incluído:

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

Caso você queira apenas uma abordagem mais legível e simples, veja aqui soluções alternativas:

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

que funciona assim:

>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

e mais uma solução cmd pura:

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

funciona assim:

>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

e esta é a solução de sintaxe IF THEN ELSE full:

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%

funciona assim:

>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
    
por 28.12.2016 / 22:46