Variabel dalam file batch tidak diatur ketika di dalam IF?

69

Saya punya dua contoh file batch yang sangat sederhana:

Menetapkan nilai ke variabel:

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

Yang, seperti yang diharapkan, menghasilkan:

FOO: 1 
Press any key to continue . . .

Namun, jika saya menempatkan dua baris yang sama di dalam blok JIKA TIDAK DITETAPKAN:

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

Ini secara tak terduga menghasilkan:

FOO: 
Press any key to continue . . .

Ini seharusnya tidak ada hubungannya dengan IF, jelas blok sedang dieksekusi. Jika saya mendefinisikan BAR di atas if, hanya teks dari perintah PAUSE yang ditampilkan, seperti yang diharapkan.

Apa yang menyebabkannya?


Pertanyaan tindak lanjut: Apakah ada cara untuk memungkinkan ekspansi yang tertunda tanpa setlocal?

Jika saya akan memanggil file batch contoh sederhana ini dari dalam yang lain, contoh menetapkan FOO, tetapi hanya LOCALLY.

Sebagai contoh:

testcaller.bat

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

test.bat

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

Ini menampilkan:

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

Dalam hal ini, tampaknya saya harus mengaktifkan ekspansi yang tertunda di CALLER, yang mungkin merepotkan.

cokelat
sumber

Jawaban:

84

Variabel lingkungan dalam file batch diperluas ketika garis diuraikan. Dalam hal blok dibatasi oleh tanda kurung (seperti Anda if defined) seluruh blok dihitung sebagai "baris" atau perintah.

Ini berarti bahwa semua kemunculan% FOO% diganti dengan nilainya sebelum blok dijalankan. Dalam kasus Anda tanpa apa-apa, karena variabel belum memiliki nilai.

Untuk mengatasi ini, Anda dapat mengaktifkan ekspansi yang tertunda :

setlocal enabledelayedexpansion

Ekspansi yang tertunda menyebabkan variabel yang dibatasi oleh tanda seru ( !) dievaluasi pada eksekusi alih-alih penguraian yang akan memastikan perilaku yang benar dalam kasus Anda:

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

help set merinci ini juga:

Akhirnya, dukungan untuk ekspansi variabel lingkungan tertunda telah ditambahkan. Dukungan ini selalu dinonaktifkan secara default, tetapi dapat diaktifkan / dinonaktifkan melalui /Vsaklar baris perintah ke CMD.EXE. LihatCMD /?

Ekspansi variabel lingkungan yang tertunda berguna untuk mengatasi batasan ekspansi saat ini yang terjadi ketika baris teks dibaca, bukan ketika dieksekusi. Contoh berikut menunjukkan masalah dengan ekspansi variabel langsung:

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

tidak akan pernah menampilkan pesan, karena %VAR%di kedua IF pernyataan diganti ketika pernyataan IF pertama dibaca, karena secara logis termasuk tubuh IF, yang merupakan pernyataan majemuk. Jadi JIKA di dalam pernyataan majemuk benar-benar membandingkan "sebelum" dengan "setelah" yang tidak akan pernah sama. Demikian pula, contoh berikut tidak akan berfungsi seperti yang diharapkan:

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

dalam hal itu tidak akan membangun daftar file di direktori saat ini, tetapi hanya akan mengatur LIST variabel ke file terakhir yang ditemukan. Sekali lagi, ini karena %LIST%diperluas hanya sekali ketika FOR pernyataan dibaca, dan pada saat itu LISTvariabelnya kosong. Jadi loop FOR aktual yang kita jalankan adalah:

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

yang hanya menyimpan pengaturan LISTke file terakhir yang ditemukan.

Ekspansi variabel lingkungan yang tertunda memungkinkan Anda untuk menggunakan karakter yang berbeda (tanda seru) untuk memperluas variabel lingkungan pada waktu eksekusi. Jika ekspansi variabel yang tertunda diaktifkan, contoh-contoh di atas dapat ditulis sebagai berikut agar berfungsi sebagaimana dimaksud:

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
sumber
17
Dan jika Anda perlu menggema !, gunakan ^^^!(lepas dua kali). Kalau tidak, fitur "ekspansi tertunda" akan memakannya.
grawity
4

