ตัวแปรในแบตช์ไฟล์ไม่ได้ถูกตั้งค่าเมื่ออยู่ภายใน IF?


67

ฉันมีสองตัวอย่างของไฟล์แบทช์ที่ง่ายมาก:

การกำหนดค่าให้กับตัวแปร:

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

ซึ่งตามที่คาดไว้ผลลัพธ์ใน:

FOO: 1 
Press any key to continue . . .

อย่างไรก็ตามถ้าฉันวางสองบรรทัดเดียวกันไว้ในบล็อก IF NOT DEFINED:

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

ผลลัพธ์นี้โดยไม่คาดหมายใน:

FOO: 
Press any key to continue . . .

นี่ไม่ควรเกี่ยวข้องกับ IF แต่อย่างใดบล็อกกำลังถูกดำเนินการ หากฉันกำหนด BAR ไว้ด้านบน if ข้อความจากคำสั่ง PAUSE เท่านั้นที่จะปรากฏขึ้นตามที่คาดไว้

สิ่งที่ช่วยให้?


คำถามติดตามผล: มีวิธีใดบ้างที่จะเปิดใช้งานการขยายที่ล่าช้าโดยไม่มี setlocal

ถ้าฉันจะเรียกไฟล์แบตช์ตัวอย่างง่าย ๆ นี้จากข้างในอีกตัวอย่างก็ตั้งค่า FOO แต่เฉพาะ LOCALLY

ตัวอย่างเช่น:

testcaller.bat

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

test.bat

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

สิ่งนี้แสดง:

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

ในกรณีนี้ปรากฏว่าฉันต้องเปิดใช้งานการขยายเวลาล่าช้าใน CALLER ซึ่งอาจเป็นเรื่องยุ่งยาก

คำตอบ:


82

ตัวแปรสภาพแวดล้อมในไฟล์แบตช์จะถูกขยายเมื่อแยกบรรทัด ในกรณีของบล็อกคั่นด้วยวงเล็บ (เป็นของคุณif defined) บล็อกทั้งหมดนับเป็น "บรรทัด" หรือคำสั่ง

ซึ่งหมายความว่าการเกิดขึ้นทั้งหมดของ% FOO% จะถูกแทนที่ด้วยค่าของพวกเขาก่อนที่บล็อกจะถูกเรียกใช้ ในกรณีของคุณไม่มีอะไรเนื่องจากตัวแปรยังไม่มีค่า

ในการแก้ปัญหานี้คุณสามารถเปิดใช้งานการขยายที่ล่าช้าได้ :

setlocal enabledelayedexpansion

การขยายเวลาที่ล่าช้าทำให้เกิดตัวแปรที่คั่นด้วยเครื่องหมายอัศเจรีย์ ( !) เพื่อประเมินการดำเนินการแทนการแยกวิเคราะห์ซึ่งจะช่วยให้แน่ใจว่าพฤติกรรมที่ถูกต้องในกรณีของคุณ:

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

help set รายละเอียดนี้เกินไป:

ในที่สุดรองรับการขยายตัวของตัวแปรสภาพแวดล้อมที่ล่าช้า การสนับสนุนนี้ถูกปิดใช้งานอยู่เสมอโดยค่าเริ่มต้น แต่อาจจะมีการเปิดใช้งาน / ปิดการใช้งานผ่านทางสวิตช์บรรทัดคำสั่งไปยัง/V CMD.EXEดูCMD /?

การขยายตัวของตัวแปรสภาพแวดล้อมที่ล่าช้ามีประโยชน์สำหรับการหลีกเลี่ยงข้อ จำกัด ของการขยายปัจจุบันซึ่งเกิดขึ้นเมื่อมีการอ่านบรรทัดข้อความไม่ใช่เมื่อมีการดำเนินการ ตัวอย่างต่อไปนี้แสดงให้เห็นถึงปัญหากับการขยายตัวแปรทันที:

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

จะไม่แสดงข้อความเนื่องจาก%VAR%ในทั้งสอง IFคำสั่งจะถูกแทนที่เมื่ออ่านคำสั่ง IF ครั้งแรกเนื่องจากมันรวมถึงเนื้อหาของตรรกะIFซึ่งเป็นคำสั่งผสม ดังนั้น IF ภายในคำสั่งผสมคือการเปรียบเทียบ "ก่อน" กับ "หลังจาก" ซึ่งจะไม่เท่ากัน ตัวอย่างต่อไปนี้จะไม่ทำงานตามที่คาดไว้:

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

ตรงที่มันจะไม่สร้างรายการของไฟล์ในไดเรกทอรีปัจจุบัน แต่จะตั้งค่าLIST ตัวแปรเป็นไฟล์สุดท้ายที่พบแทน อีกครั้งนี้เป็นเพราะการ%LIST%ขยายตัวเพียงครั้งเดียวเมื่อFOR อ่านคำสั่งและในเวลานั้นLISTตัวแปรว่างเปล่า ดังนั้น FOR FOR จริง ๆ ที่เราเรียกใช้งานคือ:

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

ซึ่งเพิ่งตั้งค่าLISTไปยังไฟล์ล่าสุดที่พบ

