4.1 環(huán)境變量延遲擴(kuò)展(上)

2022-04-13 15:07 更新

《批處理入門手冊(cè)》
四. 環(huán)境變量延遲擴(kuò)展(上)
-----------------------------------------------------------------------------------------------------
4.1 學(xué)習(xí)環(huán)境變量延遲擴(kuò)展

學(xué)習(xí)要點(diǎn):
一、什么是延遲環(huán)境變量擴(kuò)展?
二、批處理變量延遲詳解

-----------------------------------------------------------------------------------------------------


一,什么是延遲環(huán)境變量擴(kuò)展?

原文地址:

http://bbs.bathome.cn/thread-3083-1-1.html

延遲變量全稱"延遲環(huán)境變量擴(kuò)展",要理解這個(gè)東西,我們還得先理解一下什么叫變量擴(kuò)展! CMD在解釋我們的命令的時(shí)候,首先會(huì)讀取一條完整的命令,然后對(duì)其進(jìn)行一些命令格式的匹配操作,看你所輸入的命令格式是不是符合它的要求.如果我們要在命令中引用一些變量,那么我們?nèi)绾巫孋MD在解釋我們的命令時(shí),能識(shí)別出這個(gè)變量呢?這時(shí)我們就可以在變量名字兩邊加一個(gè)%號(hào),如%name%.當(dāng)CMD在讀取我們的整條命令進(jìn)行格式匹配的時(shí)候,就會(huì)發(fā)現(xiàn)name這個(gè)字符兩邊加了%號(hào),就不會(huì)把它當(dāng)作普通字符處理,而是會(huì)把它當(dāng)作一個(gè)變量處理,變量名叫name然后CMD就會(huì)找到變量名對(duì)應(yīng)的值,用該值替換掉這個(gè)變量名字(name),(如果變量名不存在值,就返回空值).再將這個(gè)替換好并且匹配的命令執(zhí)行!這個(gè)替換值的過程,就叫做變量擴(kuò)展,說白了就是把變量的名字,用它的值給替換掉后執(zhí)行!也就是批處理如何識(shí)別一個(gè)變量的過程.(注意:這里只是變量的擴(kuò)展的意思,不是延遲環(huán)境變量擴(kuò)展,要理解延遲環(huán)境變量擴(kuò)展,必須先理解什么是變量的擴(kuò)展) 也就是批處理如何識(shí)別一個(gè)變量的過程.


例1, 

@echo off
set var=test
echo %var%
pause

CMD在讀取到echo %var%這句命令后,就會(huì)進(jìn)行匹配操作,它馬上就發(fā)現(xiàn)var字符兩邊有%號(hào),這時(shí)CMD就會(huì)把它當(dāng)作一 個(gè)變量處理,查看這個(gè)var變量名是不是有值,如果有就用該值把變量名var給替換掉,這里我們的var在上一條命令set var=test中,給var賦值為test,所以CMD會(huì)用test把%var%這個(gè)變量名替換掉,替換后的結(jié)果就為echo test了.這些步驟都是CMD進(jìn)行匹配操作的步驟,匹配完后,他再執(zhí)行echo test這條語句,這時(shí)我們的CMD中就會(huì)echo出一個(gè)test了.什么是環(huán)境變量擴(kuò)展知道了,那什么是延遲環(huán)境變量擴(kuò)展呢?在理解環(huán)境變量擴(kuò)展時(shí),我們知道CMD在解釋命令時(shí),首先會(huì)把一條完整的命令進(jìn)行讀取,然后進(jìn)行匹配操作,匹配時(shí)它會(huì)把命令里的變量用變量的值給替換掉,然后執(zhí)行這個(gè)替換好的命令.問題就出在"一條完整的命令",在BAT中,IF FOR這樣的命令都可以加括號(hào),將一些命令嵌套在里面執(zhí)行.這樣的話對(duì)于一條可以加擴(kuò)號(hào)嵌其他命令的命令,他的完整格式就是for %%i in (....)這樣一個(gè)整體.此時(shí),如果我們?nèi)绻诶ㄌ?hào)里面嵌入一些設(shè)置變量值的命令,就會(huì)出現(xiàn)問題了!