Perilaku yang sama juga terjadi ketika perintah berada pada satu baris ( &adalah pemisah perintah):

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

Penjelasan Joey adalah favorit saya. Namun perhatikan bahwa enabledelayedexpansionitu tidak berfungsi pada Windows NT 4.0 (dan saya tidak yakin tentang Windows 2000).

Tentang pertanyaan tindak lanjut Anda, tidak, tidak mungkin untuk EnableDelayedExpansiontanpa setlocal. Namun perilaku asli yang bertentangan dengan Anda dapat digunakan untuk mengatasi masalah kedua: triknya adalah endlocalpada baris yang sama di mana Anda mengatur kembali nilai variabel yang Anda butuhkan.

Ini test.batmodifikasi Anda :

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

Tapi di sini ada solusi lain untuk masalah itu: gunakan prosedur dalam file yang sama alih-alih blok inline atau file eksternal.

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
dolmen
sumber
"Kuncinya adalah endlocal pada baris yang sama" - TERIMA KASIH untuk itu!
dforce
3

Jika tidak berfungsi seperti itu, Anda kemungkinan telah menunda ekspansi variabel lingkungan. Anda dapat mematikannya dengan cmd /V:OFFatau menggunakan tanda seru di dalam if Anda:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
John T
sumber
3
Mengaktifkan ekspansi yang tertunda hanya mengubah semantik dari tanda seru. Itu tidak memiliki efek apa pun pada ekspansi variabel normal. Selain itu, mengaktifkannya secara tidak sengaja sangat tidak mungkin; orang yang mengaktifkannya biasanya melakukannya dengan sengaja.
Joey
1

Ini terjadi karena baris Anda dengan FORperintah hanya mengevaluasi sekali. Anda perlu beberapa cara untuk mengevaluasi kembali. Anda dapat mensimulasikan ekspansi yang tertunda dengan CALLperintah:

for /l %%I in (0,1,5) do call echo %%RANDOM%%
Konsultasi Gratis
sumber
Ekspansi yang tertunda tidak berfungsi, tetapi ini / lakukan panggilan / bekerja pada win7, untuk skrip cmd 3 baris saya untuk menemukan hardlink nyata: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') melakukan panggilan set CWD_REAL = %% a echo cd / d% CWD_REAL% cd / d% CWD_REAL%
mosh
0

Perlu dicatat bahwa itu TIDAK berfungsi jika itu adalah perintah tunggal pada baris yang sama.

misalnya

   if not defined BAR set FOO=1

akan benar-benar diatur FOOke 1. Anda kemudian dapat melakukan pemeriksaan lain seperti:

   if not defined BAR echo FOO: %FOO%

Hei, aku tidak pernah bilang itu cantik. Tetapi jika Anda tidak ingin mengacaukan setlocal atau bisnis ekspansi yang tertunda, ini adalah solusi cepat.

demoncodemonkey
sumber
0

Kadang-kadang, seperti yang saya miliki dalam kasus saya, ketika Anda harus kompatibel dengan sistem lama seperti server NT4 lama di mana ekspansi tertunda tidak berfungsi atau karena alasan tertentu tidak berfungsi, Anda mungkin perlu solusi alternatif.

Jika Anda akan menggunakan Delayd Expansion, disarankan juga agar setidaknya check-in batch harus disertakan:

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

Jika Anda hanya ingin pendekatan yang lebih mudah dibaca dan sederhana, berikut adalah solusi alternatif:

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

yang berfungsi seperti ini:

>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

dan satu lagi solusi cmd murni:

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

kerjanya seperti ini:

>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

dan ini penuh JIKA KEMUDIAN solusi sintaks LAIN:

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%

kerjanya seperti ini:

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