Variáveis ​​no arquivo em lotes não estão sendo definidas quando dentro de IF?

69

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, apenas o texto do comando PAUSE será exibido, conforme o esperado.

O que da?


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

Se eu chamar esse arquivo em lote de exemplo simples de outro, o exemplo define FOO, mas apenas LOCALLY.

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

Nesse caso, parece que eu tenho que ativar a expansão atrasada no CALLER, o que pode ser um aborrecimento.

Castanho
fonte

Respostas:

84

As variáveis ​​de ambiente nos arquivos em lote são expandidas quando uma linha é analisada. No caso de blocos delimitados por parênteses (como o seu 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 que o bloco seja executado. No seu caso sem nada, pois a variável ainda não tem um valor.

Para resolver isso, você pode ativar 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 analisar, 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:

Finalmente, foi adicionado suporte para expansão de variável de ambiente atrasada. Esse suporte está sempre desativado por padrão, mas pode ser ativado / desativado através da /Vopção de linha de comando para CMD.EXE. VejoCMD /?

A expansão de variável de ambiente atrasada é útil para contornar as limitações da expansão atual, que ocorre quando uma linha de texto é lida, não quando é executada. O exemplo a seguir demonstra o problema com a expansão imediata de variáveis:

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

nunca exibiria a mensagem, pois as instruções %VAR%em ambas IF são substituídas quando a primeira instrução IF é lida, uma vez que inclui logicamente o corpo da IF, que é uma instrução composta. Portanto, o IF dentro da instrução composta está realmente comparando "antes" com "depois", o que nunca será igual. Da mesma forma, o exemplo a seguir não funcionará conforme o esperado:

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

na medida em que não criará uma lista de arquivos no diretório atual, mas apenas definirá a LIST variável para o último arquivo encontrado. Novamente, isso ocorre porque o %LIST%é expandido apenas uma vez quando a FOR instrução é lida e, nesse momento, a LISTvariável está vazia. Portanto, o loop FOR real que estamos executando é:

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

que continua definindo LISTo último arquivo encontrado.

A expansão de variável de ambiente atrasada permite que você use um caractere diferente (o ponto de exclamação) para expandir as variáveis ​​de ambiente no tempo de execução. Se a expansão variável atrasada estiver ativada, os exemplos acima podem ser escritos da seguinte maneira 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%
Joey
fonte
17
E se precisar ecoar !, use ^^^!(escape duas vezes). Caso contrário, o recurso "expansão atrasada" o consumirá.
grawity
4

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

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

A explicação de Joey é a minha favorita. Observe, no entanto, que enabledelayedexpansionnã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 EnableDelayedExpansionsem setlocal. No entanto, o comportamento original que estava indo contra você pode ser usado para solucionar esse segundo problema: o truque é endlocalna mesma linha em que você define novamente os valores das variáveis ​​necessárias.

Aqui está o seu test.batmodificado:

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

Mas aqui está outra solução alternativa 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
dolmen
fonte
"o truque é endlocal na mesma linha" - OBRIGADO por isso!
dforce 29/04
3

Se não estiver funcionando dessa maneira, é provável que você tenha atrasado a expansão da variável de ambiente. Você pode desativá-lo cmd /V:OFFou usar pontos de exclamação dentro do seu se:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
John T
fonte
3
A ativação da expansão atrasada altera apenas a semântica do ponto de exclamação. Não tem nenhum efeito na expansão normal de variáveis. Além disso, habilitar acidentalmente é muito improvável; as pessoas que o habilitam geralmente o fazem de propósito.
Joey
1

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

for /l %%I in (0,1,5) do call echo %%RANDOM%%
Consultoria gratuita
fonte
A expansão atrasada não funcionou, mas isso / chame / funcionou no win7, para que meu script cmd de 3 linhas encontre o link real: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') chamam o conjunto CWD_REAL = %% um cd de eco / d% CWD_REAL% cd / d% CWD_REAL%
mosh
0

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

por exemplo

   if not defined BAR set FOO=1

será realmente definido FOOcomo 1. Você poderá 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 com o setlocal ou com os negócios de expansão atrasada, é uma solução rápida.

demoncodemonkey
fonte
0

Às vezes, como eu fazia 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, você pode precisar de uma solução alternativa.

Caso você use a Expansão atrasada, também é recomendável incluir pelo menos o lote de check-in:

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

Caso você queira 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 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 completa SE ENTÃO 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
Arunas Bartisius
fonte