例2, 

@echo off
for /l %%i in (1,1,5) do (
    set var=%%i
    echo %var%
)
pause

執(zhí)行后會(huì)顯示5個(gè)空行的錯(cuò)誤提示!為什么?根據(jù)我們上面說的知識(shí)來理解。

通過這兩個(gè)例子,大家應(yīng)該已經(jīng)理解,如果只有環(huán)境變量擴(kuò)展這個(gè)過程的話,如果我們?cè)诳梢郧短酌畹拿钪袌?zhí)行 賦值操作時(shí),會(huì)讓我們的BAT出現(xiàn)給變量賦值的問題.那么這個(gè)時(shí)候"延遲環(huán)境變量擴(kuò)展",這個(gè)概念就被提出來了。在批處理中,我們可以用setloacl enabledelayedexpansion 這個(gè)命令來啟用"延遲環(huán)境變量擴(kuò)展" ,在我們啟用了"延遲環(huán)境變量擴(kuò)展"后,當(dāng)CMD在解釋含有嵌套格式的命令時(shí),他會(huì)把嵌套的命令一條一條的先執(zhí)行一次,然后再進(jìn)行匹配操作,這樣我們的賦值操作就會(huì)完成.并且在"延遲環(huán)境變量擴(kuò)展"啟用后,CMD會(huì)用!號(hào)來判斷這是不是一個(gè)變量 。如沒啟用前變量用%name%這樣的格式判斷,啟用后就用!name!這樣的格式判斷了,這個(gè)符號(hào)我們需要注意!


例3,


@echo off
setlocal enabledelayedexpansion
set var=1
for /l %%i in (1,1,5) do (
    set /a var =%%i 
    echo !var!
)
pause

這樣大家應(yīng)該明白什么是延遲環(huán)境變量擴(kuò)展了吧.再來一個(gè)例子


例4, 

@echo off
set var=test & echo %test%
pause


這條命令放在一行,表示他是一條完整的命令,不啟用"延遲環(huán)境變量擴(kuò)展",就會(huì)出現(xiàn)上面的賦值錯(cuò)誤!改成下這樣就 OK了:

@echo off
setlocal enabledelayedexpansion
set var=test & echo !var!
pause

-----------------------------------------------------------------------------------------------------

 


二,批處理變量延遲詳解

原文地址:

http://bbs.bathome.cn/viewthread.php?tid=2898

關(guān)于環(huán)境變量延遲擴(kuò)展,使用set /?可以查看到部分說明,不過考慮到其粗劣的翻譯水平,建議在查看之前,首先chcp 437切換為英文查看原英文說明。鑒于文中已說得十分詳盡,而且有數(shù)個(gè)代碼示例,應(yīng)該不難理解。在此僅略作一些補(bǔ)充。


在許多可見的官方文檔中,均將使用一對(duì)百分號(hào)閉合環(huán)境變量以完成對(duì)其值的替換行為稱之為“擴(kuò)展(expansion )”,這其實(shí)是一個(gè)第一方的概念,是從命令解釋器的角度進(jìn)行稱謂的,而從我們使用者的角度來看,則可以將它看作是引用(Reference)、調(diào)用(Call)或者獲?。℅et)。


而命令解釋器是擴(kuò)展環(huán)境變量的行為大致如下:首先讀取命令行的一條完整語句,在進(jìn)行一些先期的預(yù)處理之后,命令被解釋執(zhí)行之前,會(huì)對(duì)其中用百分號(hào)閉合的字符串進(jìn)行匹配,如果在環(huán)境空間中找到了與字符串相匹配的環(huán)境變量,則用其值替換掉原字符串及百分號(hào)本身,如果未得到匹配,則用一個(gè)空串替換,這個(gè)過程就是環(huán)境變量的“擴(kuò)展”,它仍然屬于命令行的預(yù)處理范疇。


