Các biến trong tệp bó không được đặt khi bên trong IF?


69

Tôi có hai ví dụ về các tệp bó rất đơn giản:

Gán một giá trị cho một biến:

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

Mà, như mong đợi, kết quả trong:

FOO: 1 
Press any key to continue . . .

Tuy nhiên, nếu tôi đặt hai dòng giống nhau bên trong một khối NẾU KHÔNG ĐƯỢC ĐỊNH NGH: A:

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

Điều này bất ngờ dẫn đến:

FOO: 
Press any key to continue . . .

Điều này không liên quan gì đến IF, rõ ràng khối đang được thực thi. Nếu tôi xác định BAR ở trên if, chỉ văn bản từ lệnh PAUSE được hiển thị, như mong đợi.

Đưa cái gì?


Câu hỏi tiếp theo: Có cách nào để kích hoạt mở rộng bị trì hoãn mà không cần setlocal không?

Nếu tôi gọi tệp bó ví dụ đơn giản này từ bên trong tệp khác, ví dụ sẽ đặt FOO, nhưng chỉ LỘC.

Ví dụ:

testcaller.bat

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

kiểm tra

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

Điều này hiển thị:

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

Trong trường hợp này, có vẻ như tôi phải kích hoạt mở rộng bị trì hoãn trong CALLER, điều này có thể gây rắc rối.

Câu trả lời:


84

Biến môi trường trong tệp bó được mở rộng khi một dòng được phân tích cú pháp. Trong trường hợp các khối được phân cách bằng dấu ngoặc đơn (như của bạn if defined), toàn bộ khối được tính là một "dòng" hoặc lệnh.

Điều này có nghĩa là tất cả các lần xuất hiện của% FOO% được thay thế bằng các giá trị của chúng trước khi khối được chạy. Trong trường hợp của bạn không có gì, vì biến chưa có giá trị.

Để giải quyết điều này, bạn có thể kích hoạt mở rộng bị trì hoãn :

setlocal enabledelayedexpansion

Mở rộng bị trì hoãn khiến các biến được phân tách bằng dấu chấm than ( !) được đánh giá khi thực hiện thay vì phân tích cú pháp sẽ đảm bảo hành vi chính xác trong trường hợp của bạn:

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

help set chi tiết này quá:

Cuối cùng, hỗ trợ cho việc mở rộng biến môi trường bị trì hoãn đã được thêm vào. Hỗ trợ này luôn bị tắt theo mặc định, nhưng có thể được bật / tắt thông qua /Vchuyển đổi dòng lệnh sang CMD.EXE. XemCMD /?

Mở rộng biến môi trường bị trì hoãn là hữu ích để khắc phục những hạn chế của việc mở rộng hiện tại xảy ra khi một dòng văn bản được đọc, không phải khi nó được thực thi. Ví dụ sau đây cho thấy vấn đề với việc mở rộng biến ngay lập tức:

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

sẽ không bao giờ hiển thị thông báo, vì %VAR%trong cả hai IF câu lệnh được thay thế khi câu lệnh IF đầu tiên được đọc, vì nó bao gồm phần thân của IFcâu lệnh là câu lệnh ghép. Vì vậy, IF bên trong câu lệnh ghép thực sự so sánh "trước" với "sau" sẽ không bao giờ bằng nhau. Tương tự, ví dụ sau sẽ không hoạt động như mong đợi:

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

trong đó nó sẽ không xây dựng một danh sách các tệp trong thư mục hiện tại, mà thay vào đó sẽ chỉ đặt LIST biến thành tệp cuối cùng được tìm thấy. Một lần nữa, điều này là do %LIST%được mở rộng chỉ một lần khi FOR câu lệnh được đọc và tại thời điểm đó LISTbiến là trống. Vì vậy, vòng lặp FOR thực tế mà chúng ta đang thực hiện là:

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

mà chỉ giữ cài đặt LISTđến tập tin cuối cùng được tìm thấy.