การขยายตัวแปรสภาพแวดล้อมที่ล่าช้าช่วยให้คุณสามารถใช้อักขระอื่น (เครื่องหมายอัศเจรีย์) เพื่อขยายตัวแปรสภาพแวดล้อมในเวลาดำเนินการ หากเปิดใช้งานการขยายตัวตัวแปรล่าช้าล่าช้าตัวอย่างข้างต้นสามารถเขียนได้ดังนี้เพื่อทำงานตามที่ตั้งใจไว้:

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%

17
และถ้าคุณต้องการที่จะสะท้อนให้!ใช้^^^!(หนีสองครั้ง) มิฉะนั้นคุณสมบัติ "การขยายตัวล่าช้า" จะกินมัน
grawity

4

พฤติกรรมเดียวกันนี้ยังเกิดขึ้นเมื่อคำสั่งอยู่บนบรรทัดเดียว ( &คือตัวคั่นคำสั่ง):

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

คำอธิบายของ Joey เป็นสิ่งที่ฉันโปรดปราน อย่างไรก็ตามโปรดทราบว่าenabledelayedexpansionไม่สามารถใช้งานได้กับ Windows NT 4.0 (และฉันไม่แน่ใจเกี่ยวกับ Windows 2000)

เกี่ยวกับคำถามติดตามของคุณไม่มันไม่ได้เป็นไปได้ที่จะได้โดยไม่ต้องEnableDelayedExpansion setlocalอย่างไรก็ตามพฤติกรรมดั้งเดิมที่ขัดแย้งกับคุณสามารถใช้เพื่อแก้ไขปัญหาที่สอง: เคล็ดลับคือการendlocalอยู่ในบรรทัดเดียวกันกับที่คุณตั้งค่าของตัวแปรที่คุณต้องการอีกครั้ง

นี่คือการtest.batแก้ไขของคุณ:

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

แต่นี่เป็นวิธีแก้ปัญหาอื่นสำหรับปัญหานั้น: ใช้โพรซีเดอร์ในไฟล์เดียวกันแทนที่จะเป็นบล็อกอินไลน์หรือไฟล์ภายนอก

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF

"เคล็ดลับคือการ endlocal ในบรรทัดเดียวกัน" - ขอบคุณสำหรับการที่!
dforce

3

หากวิธีนี้ใช้งานไม่ได้แสดงว่าคุณมีความล่าช้าในการขยายตัวแปรสภาพแวดล้อม คุณสามารถปิดcmd /V:OFFหรือใช้เครื่องหมายอัศเจรีย์ภายในหาก:

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

3
การเปิดใช้งานการขยายที่ล่าช้าเพียงแค่เปลี่ยนความหมายของเครื่องหมายอัศเจรีย์ ไม่มีผลใด ๆ กับการขยายตัวของตัวแปรปกติ นอกจากนี้การเปิดใช้งานโดยไม่ตั้งใจนั้นเป็นไปได้ยากมาก ผู้ที่เปิดใช้งานมักจะทำตามวัตถุประสงค์
Joey

1

สิ่งนี้เกิดขึ้นเนื่องจากบรรทัดที่มีFORคำสั่งประเมินเพียงครั้งเดียว คุณต้องการวิธีการประเมินใหม่ คุณสามารถจำลองการขยายเวลาล่าช้าด้วยCALLคำสั่ง:

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

การขยายตัวที่ล่าช้าไม่ทำงาน แต่สิ่งนี้ / โทรออก / ทำงานบน win7 สำหรับสคริปต์ cmd 3 บรรทัดของฉันเพื่อค้นหา hardlink จริง: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') ทำการตั้งค่าการโทร CWD_REAL = %% a echo cd / d% CWD_REAL% cd / d% CWD_REAL%
mosh

0

มันน่าสังเกตว่ามันใช้งานได้ถ้ามันเป็นคำสั่งเดียวในบรรทัดเดียวกัน

เช่น

   if not defined BAR set FOO=1

จะถูกตั้งค่าFOOเป็น 1 จากนั้นคุณสามารถทำการตรวจสอบอื่นเช่น:

   if not defined BAR echo FOO: %FOO%

เฮ้ฉันไม่เคยพูดว่ามันสวย แต่ถ้าคุณไม่ต้องการยุ่งกับ setlocal หรือธุรกิจการขยายตัวล่าช้ามันเป็นวิธีแก้ปัญหาอย่างรวดเร็ว


0

บางครั้งอย่างที่ฉันมีในกรณีของฉันเมื่อคุณจำเป็นต้องเข้ากันได้กับระบบดั้งเดิมเช่นเซิร์ฟเวอร์ NT4 เก่าที่การขยายตัวล่าช้าไม่ทำงานหรือด้วยเหตุผลบางอย่างไม่ทำงานคุณอาจต้องใช้โซลูชันอื่น

ในกรณีที่คุณจะใช้ Delayd Expansion ขอแนะนำให้รวมอย่างน้อยการเช็คอินแบทช์:

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

ในกรณีที่คุณต้องการวิธีการที่อ่านง่ายและง่ายขึ้นนี่คือวิธีแก้ไขปัญหาอื่น:

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

ซึ่งใช้งานได้เช่นนี้:

>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

และอีกหนึ่งโซลูชั่น cmd ที่บริสุทธิ์ยิ่งขึ้น:

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

มันทำงานได้เช่นนี้:

>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

และสิ่งนี้จะเต็มถ้าแก้ปัญหาไวยากรณ์แล้ว 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%

มันทำงานได้เช่นนี้:

>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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.