而一條“完整的語句”,在NT的命令解釋器CMD中被解釋為“for if else”等含有語句塊的語句和用“& | && ||”等連接起來的復(fù)合語句。

因此,當(dāng)CMD讀取for語句時(shí),其后用一對(duì)圓擴(kuò)號(hào)閉合的所有語句將一同讀取,并完成必要的預(yù)處理工作,這其中就包括環(huán)境變量的擴(kuò)展,所以在for中的所有語句執(zhí)行之前,所有的環(huán)境變量都已經(jīng)被替換為for之前所設(shè)定的值,從而成為一個(gè)字符串常量,而不再是變量。無論在for中將那些環(huán)境變量如何修改,真正受到影響的只是環(huán)境變量空間,而非for語句內(nèi)部。


而為了能夠在for語句內(nèi)部感知環(huán)境變量的動(dòng)態(tài)變化,CMD設(shè)計(jì)了延遲的環(huán)境變量擴(kuò)展特性,也就是說,當(dāng)CMD讀取了一條完整的語句之后,它不會(huì)立即執(zhí)行變量的擴(kuò)展行為,而會(huì)在某個(gè)單條語句執(zhí)行之前再進(jìn)行擴(kuò)展,也就是說,這個(gè)擴(kuò)展行為被“延遲”了。


延遲環(huán)境變量擴(kuò)展特性在CMD中缺省是關(guān)閉的,開啟它的方法目前有兩個(gè):一是CMD /v:off(此處說法有誤,應(yīng)為 CMD /v:on——namejm 注),它會(huì)打開一個(gè)新的命令行外殼,在使用exit退出這個(gè)外殼之前,擴(kuò)展特性始終有效,常用于命令行環(huán)境中;二是setlocal EnableDelayedExpansion,它會(huì)使環(huán)境變量的修改限制到局部空間中,在endlocal之后,擴(kuò)展特性和之前對(duì)環(huán)境變量的修改將一同消失,常用于批處理語句中。(原貼詳見:http://www.cn-dos.net/forum/viewthread.php?tid=28273)


上面是willsort寫的帖子對(duì)于新手來說比較難理解。不過沒關(guān)系,我們先分析一個(gè)例子,同樣是引用willsort老大的。


本例啟用了變量延遲,是個(gè)正確的例子!


例1, 

@echo off & setlocal EnableDelayedExpansion
for /f  "tokens=* delims=" %%i in ("Hello world.") do (
    set n=%%i
    set n=!n:ld.=t!
    set n=!n:o w= S!
    set n=!n:He=Wi!
    echo !n!
)
Pause

將上面代碼保存為.bat雙擊執(zhí)行后會(huì)顯示“Will Sort”字符串,下面將講解每個(gè)語句的意思:

1.@echo off & setlocal EnableDelayedExpansion
關(guān)閉命令回顯,并啟用變量延遲

2.for /f  "tokens=* delims=" %%i in ("Hello world.") do (  )
for命令及其參數(shù)的使用,請(qǐng)大家在論壇里搜索相關(guān)字眼。限于篇幅問題,這里不作討論。如果此時(shí)你不明白它的意思,那么你就當(dāng)它的作用是把字符串“Hello world.”賦值給%%i好了,當(dāng)然這只是權(quán)宜之計(jì),以后一定要學(xué)習(xí)for的使用!

3.set n=%%i
把%%i的值(即Hello world.)賦予給變量n,這個(gè)大家都知道吧

4.set n=!n:ld.=t!
這里要講講set替換字符的功能了。這個(gè)語句的意思是,先獲取變量n的值(此時(shí)n的值是“Hello world.”),然后將字符“t”替換字符“l(fā)d.”,然后再將替換后的結(jié)果再次賦值給變量n(此時(shí)n的值變?yōu)椤癏ello wort”)。至于set替換字符的編寫格式,大家可以在CMD鍵入“set/?”找到“%PATH:str1=str2%”這段有說明