Mở rộng biến môi trường bị trì hoãn cho phép bạn sử dụng một ký tự khác (dấu chấm than) để mở rộng các biến môi trường tại thời điểm thực hiện. Nếu mở rộng biến bị trì hoãn được bật, các ví dụ trên có thể được viết như sau để hoạt động như dự định:

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
Và nếu bạn cần lặp lại một !, sử dụng ^^^!(thoát nó hai lần). Nếu không, tính năng "mở rộng bị trì hoãn" sẽ ăn nó.
grawity

4

Hành vi tương tự cũng xảy ra khi các lệnh nằm trên một dòng đơn ( &là dấu phân cách lệnh):

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

Lời giải thích của Joey là yêu thích của tôi. Tuy nhiên, xin lưu ý rằng enabledelayedexpansionnó không hoạt động trên Windows NT 4.0 (và tôi không chắc về Windows 2000).

Về câu hỏi tiếp theo của bạn, không, không thể EnableDelayedExpansionkhông có setlocal. Tuy nhiên, hành vi ban đầu chống lại bạn có thể được sử dụng để khắc phục vấn đề thứ hai: mẹo là endlocaltrên cùng một dòng nơi bạn đặt lại các giá trị của các biến bạn cần.

Đây là test.batsửa đổi của bạn :

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

Nhưng đây là một cách giải quyết khác cho vấn đề đó: sử dụng một quy trình trong cùng một tệp thay vì một khối nội tuyến hoặc một tệp bên ngoài.

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

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

"Bí quyết là để endlocal trên cùng một dòng" - CẢM ƠN điều đó!
bắt buộc

3

Nếu nó không hoạt động theo cách đó, bạn có thể đã trì hoãn việc mở rộng biến môi trường. Bạn có thể tắt nó đi cmd /V:OFFhoặc sử dụng dấu chấm than bên trong nếu:

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

3
Cho phép mở rộng bị trì hoãn chỉ làm thay đổi ngữ nghĩa của dấu chấm than. Nó không có bất kỳ ảnh hưởng nào đến việc mở rộng biến thông thường. Bên cạnh đó, vô tình cho phép nó là rất khó xảy ra; những người đã kích hoạt nó thường làm như vậy trên mục đích.
Joey

1

Điều này xảy ra bởi vì dòng của bạn với FORlệnh chỉ đánh giá một lần. Bạn cần một số cách để đánh giá lại nó. Bạn có thể mô phỏng việc mở rộng bị trì hoãn bằng CALLlệnh:

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

Việc mở rộng bị trì hoãn không hoạt động, nhưng điều này / thực hiện cuộc gọi / đã hoạt động trên win7, đối với tập lệnh cmd 3 dòng của tôi để tìm liên kết cứng thực sự: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') thực hiện cài đặt cuộc gọi CWD_REAL = %% một tiếng vang cd / d% CWD_REAL% cd / d% CWD_REAL%
mosh

0

Điều đáng chú ý là nó KHÔNG hoạt động nếu đó là một lệnh trên cùng một dòng.

ví dụ

   if not defined BAR set FOO=1

thực tế sẽ được đặt FOOthành 1. Sau đó, bạn có thể thực hiện một kiểm tra khác như:

   if not defined BAR echo FOO: %FOO%

Này, tôi chưa bao giờ nói nó đẹp. Nhưng nếu bạn không muốn gây rối với setlocal hoặc doanh nghiệp mở rộng bị trì hoãn, thì đó là cách giải quyết nhanh chóng.


0

Đôi khi, như trong trường hợp của tôi, khi bạn cần tương thích với các hệ thống cũ như máy chủ NT4 cũ, nơi mở rộng bị trì hoãn không hoạt động hoặc vì một số lý do không hoạt động, bạn có thể cần giải pháp thay thế.

Trong trường hợp bạn sẽ sử dụng Delayd Expansion, cũng nên đưa vào ít nhất là kiểm tra theo đợt:

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

Trong trường hợp bạn chỉ muốn tiếp cận dễ đọc và đơn giản hơn, đây là các giải pháp thay thế:

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

hoạt động như thế này:

>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

và một giải pháp cmd tinh khiết hơn:

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

nó hoạt động như thế này:

>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

và đây là giải pháp cú pháp NẾU ELSE đầy đủ:

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%

nó hoạt động như thế này:

>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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.