大綱
一 前言二 for語句的基本用法三 for /f (delims、tokens、skip、eol、userbackq、變量延遲)四 for /r (遞歸遍歷)五 for /d (遍歷目錄)六 for /l (計(jì)數(shù)循環(huán))
一、前言
在批處理中,for是最為強(qiáng)大的命令語句,它的出現(xiàn),使得解析文本內(nèi)容、遍歷文件路徑、數(shù)值遞增/遞減等操作成為可能;配合if、call、goto等流程控制語句,更是可以實(shí)現(xiàn)腳本復(fù)雜的自動(dòng)化、智能化操作;合理使用for語句,還能使代碼大為簡化,免除各位編寫大量重復(fù)語句之苦。而能否熟練使用for語句,已經(jīng)成為衡量一個(gè)人批處理水平高低最主要的標(biāo)準(zhǔn)。
在這個(gè)系列教程中,我將通過實(shí)際應(yīng)用中頻繁出現(xiàn)的例子,帶領(lǐng)大家步入for語句的神奇之門,一步步邁向for語句的魔幻殿堂,使得大家在實(shí)際的應(yīng)用中,能獨(dú)立寫出簡潔高效的代碼,在批處理的世界里自由馳騁。
注意:以下的講解,都是基于簡體中文版Windows XP Pro SP3的操作系統(tǒng)環(huán)境。
二、for語句的基本用法
正如色彩繽紛的七彩光芒是由紅綠藍(lán)三原色構(gòu)成的一樣,最復(fù)雜的for語句,也有其基本形態(tài),它的模樣是這樣的:
在cmd窗口中:
FOR %variable IN (set) DO command [command-parameters]
在批處理文件中:
FOR %%variable IN (set) DO command [command-parameters]
具體例子:
For %i in (1 2 3) do @echo %i
之所以要區(qū)分cmd窗口和批處理文件兩種環(huán)境,是因?yàn)樵谶@兩種環(huán)境下,命令語句表現(xiàn)出來的行為雖然基本一樣,但是在細(xì)節(jié)上還是稍有不同。
最明顯的一個(gè)差異就是:在cmd窗口中,for之后的形式變量I必須使用單百分號(hào)引用,即%i;而在批處理文件中,引用形式變量i必須使用雙百分號(hào),即%%i。
為了方便起見,若不是特別強(qiáng)調(diào),以下的講解都以批處理文件環(huán)境為例。
我們先來看一下for語句的基本要素都有些什么:
1、for、in和do是for語句的關(guān)鍵字,它們?nèi)齻€(gè)缺一不可;
2、%%I是for語句中對形式變量的引用,就算它在do后的語句中沒有參與語句的執(zhí)行,也是必須出現(xiàn)的;
3、in之后,do之前的括號(hào)不能省略;
4、command1表示字符串或變量,command2表示字符串、變量或命令語句;
現(xiàn)在,你可能已經(jīng)會(huì)寫一個(gè)簡單的for語句了,比如:
[code1]
@echo offfor %%I in (bbs.bathome.net) do echo %%Ipause
保存為批處理文件并執(zhí)行,將會(huì)在彈出的批處理窗口中看到這樣的信息:
bbs.bathome.net請按任意鍵繼續(xù)...
很快地,你會(huì)覺得這個(gè)for語句是如此的簡單,簡單到你絲毫感受不出它的強(qiáng)大:這個(gè)for語句,和我直接用echo語句沒什么兩樣?。?br>
是的,演示代碼永遠(yuǎn)都只是演示而已,就像大多數(shù)高級語言的教科書一樣,在引導(dǎo)新手學(xué)習(xí)的時(shí)候,基本上都是千篇一律地告訴大家如何編寫一個(gè)能顯示 hello world! 的窗口,從這些演示代碼中,你看不到它們具有多少實(shí)用性,你只是感到有點(diǎn)好奇:咦,居然彈出了一個(gè)窗口?片刻之后,你就會(huì)覺得索然無味。
那好吧,為了讓大家對for更加感興趣,我們先來分析一下for語句的一些注意事項(xiàng),之后,再讓大家看看更為強(qiáng)大的for語句實(shí)例。
1、for語句的形式變量I,可以換成26個(gè)字母中的任意一個(gè),這些字母會(huì)區(qū)分大小寫,也就是說,%%I和%%i會(huì)被認(rèn)為不是同一個(gè)變量;形式變量I還可以換成其他的字符,但是,為了不與批處理中的%0~%9這10個(gè)形式變量發(fā)生沖突,請不要隨意把%%I替換為%%0 ~%%9中的任意一個(gè);
2、in和do之間的command1表示的字符串或變量可以是一個(gè),也可以是多個(gè),每一個(gè)字符串或變量,我們稱之為一個(gè)元素,每個(gè)元素之間,用空格鍵、跳格鍵、逗號(hào)、分號(hào)或等號(hào)分隔;
3、for語句依次提取command1中的每一個(gè)元素,把它的值賦予形式變量I,帶到do后的command2中參與命令的執(zhí)行;并且每次只提取一個(gè)元素,然后執(zhí)行一次do后的命令語句,而無論這個(gè)元素是否被帶到command2中參與了command2的運(yùn)行;當(dāng)執(zhí)行完一次do后的語句之后,再提取command1中的下一個(gè)元素,再執(zhí)行一次command2,如此循環(huán),直到command1中的所有元素都已經(jīng)被提取完畢,該for語句才宣告執(zhí)行結(jié)束;
其中,第3點(diǎn)是最為關(guān)鍵的,它描述了for語句的執(zhí)行過程,是for語句的精髓所在,大家一定要牢記這一條,才能深刻理解更為復(fù)雜的for流程。
有了以上的基礎(chǔ),我們再來看一個(gè)例子,這個(gè)例子修改了[code1]的部分內(nèi)容,結(jié)果將大不一樣:
[code2]
@echo offfor %%I in (bbs,bathome,net) do echo %%Ipause
和[code1]的執(zhí)行結(jié)果[result1]相比,[result2]發(fā)生了如下變化:
1、顯示結(jié)果分成了3行(不算最后一行中文提示);
2、每一行都從逗號(hào)處被切分;
如果把 bbs.bathome.net 這個(gè)字符串中的點(diǎn)號(hào)換為空格、跳格或等號(hào),執(zhí)行結(jié)果將和example2的執(zhí)行結(jié)果別無二致。
現(xiàn)在,我們來分析一下[code2]代碼中for語句的執(zhí)行過程:
首先,for語句以逗號(hào)為分隔符,把 bbs,bathome.net 這個(gè)字符串切分成三個(gè)元素:bbs、bathome和cn,由此決定了do后的語句將會(huì)被執(zhí)行3次;
然后,第一次執(zhí)行過程是這樣的:先把 bbs 這個(gè)字符串作為形式變量I的值,帶入do后的語句中加以執(zhí)行,也就是執(zhí)行 echo %%I 語句,此時(shí)的I值為bbs,因此,第一次執(zhí)行的結(jié)果,將會(huì)在屏幕上顯示bbs這個(gè)字符串;第二次執(zhí)行和第一次執(zhí)行的過程是一樣的,只不過此時(shí)I的值已經(jīng)被替換為command1中的第二個(gè)元素了,也就是 bathome 這個(gè)字符串;如此循環(huán),當(dāng)?shù)谌蝒cho執(zhí)行完畢之后,整條for語句才算執(zhí)行完畢,此時(shí),將執(zhí)行下一條語句,也就是pause命令。
其實(shí),這個(gè)例子只比上一個(gè)例子多了一點(diǎn)花樣,有趣了那么一點(diǎn)點(diǎn):一條for語句的執(zhí)行結(jié)果居然被分成了3行!
為了讓大家見識(shí)一下for的真正威力,本人絞盡腦汁,翻帖無數(shù),不得要領(lǐng),萬般無奈之下,只好亮出了塵封在箱底多年的一段代碼:檢測當(dāng)前硬盤都有哪些分區(qū)。
[code3]
@echo offset str=c d e f g h i j k l m n o p q r s t u v w x y zecho 當(dāng)前硬盤的分區(qū)有:for %%i in (%str%) do if exist %%i: echo %%i:pause
這段代碼能檢測硬盤都有哪些分區(qū),包括U盤和移動(dòng)硬盤的分區(qū),但是,當(dāng)光驅(qū)中有盤的時(shí)候,也會(huì)被列出來,這是本代碼的一個(gè)缺憾,在以后的講解中,我將向大家講述如何消除這個(gè)瑕疵,敬請關(guān)注本系列的后續(xù)章節(jié)。
高級應(yīng)用:
想知道當(dāng)前目錄下都有哪些文件嗎?請用下面的代碼:
@echo offfor %%i in (*.*) do echo '%%i'pause
想列出當(dāng)前目錄下所有的文本文件嗎?請用下面的代碼
@echo offfor %%i in (*.txt) do echo '%%i'pause
想列出只用兩個(gè)字符作為文件名的文本文件嗎?(注:實(shí)際上這個(gè)代碼是輸出少于或等于兩個(gè)字符作為文件名的文本文件)請用下面的代碼:
@echo offfor %%i in (??.txt) do echo '%%i'pause
題外話:
1、列出當(dāng)前目錄下各種文件的方法,最簡單的還是用dir命令,但是,從以上代碼中,各位可以加深對for語句執(zhí)行流程的理解(用到了通配符*和?);
2、注意:以上代碼不能列出含有隱藏或系統(tǒng)屬性的文件;(注:這里其實(shí)有一個(gè)很有趣的現(xiàn)象,windows中的系統(tǒng)文件一般具備兩種屬性——隱藏和系統(tǒng);但是你如果測試的話就會(huì)發(fā)現(xiàn),加上+s屬性,但是不加+h的文件是可以被簡單的for顯示出來的。
例如:
@echo offattrib +s 1.txtFor %%i in (*.txt) do Echo %%ipause
這里的1.txt在結(jié)果中顯示出來了。所以“以上代碼不能列出含有隱藏或系統(tǒng)屬性的文件”是不準(zhǔn)確的,而因該說成“以上代碼不能列出含有隱藏屬性的文件”)
三、文本解析顯神威:for /f 用法詳解
前言
for /f 是個(gè)十分強(qiáng)大的家伙。
如果說,for語句是批處理中最強(qiáng)大的語句的話,那么,for /f 就是精華中的精華。
for /f 的強(qiáng)大,和它擁有眾多的開關(guān)密切相關(guān)。因?yàn)殚_關(guān)眾多,所以用法復(fù)雜,本章將分成若干小節(jié),為大家逐一介紹強(qiáng)大的 for /f 語句。
(一)為解析文本而生:for /f 的基本用法
所有的對象,無論是文件、窗體、還是控件,在所有的非機(jī)器語言看來,無外乎都是形如'c:\test.txt'、'CWnd'之類的文本信息;而所有的對象,具體的如ini文件中的某條配置信息、注冊表中的某個(gè)鍵值、數(shù)據(jù)庫中的某條記錄……都只有轉(zhuǎn)化為具有一定格式的文本信息,方可被代碼識(shí)別、操控??梢哉f,編程的很大一部分工作,都是在想方設(shè)法絞盡腦汁如何提取這些文本信息。
而提取文本信息,則是for /f的拿手好戲:讀取文件內(nèi)容;提取某幾行字符;截取某個(gè)字符片段;對提取到的內(nèi)容再切分、打亂、雜糅……只要你所能想到的花樣,for /f 都會(huì)想方設(shè)法幫你辦到,因?yàn)椋?span>for /f 就是被設(shè)計(jì)成專門用于解析文本的。
先來看個(gè)例子。
假如有個(gè)文本文件test.txt,內(nèi)容如下:
[txt1]
論壇的目標(biāo)是:不求最大,但求最好,做最實(shí)用的批處理論壇。論壇地址:bbs.bathome.net。這里是:新手晉級的福地,高手論劍的天堂。
那么,將如下代碼保存為test.cmd,并放在test.txt同一目錄下運(yùn)行,將會(huì)在屏幕上原樣顯示test.txt的內(nèi)容:
[code4]
@echo offfor /f %%i in (test.txt) do echo %%ipause
這段代碼,主要是讓你樹立這樣一種觀念:讀取文本文件的內(nèi)容(注:改為“逐行分析文本文件的內(nèi)容”,因?yàn)樽x取文本文件內(nèi)容的方法命令有很多,比如重定向輸入,又比如type/more/find/sort等命令),請使用 for /f 語句!
進(jìn)階話題:for /f 語句是把整個(gè)test.txt一次性顯示出來的?
在這段代碼中,雖然執(zhí)行結(jié)果是把test.txt中的所有內(nèi)容都顯示出來了,貌似 for /f 語句是把整個(gè)test.txt一次性顯示到屏幕上,實(shí)際上并非如此。
無論for語句做何種變化,它的執(zhí)行過程仍然遵循基本的for流程:依次處理每個(gè)元素,直到所有的元素都被處理為止。只不過在for /f語句中,這里的元素是指文件中的每一行,也就是說,for /f 語句是以行為單位處理文本文件的。這是一條極為重要的規(guī)則,在上一章中也強(qiáng)調(diào)過它的重要性,希望在接下來的學(xué)習(xí)過程中,你能時(shí)刻牢記這一原則,那么,很多問題將會(huì)迎刃而解。以下是驗(yàn)證這一說法的演示代碼(在[code4]的基礎(chǔ)上添加了&pause語句):
[code5]
@echo offfor /f %%i in (test.txt) do echo %%i&pausepause
(二) 切分字符串的利器:delims=
也許你對[code4]這段代碼不屑一顧:不就是把test.txt的內(nèi)容顯示出來了么?好像用處不大啊。
好吧,我們來玩?zhèn)€魔術(shù)。
還是[txt1]這段文本,把[code4]改造一下:
[code6]
@echo offfor /f 'delims=,' %%i in (test.txt) do echo %%ipause
再次運(yùn)行test.cmd,看到什么變化了嗎?
[result2]
論壇的目標(biāo)是:不求最大論壇地址:bbs.bathome.net。這里是:新手晉級的福地請按任意鍵繼續(xù)...
結(jié)果,你驚奇地發(fā)現(xiàn),每行第一個(gè)逗號(hào)之后的所有內(nèi)容都不見了(如果有不存在逗號(hào)的行,則保留原樣),也就說,你成功地提取到了每行第一個(gè)逗號(hào)之前的所有內(nèi)容!
試想一下,這段代碼會(huì)有什么用呢?
如果別人給了你一個(gè)軟件清單,每行都是'英文軟件名(逗號(hào))中文軟件名'的格式,而你卻只想保留英文名的時(shí)候,這段代碼將是多么有用??!再假設(shè),有這么一個(gè)IP文件,第一列是數(shù)字格式的IP地址,第二列是具體的空間地址,列與列之間用逗號(hào)分隔,而你想提取其中數(shù)字格式的IP,呵呵,我不說你也知道該怎么辦了吧?
要是文本內(nèi)容不是以逗號(hào)分隔,而是以其他符號(hào)分隔,那么,把'delims=,'的逗號(hào)換成相應(yīng)的符號(hào)就可以了。
在這里,我們引入了一個(gè)新的開關(guān):'delims=,',它的含義是:以逗號(hào)作為被處理的字符串的分隔符號(hào)。
在批處理中,指定分隔符號(hào)的方法是:添加一個(gè)形如 'delims=符號(hào)列表' 的開關(guān),這樣,被處理的每行字符串都會(huì)被符號(hào)列表中羅列出來的符號(hào)切分開來。
需要注意的是:如果沒有指定'delims=符號(hào)列表'這個(gè)開關(guān),那么,for /f 語句默認(rèn)以空格鍵或跳格鍵作為分隔符號(hào)。請把[txt1]中不同位置上的標(biāo)點(diǎn)符號(hào)改為空格或跳格,再運(yùn)行[code4]試試。
進(jìn)階話題:如果我要指定的符號(hào)不止一個(gè),該怎么辦?
在上面的講解中,我提到了指定分隔符號(hào)的方法:添加一個(gè)形如'delims=符號(hào)列表'的開關(guān)。不知道你注意到?jīng)]有,我的說法是'符號(hào)列表'而非'符號(hào)',這是大有講究的,因?yàn)?,你可以一次性指定多個(gè)分隔符號(hào)!
還是以[txt1]為例,把[code6]再改造一下
[code7]
@echo offfor /f 'delims=.,' %%i in (test.txt) do echo %%ipause
結(jié)果顯示:
[result3]
論壇的目標(biāo)是:不求最大論壇地址:bbs這里是:新手晉級的福地請按任意鍵繼續(xù)...
這樣,第一個(gè)點(diǎn)號(hào)或第一個(gè)逗號(hào)之前的內(nèi)容都被提取出來了。
[code7]的執(zhí)行過程是:逐行讀取test.txt中的內(nèi)容,以點(diǎn)號(hào)和逗號(hào)切分每一行的內(nèi)容(不存在點(diǎn)號(hào)和逗號(hào)的行,則不再切分,為了描述的方便,我們把被點(diǎn)號(hào)或逗號(hào)切分的一個(gè)一個(gè)的字符串片段,稱之為節(jié)),然后,for /f 會(huì)提取第一節(jié)的內(nèi)容作為最終結(jié)果,顯示在屏幕上。需要注意的是,在這里,所有行的字符串被切分成了兩個(gè)以上的節(jié),但是,[code7]的代碼只會(huì)提取第一節(jié)字符串的內(nèi)容,因?yàn)?for /f 語句默認(rèn)只提取第一節(jié)的符串。
(三) 定點(diǎn)提?。簍okens=
上一節(jié)在講解 delims= 的時(shí)候,我一再強(qiáng)調(diào) for /f 默認(rèn)只能提取到第一節(jié)的內(nèi)容,現(xiàn)在我們來思考一個(gè)問題:如果我要提取的內(nèi)容不在第一節(jié)上,那怎么辦?
這回,就該輪到 tokens= 出馬了。
tokens= 后面一般跟的是數(shù)字,如 tokens=2,也可以跟多個(gè),但是每個(gè)數(shù)字之間用逗號(hào)分隔,如 tokens=3,5,8,它們的含義分別是:提取第2節(jié)字符串、提取第3、第5和第8節(jié)字符串。注意,這里所說的“節(jié)”,是由 delims= 這一開關(guān)劃分的,它的內(nèi)容并不是一成不變的。
下面來看一個(gè)例子:
[txt2]
尺有所短,寸有所長,學(xué)好批處理沒商量,考慮問題復(fù)雜化,解決問題簡潔化。
對[txt2]這段文本,假設(shè)它們保存在文件test.txt中,如果我想提取“學(xué)好批處理沒商量”這句話,該如何寫代碼呢?
我們稍微觀察一下[txt2]就會(huì)發(fā)現(xiàn),如果以逗號(hào)作為切分符號(hào),就正好可以把“學(xué)好批處理沒商量”化為單獨(dú)的一“節(jié)”,結(jié)合上一節(jié)的講解,我們知道,'delims=,' 這個(gè)開關(guān)是不可缺少的,而要提取的內(nèi)容在以逗號(hào)切分的第3節(jié)上,那么,tokens= 后面的數(shù)字就應(yīng)該是3了,最終的代碼如下:
[code8]
@echo offfor /f 'delims=, tokens=3' %%i in (test.txt) do echo %%ipause
如果我們現(xiàn)在要提取的不只一個(gè)“節(jié)”,而是多個(gè),那又怎么辦呢?比如,要提取以逗號(hào)切分的第2節(jié)和第5節(jié)字符串,是寫成這樣嗎?
[code9]
@echo offfor /f 'delims=, tokens=2,5' %%i in (test.txt) do echo %%ipause
運(yùn)行批處理后發(fā)現(xiàn),執(zhí)行結(jié)果只顯示了第2節(jié)的內(nèi)容。
原來,echo 后面的 %%i 只接收到了 tokens=2,5 中第一個(gè)數(shù)值2所代表的那個(gè)字符串,而第二個(gè)數(shù)值5所代表的字符串因?yàn)闆]有變量來接收,所以就無法在執(zhí)行結(jié)果中顯示出來了。
那么,要如何接收 tokens= 后面多個(gè)數(shù)值所指代的內(nèi)容呢?
for /f 語句對這種情況做如下規(guī)定:
如果 tokens= 后面指定了多個(gè)數(shù)字,如果形式變量為%%i,那么,第一個(gè)數(shù)字指代的內(nèi)容用第一個(gè)形式變量%%i來接收,第二個(gè)數(shù)字指代的內(nèi)容用第二個(gè)形式變量%%j來接收,第三個(gè)數(shù)字指代的內(nèi)容用第三個(gè)形式變量%%k來接收……第N個(gè)數(shù)字指代的內(nèi)容用第N個(gè)形式變量來接收,其中,形式變量遵循字母的排序,第N個(gè)形式變量具體是什么符號(hào),由第一個(gè)形式變量來決定:如果第一個(gè)形式變量是%%i,那么,第二個(gè)形式變量就是%%j;如果第一個(gè)形式變量用的是%%x,那么,第二個(gè) 形式變量就是%%y。
現(xiàn)在回頭去看[code9],你應(yīng)該知道如何修改才能滿足題目的要求了吧?修改結(jié)果如下:
[code10]
@echo offfor /f 'delims=, tokens=2,5' %%i in (test.txt) do echo %%i %%jpause
如果有這樣一個(gè)要求:顯示[txt2]中的內(nèi)容,但是逗號(hào)要替換成空格,如何編寫代碼?
結(jié)合上面所學(xué)的內(nèi)容,稍加思索,你可能很快就得出了答案:
[code11]
@echo offfor /f 'delims=, tokens=1,2,3,4,5' %%i in (test.txt) do echo %%i %%j %%k %%l %%mpause
寫完之后,你可能意識(shí)到這樣一個(gè)問題:假如要提取的“節(jié)”數(shù)不是5,而是10,或者20,或者更多,難道我也得從1寫到10、20或者更多嗎?有沒有更簡潔的寫法呢?
答案是有的,那就是:如果要提取的內(nèi)容是連續(xù)的多“節(jié)”的話,那么,連續(xù)的數(shù)字可以只寫最小值和最大值,中間用短橫連接起來即可,比如 tokens=1,2,3,4,5 可以簡寫為 tokens=1-5 。
還可以把這個(gè)表達(dá)式寫得更復(fù)雜一點(diǎn):tokens=1,2-5,tokens=1-3,4,5,tokens=1-4,5……怎么方便就怎么寫吧。
大家可能還看到一種比較怪異的寫法:
[code12]
@echo offfor /f 'delims=, tokens=1,*' %%i in (test.txt) do echo %%i %%jpause
結(jié)果,第一個(gè)逗號(hào)不見了,取代它的是一個(gè)空格符號(hào),其余部分保持不變。
其中奧妙就在這個(gè)星號(hào)上面。
tokens=后面所接的星號(hào)具備這樣的功能:字符串從左往右被切分成緊跟在*之前的數(shù)值所表示的節(jié)數(shù)之后,字符串的其余部分保持不變,整體被*所表示的一個(gè)變量接收。
理論講解是比較枯燥的,特別是為了嚴(yán)密起見,還使用了很多限定性的修飾詞,導(dǎo)致句子很長,增加了理解的難度,我們還是結(jié)合[code12]來講解一下吧。
[txt2] 的內(nèi)容被切分,切分符號(hào)為逗號(hào),當(dāng)切分完第一節(jié)之后,切分動(dòng)作不再繼續(xù)下去,因?yàn)?tokens=1,* 中,星號(hào)前面緊跟的是數(shù)字1;第一節(jié)字符串被切分完之后,其余部分字符串不做任何切分,整體作為第二節(jié)字符串,這樣,[txt2]就被切分成了兩節(jié),分別 被變量%%i和變量%%j接收。
以上幾種切分方式可以結(jié)合在一起使用。不知道下面這段代碼的含義你是否看得懂,如果看不懂的話,那就運(yùn)行一下代碼,然后反復(fù)揣摩,你一定會(huì)更加深刻地理解本節(jié)所講解的內(nèi)容的:
[code13]
@echo offfor /f 'delims=, tokens=1,3-4,*' %%i in (test.txt) do echo %%i %%j %%k %%lpause
(四) 跳過無關(guān)內(nèi)容,直奔主題:skip=n
很多時(shí)候,有用的信息并不是貫穿文本內(nèi)容的始終,而是位于第N行之后的行內(nèi),為了提高文本處理的效率,或者不受多余信息的干擾,for /f 允許你跳過這些無用的行,直接從第N+1行開始處理,這個(gè)時(shí)候,就需要使用參數(shù) skip=n,其中,n是一個(gè)正整數(shù),表示要跳過的行數(shù)。例如:
[code14]
@echo offfor /f 'skip=2' %%i in (test.txt) do echo %%ipause
這段代碼將跳過頭兩行內(nèi)容,從第3行起顯示test.txt中的信息。
(五) 忽略以指定字符打頭的行:eol=
在cmd窗口中敲入:for /?,相關(guān)的解釋為:
eol=c -指一個(gè)行注釋字符的結(jié)尾(就一個(gè))
FOR /F 'eol=; tokens=2,3* delims=, ' %i in (myfile.txt) do @echo %i %j %k會(huì)分析 myfile.txt 中的每一行,忽略以分號(hào)打頭的那些行……
第一條解釋狗屁不通,頗為費(fèi)解:行注釋字符的結(jié)尾是什么意思?“(就一個(gè))”怎么回事?結(jié)合第二條解釋,才知道eol有忽略指定行的功能。但是,這兩條解釋是互相矛盾的:到底是忽略以指定字符打頭的行,還是忽略以指定字符結(jié)尾的行?
實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),還是用代碼來檢驗(yàn)一下eol的作用吧:
[code15]
@echo offfor /f 'eol=;' %%i in (test.txt) do echo %%ipause
結(jié)果,那些以分號(hào)打頭的行沒有顯示出來。
由此可見,第二條解釋是正確的,eol= 的準(zhǔn)確含義是:忽略以指定字符打頭的行。而第一條的“結(jié)尾”純屬微軟在信口開河。
那么,“(就一個(gè))”又作何解釋呢?
試試這個(gè)代碼:
[code16]
@echo offfor /f 'eol=,;' %%i in (test.txt) do echo %%ipause
此時(shí),屏幕上出現(xiàn)“此時(shí)不應(yīng)有' ;'?!钡膱?bào)錯(cuò)信息。可見,在指定字符的時(shí)候,只能指定1個(gè)——在很多時(shí)候,我對這樣的設(shè)計(jì)頗有微詞而又無可奈何:為什么只能指定1個(gè)而不是多個(gè)?要忽略多個(gè)還得又是if又是findstr加管道來多次過濾,那效率實(shí)在太低下了——能用到的功能基本上都提供,但是卻又做不到更好,批處理,你的功能為什么那么弱?
不知道大家注意到?jīng)]有,如果test.txt中有以分號(hào)打頭的行,那么,這些行在代碼[code14]的執(zhí)行結(jié)果中將憑空消失。
原來,for /f 語句是默認(rèn)忽略以分號(hào)打頭的行內(nèi)容的,正如它默認(rèn)以空格鍵或跳格鍵作為字符串的切分字符一樣。(注:eol=;這種默認(rèn)設(shè)置,在delims=;時(shí)變得無效。)
很多時(shí)候,我們可以充分利用這個(gè)特點(diǎn),比如,在設(shè)計(jì)即將用for讀取的配置文件的時(shí)候,可以在注釋文字的行首加上分號(hào),例如在編寫病毒文件查殺代碼的時(shí)候,可以通過for語句來讀取病毒文件列表,那么,病毒文件列表.ini這個(gè)配置文件可以這樣寫:
;以下是常見的病毒文件,請見一個(gè)殺一個(gè);copyleft:沒有qq.exemsn.exeiexplore.exe
如果要取消這個(gè)默認(rèn)設(shè)置,可選擇的辦法是:
1、為eol=指定另外一個(gè)字符;
2、使用 for /f 'eol=' 語句,也就是說,強(qiáng)制指定字符為空,就像對付delims=一樣。
(六)如何決定該使用 for /f 的哪種句式?(兼談usebackq的使用)
for /f %%i in (……) do (……) 語句有好幾種變形語句,不同之處在于第一個(gè)括號(hào)里的內(nèi)容:有的是用單引號(hào)括起來,有的是用雙引號(hào)包住,有的不用任何符號(hào)包裹,具體格式為:
1、for /f %%i in (文件名) do (……)2、for /f %%i in ('命令語句') do (……)3、for /f %%i in ('字符串') do (……)
看到這里,我想很多人可能已經(jīng)開始犯了迷糊了:如果要解決一個(gè)具體問題,面對這么多的選擇,如何決定該使用哪一條呢?
實(shí)際上,當(dāng)我在上面羅列這些語句的時(shí)候,已經(jīng)有所提示了,不知道你是否注意到了。
如果你一時(shí)無法參透其中奧妙,那也無妨,請聽我一一道來便是。
1、當(dāng)你希望讀取文本文件中的內(nèi)容的話,第一個(gè)括號(hào)中不用任何符號(hào)包裹,應(yīng)該使用的是第1條語句;例如:你想顯示test.txt中的內(nèi)容,那么,就使用 for /f %%i in (test.txt) do echo %%i;
2、當(dāng)你讀取的是命令語句執(zhí)行結(jié)果中的內(nèi)容的話,第一個(gè)括號(hào)中的命令語句必須使用單引號(hào)包裹,應(yīng)該使用的是第2條語句;例如:你想顯示當(dāng)前目錄下文件名中含有test字符串的文本文件的時(shí)候,應(yīng)該使用 for /f %%i in ('dir /a-d /b *test*.txt') do echo %%i 這樣的語句;
3、當(dāng)你要處理的是一個(gè)字符串的時(shí)候,第一個(gè)括號(hào)中的內(nèi)容必須用雙引號(hào)括起來,應(yīng)該是用的是第3條語句;例如:當(dāng)你想把bbs.bathome.net這串字符中的點(diǎn)號(hào)換為短橫線并顯示出來的話,可以使用 for /f 'delims=. tokens=1-3' %%i in ('bbs.bathome.net') do echo %%i-%%j-%%k 這樣的語句。
很顯然,第一個(gè)括號(hào)里是否需要用符號(hào)包裹起來,以及使用什么樣的符號(hào)包裹,取決于要處理的對象屬于什么類型:如果是文件,則無需包裹;如果是命令語句,則用單引號(hào)包裹;如果是字符串,則使用雙引號(hào)括起來。
當(dāng)然,事情并不是絕對如此,如果細(xì)心的你想到了批處理中難纏的特殊字符,你肯定會(huì)頭大如斗。
或許你頭腦中靈光一閃,已經(jīng)想到了一個(gè)十分頭痛的問題:在第1條語句中,如果文件名中含有空格或&,該怎么辦?
照舊嗎?
拿個(gè)叫 test 1.txt 的文件來試試。
你很快寫好了代碼,新建文件-->碼字-->保存為批處理,前后費(fèi)時(shí)不到1分鐘:
[code17]
@echo offfor /f %%i in (test 1.txt) do echo %%ipause
你興沖沖地雙擊批處理,運(yùn)行后,屏幕上出現(xiàn)了可恥的報(bào)錯(cuò)信息:系統(tǒng)找不到文件 test 。
當(dāng)你把 test 1.txt 換成 test&1.txt 后,更怪異的事情發(fā)生了:CMD窗口在你眼前一閃而過,然后,優(yōu)雅地消失了。
你可能覺得自己的代碼寫錯(cuò)了某些符號(hào),你再仔細(xì)的檢查了一次,確認(rèn)沒有筆誤,然后,你再次雙擊批處理,結(jié)果問題照舊;你開始懷疑其他程序?qū)λ赡苡杏绊?,于是關(guān)掉其他窗口,再運(yùn)行了一次,問題依舊;你不服氣地連續(xù)運(yùn)行了好幾次,還是同樣的結(jié)果。
怪哉!
你一拍大腿,猛然想起了一件事:當(dāng)路徑中含有特殊字符的時(shí)候,應(yīng)該使用引號(hào)把路徑括起來。對,就是它了!
但是,當(dāng)你把代碼寫出來之后,你很快就焉了:for /f %%i in ('test 1.txt') do echo %%i,這不就是上面提到的第3條 for /f 命令的格式嗎?批處理會(huì)把 test 1.txt 這個(gè)文件名識(shí)別為字符串?。?/p>
你百無聊賴地在CMD窗口中輸入 for /? ,并重重地敲下了回車,漫無目的地在幫助信息中尋找,希望能找到點(diǎn)什么。
結(jié)果還真讓你到了點(diǎn)什么。
你看到了這樣的描述:
usebackq - 指定新語法已在下類情況中使用: 在作為命令執(zhí)行一個(gè)后引號(hào)的字符串并且一個(gè)單引號(hào)字符為文字字符串命令并允許在 filenameset 中使用雙引號(hào)擴(kuò)起文件名稱。
但是,通讀一遍之后,你卻如墜五里霧中,不知所云。
還好,下面有個(gè)例子,并配有簡單的說明:
FOR /F 'usebackq delims==' %i IN (`set`) DO @echo %i會(huì)枚舉當(dāng)前環(huán)境中的環(huán)境變量名稱。
你仔細(xì)對比了for /f語句使用usebackq和不使用usebackq時(shí)在寫法上的差別,很快就找到了答案:當(dāng)使用了usebackq之后,如果第一個(gè)括號(hào)中是一條命令語句,那么,就要把單引號(hào)'改成后引號(hào)`(鍵盤左上角esc鍵下面的那個(gè)按鍵,與~在同一鍵位上)。
回過頭去再看那段關(guān)于usebackq的描述,字斟句酌,反復(fù)揣摩,終于被你破譯了天機(jī):usebackq 是一個(gè)增強(qiáng)型參數(shù),當(dāng)使用了這個(gè)參數(shù)之后,原來的for語句中第一個(gè)括號(hào)內(nèi)的寫法要做如下變動(dòng):如果第一個(gè)括號(hào)里的對象是一條命令語句的話,原來的單引號(hào)'要改為后引號(hào)`;如果第一個(gè)括號(hào)里的對象是字符串的話,原來的雙引號(hào)'要改為單引號(hào)';如果第一個(gè)括號(hào)里的對象是文件名的話,要用雙引號(hào)'括起來。
驗(yàn)證一下,把[code17]改寫成如下代碼:
[code18]
@echo offfor /f 'usebackq' %%i in ('test 1.txt') do echo %%ipause
測試通過!
此時(shí),你很可能會(huì)仰天長嘆:Shit,微軟這該死的機(jī)器翻譯!
至于把[code17]代碼中的空格換成&后,CMD窗口會(huì)直接退出,那是因?yàn)?是復(fù)合語句的連接符,CMD在預(yù)處理的時(shí)候,會(huì)優(yōu)先把&前后兩部分作為兩條語句來解析,而不是大家想象中的一條完整的for語句,從而產(chǎn)生了嚴(yán)重的語法錯(cuò)誤。因?yàn)闋可娴筋A(yù)處理機(jī)制問題,不屬于本節(jié)要討論的內(nèi)容,在此不做詳細(xì)講解。
這個(gè)時(shí)候,我們會(huì)吃驚地發(fā)現(xiàn),區(qū)區(qū)一條for語句,竟然有多達(dá)6種句型:
1、for /f %%i in (文件名) do (……)2、for /f %%i in ('命令語句') do (……)3、for /f %%i in ('字符串') do (……)4、for /f 'usebackq' %%i in ('文件名') do (……)5、for /f 'usebackq' %%i in (`命令語句`) do (……)6、for /f 'usebackq' %%i in ('字符串') do (……)
其中,4、5、6由1、2、3發(fā)展而來,他們有這樣的對應(yīng)關(guān)系:1-->4、2-->5、3-->6。
好在后3種情形并不常用,所以,牢牢掌握好前三種句型的適用情形就可以了,否則,要在這么多句型中確定選擇哪一條語句來使用,還真有點(diǎn)讓人頭腦發(fā)懵。
至于 for /f 為什么要增加usebacq參數(shù),我只為第4條語句找到了合理的解釋:為了兼容文件名中所帶的空格或&。它在第5、6條語句中為什么還有存在的必要,我也不是很明白,這有待于各位去慢慢發(fā)現(xiàn)。(注:這種解釋雖然有點(diǎn)不靠譜,但也算一種解釋,大家將就看看吧。啟用usebackq選項(xiàng)的時(shí)候,“文件名”取代了“字符串”,那么“字符串”只好改變?yōu)椤懊钫Z句”,“命令語句”只好用后引號(hào)重新表示——簡而言之,是“文件名”符號(hào)改變引起的蝴蝶效應(yīng)。言外之意:usebackq除了在處理帶空格的文件名時(shí)會(huì)用到外,根本就沒有其它的出場機(jī)會(huì)和存在價(jià)值。)
(七)變量延遲詳解
變量延遲在for語句中起著至關(guān)重要的作用,不只是在for語句中,在其他的復(fù)合語句中,它也在幕后默默地工作著,為了突出它的重要性,本節(jié)內(nèi)容在單獨(dú)的樓層中發(fā)出來,希望引起大家的重視。
對于批處理新手而言,“變量延遲”這個(gè)概念很可能聞所未聞,但是,它卻像一堵橫亙在你前進(jìn)道路上的無形高墻,你感受不到它的存在,但當(dāng)你試圖往前沖時(shí),它會(huì)把你狠狠地彈回來,讓你無法逾越、無功而返;而一旦找到了越過它的方法,你就會(huì)發(fā)現(xiàn),在for的世界里,前面已經(jīng)是一片坦途,而你對批處理的理解,又上升到了一個(gè)新的境界。
例如,你編寫了這樣一個(gè)代碼:
[code19]
@echo offset num=0&&echo %num%pause
你的本意是想對變量num賦值之后,再把這個(gè)值顯示出來,結(jié)果,顯示出來的并不是0,而是顯示:ECHO 處于關(guān)閉狀態(tài)。
之所以會(huì)出錯(cuò),是因?yàn)椤白兞垦舆t”這個(gè)家伙在作怪。
在講解變量延遲之前,我們需要了解一下批處理的執(zhí)行過程,它將有助于我們深入理解變量延遲。
批處理的執(zhí)行過程是怎樣的呢?
“自上而下,逐條執(zhí)行”,我想,這個(gè)經(jīng)典的說法大家都已經(jīng)耳熟能詳了,沒事的時(shí)候倒著念,也還別有一番古韻呢,但是,我想問大家的是,大家真的深刻地理解了這句話的含義了嗎?
“自上而下”,這一條和我們本節(jié)的講解關(guān)系不大,暫時(shí)略過不說,后一條,“逐條執(zhí)行”和變量延遲有著莫大的干系,它是我們本節(jié)要關(guān)注的重點(diǎn)。
很多人往往認(rèn)為一行代碼就是一條語句,從而把“逐條執(zhí)行”與“逐行執(zhí)行”等同起來,這就大錯(cuò)特錯(cuò)了。
莫非“逐條執(zhí)行”里暗藏著玄機(jī)?
正是如此。
“逐條”并不等同于“逐行”。這個(gè)“條”,是“一條完整的語句”的意思,并不是指“一行代碼”。在批處理中,是不是一條完整的語句,并不是以行來論的,而是要看它的作用范圍。
什么樣的語句才算“一條完整的語句”呢?
1、在復(fù)合語句中,整個(gè)復(fù)合語句是一條完整的語句,而無論這個(gè)復(fù)合語句占用了多少行的位置。常見的復(fù)合語句有:for語句、if……else語句、用連接符&、||和&&連接的語句,用管道符號(hào)|連接的語句,以及用括號(hào)括起來的、由多條語句組合而成的語句塊;
2、在非復(fù)合語句中,如果該語句占據(jù)了一行的位置,則該行代碼為一條完整的語句。
例如:
[code20]
1 @echo off 2 set num=0 3 for /f %%i in ('dir /a-d /b *.exe') do ( 4 set /a num+=1 5 echo num 當(dāng)前的值是 %num% 6 ) 7 echo 當(dāng)前目錄下共有 %num% 個(gè)exe文件 8 dir /a-d /b *.txt|findstr 'test'>nul&&( 9 echo 存在含有 test 字符串的文本本件10 ) || echo 不存在含有 test 字符串的文本文件11 if exist test.ini (12 echo 存在 test.ini 文件13 ) else echo 不存在 test.ini 文件14 pause
上面的代碼共有14行,但是只有完整的語句只有7條,它們分別是:
第1條:第1行的echo語句;
第2條:第2行的set語句;
第3條:第3、4、5、6行上的for復(fù)合語句;
第4條:第7行的echo語句;
第5條:第8、9、10行上用&&和||連接的復(fù)合語句;
第6條:第11、12、13行上的if……else復(fù)合語句;
第7條:第14行上的pause語句。
在這里,我之所以要花這么長的篇幅來說明一行代碼并不見得就是一條語句,是因?yàn)榕幚淼膱?zhí)行特點(diǎn)是“逐條”執(zhí)行而不是“逐行”執(zhí)行,澄清了這個(gè)誤解,將會(huì)更加理解批處理的預(yù)處理機(jī)制。
在代碼“逐條”執(zhí)行的過程中,cmd.exe這個(gè)批處理解釋器會(huì)對每條語句做一些預(yù)處理工作,這就是批處理中大名鼎鼎的“預(yù)處理機(jī)制”。
預(yù)處理的大致情形是這樣的:首先,把一條完整的語句讀入內(nèi)存中(不管這條語句有多少行,它們都會(huì)被一起讀入),然后,識(shí)別出哪些部分是命令關(guān)鍵字,哪些是開關(guān)、哪些是參數(shù),哪些是變量引用……如果代碼語法有誤,則給出錯(cuò)誤提示或退出批處理環(huán)境;如果順利通過,接下來,就把該條語句中所有被引用的變量及變量兩邊的百分號(hào)對,用這條語句被讀入內(nèi)存之就已經(jīng)賦予該變量的具體值來替換……當(dāng)所有的預(yù)處理工作完成之后,批處理才會(huì)執(zhí)行每條完整語句內(nèi)部每個(gè)命令的原有功能。也就是說,如果命令語句中含有變量引用(變量及緊鄰它左右的百分號(hào)對),并且某個(gè)變量的值在命令的執(zhí)行過程中被改變了,即使該條語句內(nèi)部的其他地方也用到了這個(gè)變量,也不會(huì)用最新的值去替換它們,因?yàn)槟硹l語句在被預(yù)處理的時(shí)候,所有的變量引用都已經(jīng)被替換成字符串常量了,變量值在復(fù)合語句內(nèi)部被改變,不會(huì)影響到語句內(nèi)部的其他任何地方。
順便說一下,運(yùn)行代碼[code20]之后,將在屏幕上顯示當(dāng)前目錄下有多少個(gè)exe文件,是否存在含有 test 字符串的文本文件,以及是否存在 test.ini 這個(gè)文件等信息。讓很多人百思不得其解的是:如果當(dāng)前目錄下存在exe文件,那么,有多少個(gè)exe文件,屏幕上就會(huì)提示多少次 'num 當(dāng)前的值是 0' ,而不是顯示1到N(N是exe文件的個(gè)數(shù))。
結(jié)合上面兩個(gè)例子,我們再來分析一下,為什么這兩段代碼的執(zhí)行結(jié)果和我們的期望有一些差距。
在[code19]中,set num=0&&echo %num%是一條復(fù)合語句,它的含義是:把0賦予變量num,成功后,顯示變量num的值。
雖然是在變量num被賦值成功后才顯示變量num的值,但是,因?yàn)檫@是一條復(fù)合語句,在預(yù)處理的時(shí)候,&&后的%num%只能被set語句之前的語句賦予變量num的具體值來替換,而不能被復(fù)合語句內(nèi)部、&&之前的set語句對num所賦予的值來替換,可見,此num非彼num。可是,在這條復(fù)合語句之前,我們并沒有對變量num賦值,所以,&&之后的%num%是空值,相當(dāng)于在&&之后只執(zhí)行了 echo 這一命令,所以,會(huì)顯示 echo 命令的當(dāng)前狀態(tài),而不是顯示變量num的值(雖然該變量的值被set語句改變了)。
在[code20]中,for語句的含義是:列舉當(dāng)前目錄下的exe文件,每發(fā)現(xiàn)一個(gè)exe文件,變量num的值就累加1,并顯示變量num的值。
看了對[code19]的分析之后,再來分析[code20]就不再那么困難了:第3、4、5行上的代碼共同構(gòu)成了一條完整的for語句,而語句'echo num 當(dāng)前的值是 %num%'與'set /a num+=1'同處復(fù)合語句for的內(nèi)部,那么,第4行上set改變了num的值之后,并不能對第5行上的變量num有任何影響,因?yàn)樵陬A(yù)處理階段,第5行上的變量引用%num%已經(jīng)被在for之前就賦予變量num的具體值替換掉了,它被替換成了0(是被第2行上的set語句賦予的)。
如果想讓代碼[code19]的執(zhí)行結(jié)果中顯示&&之前賦予num的值,讓代碼[code20]在列舉exe文件的時(shí)候,從1到N地顯示exe文件的數(shù)量,那又該怎么辦呢?
對代碼[code19],可以把用&&連接復(fù)合語句拆分為兩條單獨(dú)的語句,寫成:
@echo offset num=0echo %num%pause
但是,這不是我們這次想要的結(jié)果。
對這兩段代碼都適用的辦法是:使用變量延遲擴(kuò)展語句,讓變量的擴(kuò)展行為延遲一下,從而獲取我們想要的值。
在這里,我們先來充下電,看看“變量擴(kuò)展”有是怎么一回事。
用CN-DOS里批處理達(dá)人willsort的原話,那就是:“在許多可見的官方文檔中,均將使用一對百分號(hào)閉合環(huán)境變量以完成對其值的替換行為稱之為“擴(kuò)展(expansion)”,這其實(shí)是一個(gè)第一方的概念,是從命令解釋器的角度進(jìn)行稱謂的,而從我們使用者的角度來看,則可以將它看作是引用(Reference)、調(diào)用(Call)或者獲?。℅et)。”(見:什么情況下該使用變量延遲?http://www.cn-dos.net/forum/viewthread.php?tid=20733)說得直白一點(diǎn),所謂的“變量擴(kuò)展”,實(shí)際上就是很簡單的這么一件事情:用具體的值去替換被引用的變量及緊貼在它左右的那對百分號(hào)。
既然只要延遲變量的擴(kuò)展行為,就可以獲得我們想要的結(jié)果,那么,具體的做法又是怎樣的呢?
一般說來,延遲變量的擴(kuò)展行為,可以有如下選擇:
1、在適當(dāng)位置使用 setlocal enabledelayedexpansion 語句;
2、在適當(dāng)?shù)奈恢檬褂?call 語句。
使用 setlocal enabledelayedexpansion 語句,那么,[code19]和[code20]可以分別修改為:
@echo offsetlocal enabledelayedexpansionset num=0&&echo !num!pause
@echo offset num=0setlocal enabledelayedexpansionfor /f %%i in ('dir /a-d /b *.exe') do ( set /a num+=1 echo num 當(dāng)前的值是 !num!)echo 當(dāng)前目錄下共有 %num% 個(gè)exe文件dir /a-d /b *.txt|findstr 'test'>nul&&( echo 存在含有 test 字符串的文本本件)||echo 不存在含有 test 字符串的文本文件if exist test.ini ( echo 存在 test.ini 文件) else echo 不存在 test.ini 文件pause
使用第call語句,那么,[code19]和[code20]可以分別修改為:
@echo offset num=0&&call echo %%num%%pause
@echo offset num=0for /f %%i in ('dir /a-d /b *.exe') do ( set /a num+=1 call echo num 當(dāng)前的值是 %%num%%)echo 當(dāng)前目錄下共有 %num% 個(gè)exe文件dir /a-d /b *.txt|findstr 'test'>nul&&( echo 存在含有 test 字符串的文本本件)||echo 不存在含有 test 字符串的文本文件if exist test.ini ( echo 存在 test.ini 文件) else 不存在 test.ini 文件pause
由此可見,如果使用 setlocal enabledelayedexpansion 語句來延遲變量,就要把原本使用百分號(hào)對閉合的變量引用改為使用感嘆號(hào)對來閉合;如果使用call語句,就要在原來命令的前部加上 call 命令,并把變量引用的單層百分號(hào)對改為雙層。 其中,因?yàn)閏all語句使用的是雙層百分號(hào)對,容易使人犯迷糊,所以用得較少,常用的是使用 setlocal enabledelayedexpansion 語句(set是設(shè)置的意思,local是本地的意思,enable是能夠的意思,delayed是延遲的意思,expansion是擴(kuò)展的意思,合起來, 就是:讓變量成為局部變量,并延遲它的擴(kuò)展行為)。
通過上面的分析,我們可以知道:
1、為什么要使用變量延遲?因?yàn)橐審?fù)合語句內(nèi)部的變量實(shí)時(shí)感知到變量值的變化。
2、在哪些場合需要使用變量延遲語句?在復(fù)合語句內(nèi)部,如果某個(gè)變量的值發(fā)生了改變,并且改變后的值需要在復(fù)合語句內(nèi)部的其他地方被用到,那么,就需要使用變量延遲語句。而復(fù)合語句有:for語句、if……else語句、用連接符&、||和&&連接的語句、用管道符號(hào)|連接的語句,以及用括號(hào)括起來的、由多條語句組合而成的語句塊。最常見的場合,則是for語句和if……else語句。
3、怎樣使用變量延遲?
方法有兩種:
?、?使用 setlocal enabledelayedexpansion 語句:在獲取變化的變量值語句之前使用setlocal enabledelayedexpansion,并把原本使用百分號(hào)對閉合的變量引用改為使用感嘆號(hào)對來閉合;
?、?使用 call 語句:在原來命令的前部加上 call 命令,并把變量引用的單層百分號(hào)對改為雙層。
“變量延遲”是批處理中一個(gè)十分重要的機(jī)制,它因預(yù)處理機(jī)制而生,用于復(fù)合語句,特別是大量使用于強(qiáng)大的for語句中。只有熟練地使用這一機(jī)制,才能在for的世界中如魚得水,讓自己的批處理水平更上一層樓。很多時(shí)候,對for的處理機(jī)制,我們一直是霧里看花,即使偶有所得,也只是只可意會(huì)難以言傳。希望大家反復(fù)揣摩,多加練習(xí),很多細(xì)節(jié)上的經(jīng)驗(yàn),是只有通過大量的摸索才能得到的。
本節(jié)內(nèi)容在原理上參考了這篇文章:什么情況下該使用變量延遲?http://www.cn-dos.net/forum/viewthread.php?tid=20733,在本論壇中的地址是:http://bbs.bathome.net/viewthread.php?tid=2899
四、翻箱倒柜遍歷文件夾:for /r
(一)for /r 的作用及用法
按照幫助信息里文縐縐的說法,for /r 的作用是“遞歸”,我們換一個(gè)通俗一點(diǎn)的,叫“遍歷文件夾”。
更詳細(xì)的解釋就是:在下面的語句中,如果“元素集合”中只是一個(gè)點(diǎn)號(hào),那么,這條語句的作用就是:列舉“目錄”及其之下的所有子目錄,對這些文件夾都執(zhí)行“命令語句集合”中的命令語句。其作用與嵌套進(jìn) for /f 復(fù)合語句的 'dir /ad /b /s 路徑' 功能類似。如果省略了“目錄”,將在當(dāng)前目錄下執(zhí)行前面描述的操作。
for /r 目錄 %%i in (元素集合) do 命令語句集合
先來個(gè)代碼增強(qiáng)一下印象:
[code21]
@echo offfor /r d:\test %%i in (.) do echo %%ipause
執(zhí)行的結(jié)果如下所示:
d:\test\.d:\test\1\.d:\test\2\.d:\test\3\.
效果就是顯示 d:\test 目錄及其之下是所有子目錄的路徑,其效果與 dir /ad /b /s d:\test 類似。若要說到兩者的區(qū)別,可以歸納出3點(diǎn):
1、for /r 列舉出來的路徑最后都帶有斜杠和點(diǎn)號(hào),而 dir 語句則沒有,會(huì)對獲取到的路徑進(jìn)行進(jìn)一步加工產(chǎn)生影響;
2、for /r 不能列舉帶隱藏屬性的目錄,而 dir 語句則可以通過指定 /a 后面緊跟的參數(shù)來獲取帶指定屬性的目錄,更加靈活;
3、若要對獲取到的路徑進(jìn)行進(jìn)一步處理,則需要把 dir 語句放入 for /f 語句中進(jìn)行分析,寫成 for /f %%i in ('dir /ad /b /s') do …… 的形式;由于 for /r 語句是邊列舉路徑邊進(jìn)行處理,所以,在處理大量路徑的時(shí)候,前期不會(huì)感到有停頓,而 for /f 語句則需要等到 dir /ad /b /s 語句把所有路徑都列舉完之后,再讀入內(nèi)存進(jìn)行處理,所以,在處理大量路徑的時(shí)候,前期會(huì)感到有明顯的停頓。
第2點(diǎn)差別很容易被大家忽視,導(dǎo)致用 for /r 列舉路徑的時(shí)候會(huì)造成遺漏;而第3點(diǎn)則會(huì)讓大家有更直觀的感受,很容易感覺到兩者之間的差別。
要是“元素集合”不是點(diǎn)號(hào)呢?那又如何?
來看看這個(gè)代碼:
[code22]
@echo offfor /r d:\test %%i in (a b c) do echo %%ipause
運(yùn)行的結(jié)果是:
原來,它的含義是:列舉 d:\test 及其所有的子目錄,對所有的目錄路徑都分別添加a、b、c之后再顯示出來。
再來看一個(gè)代碼:
[code23]
@echo offfor /r d:\test %%i in (*.txt) do echo %%ipause
運(yùn)行結(jié)果是:
D:\test\test.txtD:\test\1\1.txtD:\test\1\2.txtD:\test\2\a.txtD:\test\2\b.txtD:\test\3\1.txt
這段代碼的含義是:列舉 d:\test 及其所有子目錄下的txt文本文件(以.txt結(jié)尾的文件夾不會(huì)被列出來)。
我們再回過頭來歸納一下這個(gè)語句的作用:
for /r 目錄 %%i in (元素集合) do 命令語句集合
上面語句的作用是:
1、列舉“目錄”及該目錄路徑下所有子目錄,并把列舉出來的目錄路徑和元素集合中的每一個(gè)元素拼接成形如“目錄路徑\元素”格式的新字符串,然后,對每一條這樣的新字符串執(zhí)行“命令語句集合”中的每一條命令;
特別的是:當(dāng)“元素集合”帶以點(diǎn)號(hào)分隔的通配符?或*的時(shí)候,把“元素集合”視為文件(不視為文件夾),整條語句的作用是匹配“目錄”所指文件夾及其所有子文件夾下匹配的文件;若不以點(diǎn)號(hào)分隔,則把“元素集合”視為文件夾(不視為文件);
2、當(dāng)省略掉“目錄”時(shí),則針對當(dāng)前目錄;
3、當(dāng)元素集合中僅僅是一個(gè)點(diǎn)號(hào)的時(shí)候,將只列舉目錄路徑;
(二)for /r 還是 dir /ad /b /s?列舉目錄時(shí)該如何選擇
前面已經(jīng)說過,當(dāng)列舉目錄時(shí),for /r 和 dir /ad /b /s 的效果是非常類似的,這就產(chǎn)生了一個(gè)問題:當(dāng)我要獲取目錄路徑并進(jìn)行進(jìn)一步處理的時(shí)候,兩者之間,我該如何選擇?
這個(gè)問題,前面其實(shí)已經(jīng)有過一些討論,現(xiàn)在我們再來作詳細(xì)的分析。
我們來看一下兩者各自的優(yōu)缺點(diǎn):
1、for /r:
1)優(yōu)點(diǎn):
① 只通過1條語句就可以同時(shí)實(shí)現(xiàn)獲取目錄路徑和處理目錄路徑的操作;
② 遍歷文件夾的時(shí)候,是邊列舉邊處理的,獲取到一條路徑就處理一條路徑,內(nèi)存占用小,處理大量路徑的時(shí)候不會(huì)產(chǎn)生停頓感;
2)缺點(diǎn):
① 不能獲取到帶隱藏屬性的目錄,會(huì)產(chǎn)生遺漏;
② 不能獲取帶指定屬性的目錄
2、dir /ad /s:
1)優(yōu)點(diǎn):
① 能一次性獲取帶任意屬性的目錄,不會(huì)產(chǎn)生遺漏;
② 能通過指定不同的參數(shù)獲取帶任意屬性的目錄,更具靈活性。
2)缺點(diǎn):
① dir /ad /s 語句僅能獲取到目錄路徑,若要實(shí)現(xiàn)進(jìn)一步的處理,還需要嵌入 for /f 語句中才能實(shí)現(xiàn),寫法不夠簡潔;
② 嵌入 for /f 語句之后,需要寫成 for /f 'delims=' %%i in ('dir /ad /b /s') do …… 的格式,受 for /f 語句運(yùn)行機(jī)制的制約,需要先列舉完所有的路徑放入內(nèi)存之后,才能對每一條路徑進(jìn)行進(jìn)一步的處理,處理大量路徑時(shí),內(nèi)存占用量偏大,并且在前期會(huì)產(chǎn)生明顯的停頓感,用戶體驗(yàn)度不夠好;
綜合上述分析,可以做出如下選擇:
1、若僅僅是為了獲取某文件夾及其所有子文件夾的路徑的話,請選擇 dir /ad /b /s 語句;
2、若需要過濾帶隱藏屬性的文件夾的話,for /r 和 dir 語句都可以實(shí)現(xiàn),但 for /r 內(nèi)存占用小,處理速度快,是上上之選;
3、若需要獲取所有文件夾,則除了 dir /ad /b /s 外,別無選擇,因?yàn)?for /r 語句會(huì)遺漏帶隱藏屬性的文件夾;
在實(shí)際的使用中,我更喜歡使用 for /f 和 dir 的組合,因?yàn)樗粫?huì)產(chǎn)生遺漏,并能給我?guī)砀`活的處理方式,唯一需要忍受的,就是它在處理大量路徑時(shí)前期的停頓感,以及在這背后稍微有點(diǎn)偏高的內(nèi)存占用;在我追求速度且可以忽略帶隱藏屬性的目錄的時(shí)候,我會(huì)換用 for /r 的方案,不過這樣的情形不多——有誰會(huì)愿意為了追求速度而容忍遺漏呢?
五、僅僅為了匹配第一層目錄而存在:for /d
for /d 中 /d ,完整的含義是 /directory,本意是為了處理文件夾,它的完整語句應(yīng)該是這樣的:
for /d %%i in (元素集合) do 命令語句集合
當(dāng)“元素集合”中包含有通配符?或*時(shí),它會(huì)匹配文件夾,但是,相比 for /r 而言,這個(gè)時(shí)候的for /d,其作用就小得可憐了:它僅能匹配當(dāng)前目錄下的第一級文件夾,或是指定位置上的文件夾,而不能匹配更深層次的子文件夾。
例如:for /d %%i in (d:\test*) do echo %%i 這樣的語句 ,會(huì)匹配到形如:d:\test、d:\test1、d:\test2之類的文件夾,若不存在這樣的路徑,將不會(huì)有任何回顯。
當(dāng)“元素集合”中不包含任何的通配符時(shí),它的作用和 'for %%i in (元素集合) do 命令語句集合' 這樣的語句別無二致。
因此,for /d 的角色就變得很微妙了:當(dāng)“元素集合”中包含通配符?或*時(shí),它的作用就是匹配文件夾,此時(shí),它僅能匹配當(dāng)前目錄下的第一級文件夾,或是指定位置上的文件夾,在層次深度上不及 for /r,但和 for /r 一樣的壞脾氣:不能匹配帶隱藏屬性的文件夾;在靈活性上不及for /f和dir的組合;當(dāng)“元素集合”中不包含任何統(tǒng)配符的時(shí)候,它完全是 'for %%i in (元素集合) do ……' 語句的翻版,但是又稍顯復(fù)雜。
for /d 的作用是如此有限,我使用的次數(shù)是如此之少,以至于我一度找不到它的用武之地,認(rèn)為它食之無味,棄之可惜,完全是雞肋一塊。
某年某月,我在cmd窗口里寫下了這樣的代碼:
[code24]
for /d %i in (test*) do @echo %i
我的本意是想查看在我的臨時(shí)目錄下,長年累月的測試工作到底建立了多少測試文件夾,以便我隨后把echo換成rd刪除之。這個(gè)時(shí)候,我發(fā)現(xiàn)這條代碼是如此 的簡潔,是 for /r 或 for和 dir /ad /b 的組合所無法替代的(echo換成rd就可以直接刪除掉這些測試目錄)。
簡潔的代碼給我?guī)淼南矏們H僅持續(xù)了短短10幾秒的時(shí)間,我便開始了迷惘——能用到for /d的類似情形,貌似少之又少且乏善可陳啊。
(注:正如qzwqzw所言,for /r /d是可以一起使用的;【在for有限的4個(gè)參數(shù)中,據(jù)我所知只有/r /d可以一起使用】。
例如:
@echo offFor /r /d %%i in (*) do echo %%ipause>nul
效果:
顯示當(dāng)前目錄下所有的文件夾【包括子文件夾】;等價(jià)于 'dir /ad /s /b'。
for /r /d 其實(shí)是對 /d 參數(shù)的擴(kuò)展,/d參數(shù)本身只能處理第一層文件夾,但是加上/r參數(shù)后就可以處理所有的子文件夾;
for /r /d依然不能處理隱藏文件夾。
這里給出使用for /r /d的一般條件:1.要對文件夾進(jìn)行操作(dir /ad /s /b可以顯示,但不能對文件夾進(jìn)行操作);2.不處理隱藏文件夾(說到底,還是for /f 和dir結(jié)合的命令更強(qiáng)大些)。
六、計(jì)數(shù)循環(huán):for /l
/l 者,/loop的縮寫是也,從鳥語翻譯過來,loop就是循環(huán)的意思。實(shí)際上,所有的for語句,都可以看成是一種“循環(huán)”,只是在/l中,特指按照指定次數(shù)進(jìn)行循環(huán)罷了。
for /l 語句的完整格式是這樣的:
for /l %%i in (x,y,z) do (……)
在這個(gè)語句中,x、y和z都只能取整數(shù),正負(fù)皆可,x指代起始值,y指代步長,z為終止值,具體含義為:從x開始計(jì)數(shù),以y為步長,直至最接近 z的那個(gè)整數(shù)值為止,這之間有多少個(gè)數(shù),do后的語句就執(zhí)行多少次。
舉個(gè)具體的例子:
[code25]
for /l %%i in (1,2,10) do echo bathome
在以上的代碼中,初始值是1,步長為2,終止值為10,表明計(jì)數(shù)從1開始,每隔2個(gè)數(shù)計(jì)算一次,直至最接近10的那個(gè)整數(shù),羅列出來,就是1,3,5,7,9,再下一個(gè)就是11,超過10了,不再計(jì)算在內(nèi),所以,do后的語句只執(zhí)行5次,將連續(xù)顯示5個(gè)bathome。
實(shí)際上,x,y和z的值可正可負(fù),甚至為0,限制非常寬松:
1、步長y的值不能為0;
2、當(dāng)步長y的值為正整數(shù)時(shí),終止值z不能小于初始值x;
3、當(dāng)步長y的值為負(fù)整數(shù)的時(shí)候,終止值z不能大于初始值x。
換而言之,必須保證in和do之間能取到一個(gè)有效的數(shù)組序列。
例如:
[code26]
for /l %%i in (-1,2,5) do echo bathome
[code27]
for /l %%i in (5,-2,-1) do echo bathome
以上兩條代碼的功能完全一樣,都將顯示4次bathome,區(qū)別就在于[code26]是正序計(jì)算,而[code27]是逆序計(jì)數(shù)而已。
以下幾條代碼都是有問題的:
[code28]
for /l %%i in (1,0,1) do echo bathome
[code29]
for /l %%i in (2,1,1) do echo bathome
[code30]
for /l %%i in (1,-1,2) do echo bathome
其中,[code28]違反了步長不能為0的限制,將陷入無限循環(huán)中;[code29]和[code30]都犯了同樣的錯(cuò)誤:無法獲得有效的數(shù)列元素,導(dǎo)致in和do之間取到的值為空元素,從而使得整條for語句無從執(zhí)行。
當(dāng)大家明白了 for /l 的具體功能之后,是否會(huì)想到了與它有異曲同工之妙的goto循環(huán)語句呢?似乎,for /l 和 goto 循環(huán)語句可以相互替換?
一般而言,for /l語句可以換成goto循環(huán),但是,goto循環(huán)并不一定能被 for /l 語句替換掉。具體原因,請大家仔細(xì)想想,我在此不再詳細(xì)解說,只是就大家非常關(guān)心的一個(gè)問題提供一個(gè)簡潔的答案,那就是:什么時(shí)候該用 for /l 計(jì)數(shù)循環(huán),而什么時(shí)候又該用goto條件循環(huán)?
答案非常簡單:當(dāng)循環(huán)次數(shù)確定的時(shí)候,首選 for /l 語句,也可使用goto語句但不推薦;當(dāng)循環(huán)次數(shù)不確定的時(shí)候,用goto語句將是唯一的選擇,因?yàn)椋@個(gè)時(shí)候需要用if之類的條件語句來判斷何時(shí)結(jié)束goto跳轉(zhuǎn)。
轉(zhuǎn)載:http://www.bathome.net/thread-2189-1-1.html
聯(lián)系客服