5.set n=!n:o w= S!
意思和上句一樣,只是替換和被替換的內(nèi)容不同。它是將“ S”替換“o w”(注意S前面和w前面都有個(gè)空格),其實(shí)willsort老大是想證明set替換字符是支持句點(diǎn)和空格的(第4句“l(fā)d”后面有個(gè).)。此時(shí)n的值為“Hell Sort”


6.set n=!n:He=Wi!
這句不用說了吧,執(zhí)行完這句后n的值為“Will Sort”


7.echo !n!
顯示變量n的值

需要注意的是,一旦啟用了變量延遲,就要用!號(hào)把變量括起來,而不能用%號(hào)。

  好了,每句的意思已經(jīng)說完了,下面要講本帖真正要討論的變量延遲的問題。
這里又要引用Will Sort老大的說明:當(dāng)CMD讀取for語句時(shí),其后用一對(duì)圓括號(hào)閉合的所有語句將一同讀取,并完成
必要的預(yù)處理工作,這其中就包括環(huán)境變量的擴(kuò)展,所以在for中的所有語句執(zhí)行之前,所有的環(huán)境變量都已經(jīng)被替
換為for之前所設(shè)定的值,從而成為一個(gè)字符串常量,而不再是變量。

  而為了能夠在for語句內(nèi)部感知環(huán)境變量的動(dòng)態(tài)變化,CMD設(shè)計(jì)了延遲的環(huán)境變量擴(kuò)展特性,也就是說,當(dāng)CMD讀取
了一條完整的語句之后,它不會(huì)立即執(zhí)行變量的擴(kuò)展行為,而會(huì)在某個(gè)單條語句執(zhí)行之前再進(jìn)行擴(kuò)展,也就是說,這
個(gè)擴(kuò)展行為被“延遲”了。

  總的來說是,在沒有啟用變量延遲的情況下,凡是在括號(hào)內(nèi)(即do里面)的變量,在執(zhí)行for語句之前,就已經(jīng)被
替換成for語句之前其它命令對(duì)該變量所賦予的值。這句話不懂沒關(guān)系,下面再看一個(gè)例子,看完你就會(huì)明白。


例2, 

@echo off
for /f  "tokens=* delims=" %%i in ("Hello world.") do (
set n=%%i
set n=%n:ld.=t%
set n=%n:o w= S%
    set n=%n:He=Wi%
    echo %n% 
)
Pause

  這和前面的例子差不多,只是所有!號(hào)都換成%號(hào),這是個(gè)錯(cuò)誤的例子。因?yàn)樗鼪]有啟用變量延遲,也沒有使用!
號(hào)把變量括起來。我們看到它的執(zhí)行結(jié)果是顯示“ECHO 處于關(guān)閉狀態(tài)”。
為什么會(huì)這樣呢?原因是,在沒有啟用變量延遲的情況下,凡是在括號(hào)內(nèi)(即do里面)的變量,在執(zhí)行for語句之前
,就已經(jīng)被替換成for語句之前其它命令對(duì)該變量所賦予的值。

則是說在本例中的以下幾句


set n=%%i
set n=%n:ld.=t%
set n=%n:o w= S%
set n=%n:He=Wi%
echo %n%

  第一句能正常執(zhí)行并達(dá)到它的目的,因?yàn)樗皇菃渭兊貙?%i的值賦予給變量n,所以沒有任何問題。其它幾句屬
