2009-12-03 20:51:48 +0000 2009-12-03 20:51:48 +0000
72
72

Variáveis em ficheiro de lote não sendo definidas quando dentro de IF?

Tenho dois exemplos de ficheiros de lotes muito simples:

Atribuição de 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 . . .

Contudo, se eu colocar as mesmas duas linhas dentro de um bloco SE NÃO DEFINIDO:

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

Isto unexpectedly resulta em:

FOO: 
Press any key to continue . . .

Este não deve_ ter nada a ver com o FI, claramente o bloco está a ser executado. Se eu definir BAR acima do if, apenas o texto do comando PAUSE é exibido, como esperado.

O que dá?


Pergunta de seguimento: Existe alguma forma de permitir a expansão retardada sem setlocal?

Se eu chamasse a este simples exemplo ficheiro de lote a partir de dentro de outro, o exemplo define 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! 
)

Isto mostra:

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

Neste caso, parece que tenho de permitir uma expansão retardada no CALLER, o que pode ser um incómodo.

Respostas (6)

86
86
86
2009-12-03 21:35:26 +0000

As variáveis ambientais nos ficheiros de lote são expandidas quando uma linha é analizada. No caso de blocos delimitados por parênteses (como o seu if defined), o bloco inteiro conta como uma “linha” ou comando.

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

Para resolver isto, pode activar a expansão atrasada:

setlocal enabledelayedexpansion

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

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

help set detalha isto também:

Finalmente, foi adicionado o suporte para expansão retardada de variáveis de ambiente. Este suporte é sempre desactivado por defeito, mas pode ser activado/desactivado através do interruptor de linha de comando /V para CMD.EXE. Ver CMD /? & > & > A expansão da variável de ambiente atrasada é útil para contornar as limitações da expansão actual que acontece quando uma linha de texto é lida, e não quando é executada. O exemplo seguinte demonstra o problema com a expansão imediata da variável:

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

nunca mostraria a mensagem, uma vez que o %VAR% em both IF declarações é substituído quando a primeira declaração IF é lida, uma vez que inclui logicamente o corpo do IF, que é uma declaração composta. Assim, o FI dentro da declaração composta está realmente a comparar “antes” com “depois”, que nunca será igual. Da mesma forma, o exemplo seguinte não funcionará como esperado:

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

& > na medida em que não construirá uma lista de ficheiros no directório actual, mas em vez disso apenas definirá a variável LIST para o último ficheiro encontrado. Mais uma vez, isto é porque a variável %LIST% é expandida apenas uma vez quando a declaração FOR é lida, e nesse momento a variável LIST está vazia. Assim, o actual PARA loop que estamos a executar é:

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

& > que apenas continua a definir LIST para o último ficheiro encontrado.

& > A expansão da variável de ambiente atrasada permite utilizar um carácter diferente (o ponto de exclamação) para expandir as variáveis de ambiente no momento da execução. Se a expansão retardada de variáveis estiver activada, os exemplos acima podem ser escritos como se segue para funcionar como pretendido:

& > “` 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% “`

4
4
4
2011-03-26 16:26:14 +0000
3
3
3
2009-12-03 21:02:01 +0000

Se não estiver a funcionar dessa forma, é provável que tenha atrasado a expansão das variáveis de ambiente. Pode desligá-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
1
1
1
2013-11-27 11:52:14 +0000

Isto acontece porque a sua linha com o comando FOR avalia apenas uma vez. Precisa de alguma forma de a reavaliar. Pode simular uma expansão retardada com o comando CALL:

for /l %%I in (0,1,5) do call echo %%RANDOM%%
0
0
0
2016-12-28 21:46:34 +0000

Por vezes, como tive no meu caso, quando é necessário ser compatível com sistemas herdados como os antigos servidores NT4 onde a expansão retardada não está a funcionar ou por alguma razão não está a funcionar, pode ser necessária uma solução alternativa.

No caso de utilizar a Expansão Atrasada, recomenda-se também que pelo menos a verificação em lote seja incluída:

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

Caso pretenda apenas uma abordagem mais legível e simples, aqui estão 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 desta forma:

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

que 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 completa IF THEN ELSE:

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
0
0
0
2012-06-26 15:36:32 +0000

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

ex.

if not defined BAR set FOO=1

irá de facto definir FOO para 1. Poderá então fazer outra verificação, como por exemplo:

if not defined BAR echo FOO: %FOO%

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