這樣情況:早在for語句執(zhí)行前,CMD就急不切待地將這幾句里面的所有變量n一同執(zhí)行替換行為,替換為for之前,
其它命令對(duì)n所設(shè)置的值,從而使n變成一個(gè)常量。但在本例中,for語句之前只有@echo off這句,并沒有其它命令
對(duì)n作過任何賦值行為,所以在for之前,變量n的值為空值。
  即是說,set n=%n:ld.=t% 這句里面的變量n,在CMD讀?。ㄗ⒁馐亲x取不是執(zhí)行)完整個(gè)for語句后(這時(shí)還未輪
到set執(zhí)行自己的任務(wù)),就立刻被替換為一個(gè)空值,一個(gè)空值里面沒有任何東西,所以就不存在一字符替換另一字
符這種說法(沒有東西怎么替換?)。最終到執(zhí)行set n=%n:ld.=t%語句時(shí),它只是獲取一個(gè)空值,再給變量n賦予
空值而已。其它幾句也是一樣原理。

  所以,最后echo %n%的時(shí)候變量n還是個(gè)空值,而echo命令沒有東西可以顯示,就只有顯示“ECHO 處于關(guān)閉狀態(tài)
”這句來說明自己的狀態(tài)

通過這個(gè)例子的說明,相信大家已經(jīng)知道變量延遲的作用吧!我們?cè)倩仡^來看看例1。

啟用變量延遲后,在執(zhí)行


set n=!n:ld.=t!
set n=!n:o w= S!
set n=!n:He=Wi!
echo !n!

這些語句前,它們里面的變量n不會(huì)馬上被CMD替換(啟用延遲后,CMD變得有耐性啦^_^),而未被替換的話,那么n
就還是變量,而不是常量。等到執(zhí)行set n=!n:ld.=t!等這幾句時(shí),變量n才被替換。這樣每個(gè)set命令都能感知變量
n的任何變化,從而作出正確的替換行為。這就是變量延遲啦!

不要以為只有for才要用變量延遲,下面這個(gè)例子同樣需要

例3,這是個(gè)錯(cuò)誤的例子


@echo off
set mm=girl&echo %mm%
pause

執(zhí)行后依然顯示“ECHO 處于關(guān)閉狀態(tài)”。

  原因是沒有啟用延遲,而且在set mm=girl&echo %mm%語句前沒有其它命令對(duì)mm進(jìn)行賦值。這時(shí)當(dāng)CMD執(zhí)行set
mm=girl&echo %mm%語句前,就已經(jīng)急不切待地把變量mm的值替換了,而又因?yàn)榍懊鏇]給mm賦值,所以mm被替換為空
值,變成常量。等到echo命令執(zhí)行時(shí),它其實(shí)是echo一個(gè)不會(huì)變化的常量,本例中即是空值。

有人會(huì)問,echo前面不是給mm賦值了嗎?
這個(gè)就要關(guān)系到CMD解釋命令的步驟,大家可以參詳本帖開頭willsort的帖子。

  總的來說是,如果不啟用變量延遲,在本例中,echo是不會(huì)理會(huì)也不會(huì)知道,它前面(指同一行語句)是否有其
它命令給mm賦值。它只會(huì)從set mm=girl&echo %mm%這句以上的語句中獲取它所要顯示的變量的內(nèi)容,也就是說,上
一行或上幾行的命令將mm設(shè)置成什么值,echo命令就顯示什么值。

大家這樣做就明白了:


@echo off
set mm=boy
set mm=girl&echo %mm%
pause

看看顯示什么結(jié)果就知道了!

這樣編寫例3才正確:


@echo off&setlocal EnableDelayedExpansion
set mm=girl&echo !mm!
pause

開啟了變量延遲,變量擴(kuò)展(替換)的行為就推遲到echo命令執(zhí)行時(shí),這時(shí)echo能感知它前面的命令(本例的set)
對(duì)變量mm做了什么“壞事”,從而作出正確的判斷并執(zhí)行
-----------------------------------------------------------------------------------------------------

 


注:以上文章在編輯整理時(shí)稍有修改!
Januapr編輯于2009-09-15 星期二14:32:57.46


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)