批處理-For詳解


轉自:https://www.cnblogs.com/DswCnblog/p/5435300.html

大綱

復制代碼
一 前言
二 for語句的基本用法
三 for /f (delims、tokens、skip、eol、userbackq、變量延遲)
四 for /r (遞歸遍歷)
五 for /d (遍歷目錄)
六 for /l (計數循環)
復制代碼

 

一、前言

在批處理中,for是最為強大的命令語句,它的出現,使得解析文本內容、遍歷文件路徑、數值遞增/遞減等操作成為可能;配合if、call、 goto等流程控制語句,更是可以實現腳本復雜的自動化、智能化操作;合理使用for語句,還能使代碼大為簡化,免除各位編寫大量重復語句之苦。而能否熟 練使用for語句,已經成為衡量一個人批處理水平高低最主要的標准。

在這個系列教程中,我將通過實際應用中頻繁出現的例子,帶領大家步入for語句的神奇之門,一步步邁向for語句的魔幻殿堂,使得大家在實際的應用中,能獨立寫出簡潔高效的代碼,在批處理的世界里自由馳騁。

注意:以下的講解,都是基於簡體中文版Windows XP Pro SP3的操作系統環境。


二、for語句的基本用法

正如色彩繽紛的七彩光芒是由紅綠藍三原色構成的一樣,最復雜的for語句,也有其基本形態,它的模樣是這樣的:

在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


之所以要區分cmd窗口和批處理文件兩種環境,是因為在這兩種環境下,命令語句表現出來的行為雖然基本一樣,但是在細節上還是稍有不同。
最明顯的一個差異就是:在cmd窗口中,for之后的形式變量I必須使用單百分號引用,即%i;而在批處理文件中,引用形式變量i必須使用雙百分號,即%%i。

我們先來看一下for語句的基本要素都有些什么:
  1、for、in和do是for語句的關鍵字,它們三個缺一不可;
  2、%%I是for語句中對形式變量的引用,就算它在do后的語句中沒有參與語句的執行,也是必須出現的;
  3、in之后,do之前的括號不能省略;
  4、command1表示字符串或變量,command2表示字符串、變量或命令語句;

現在,你可能已經會寫一個簡單的for語句了,比如:
[code1]

@echo off
for %%I in (bbs.bathome.net) do echo %%I
pause

保存為批處理文件並執行,將會在彈出的批處理窗口中看到這樣的信息:

bbs.bathome.net
請按任意鍵繼續...

很快地,你會覺得這個for語句是如此的簡單,簡單到你絲毫感受不出它的強大:這個for語句,和我直接用echo語句沒什么兩樣啊!

是的,演示代碼永遠都只是演示而已,就像大多數高級語言的教科書一樣,在引導新手學習的時候,基本上都是千篇一律地告訴大家如何編寫一個能顯示 hello world! 的窗口,從這些演示代碼中,你看不到它們具有多少實用性,你只是感到有點好奇:咦,居然彈出了一個窗口?片刻之后,你就會覺得索然無味。

那好吧,為了讓大家對for更加感興趣,我們先來分析一下for語句的一些注意事項,之后,再讓大家看看更為強大的for語句實例。

   1、for語句的形式變量I,可以換成26個字母中的任意一個,這些字母會區分大小寫,也就是說,%%I和%%i會被認為不是同一個變量;形式變量I還可以換成其他的字符,但是,為了不與批處理中的%0~%9這10個形式變量發生沖突,請不要隨意把%%I替換為%%0 ~%%9中的任意一個;
   2、in和do之間的command1表示的字符串或變量可以是一個,也可以是多個,每一個字符串或變量,我們稱之為一個元素,每個元素之間,用空格鍵、跳格鍵、逗號、分號或等號分隔;
   3、for語句依次提取command1中的每一個元素,把它的值賦予形式變量I,帶到do后的command2中參與命令的執行;並且每次只提取一個元 素,然后執行一次do后的命令語句,而無論這個元素是否被帶到command2中參與了command2的運行;當執行完一次do后的語句之后,再提取 command1中的下一個元素,再執行一次command2,如此循環,直到command1中的所有元素都已經被提取完畢,該for語句才宣告執行結 束;

其中,第3點是最為關鍵的,它描述了for語句的執行過程,是for語句的精髓所在,大家一定要牢記這一條,才能深刻理解更為復雜的for流程。

有了以上的基礎,我們再來看一個例子,這個例子修改了[code1]的部分內容,結果將大不一樣:
[code2]

@echo off
for %%I in (bbs,bathome,net) do echo %%I
pause

和[code1]的執行結果[result1]相比,[result2]發生了如下變化:
   1、顯示結果分成了3行(不算最后一行中文提示);
 2、每一行都從逗號處被切分;

如果把 bbs.bathome.net 這個字符串中的點號換為空格、跳格或等號,執行結果將和example2的執行結果別無二致。

現在,我們來分析一下[code2]代碼中for語句的執行過程:
 首先,for語句以逗號為分隔符,把 bbs,bathome.net 這個字符串切分成三個元素:bbs、bathome和cn,由此決定了do后的語句將會被執行3次;
 然后,第一次執行過程是這樣的:先把 bbs 這個字符串作為形式變量I的值,帶入do后的語句中加以執行,也就是執行 echo %%I 語句,此時的I值為bbs,因此,第一次執行的結果,將會在屏幕上顯示bbs這個字符串;第二次執行和第一次執行的過程是一樣的,只不過此時I的值已經被替換為command1中的第二個元素了,也就是 bathome 這個字符串;如此循環,當第三次echo執行完畢之后,整條for語句才算執行完畢,此時,將執行下一條語句,也就是pause命令。

為了讓大家見識一下for的真正威力,本人絞盡腦汁,翻帖無數,不得要領,萬般無奈之下,只好亮出了塵封在箱底多年的一段代碼:檢測當前硬盤都有哪些分區。

[code3]

@echo off
set str=c d e f g h i j k l m n o p q r s t u v w x y z
echo 當前硬盤的分區有:
for %%i in (%str%) do if exist %%i: echo %%i:
pause

這段代碼能檢測硬盤都有哪些分區,包括U盤和移動硬盤的分區,但是,當光驅中有盤的時候,也會被列出來,這是本代碼的一個缺憾,在以后的講解中,我將向大家講述如何消除這個瑕疵,敬請關注本系列的后續章節。

高級應用:

想知道當前目錄下都有哪些文件嗎?請用下面的代碼:

復制代碼
@echo off

  rem 首先建立臨時文件test.txt
  echo ;注釋行,這是臨時文件,用完刪除 >test.txt
  echo 11段 12段 13段 14段 15段 16段 >>test.txt    ::向一個文件追加內容使用>>
  echo 21段,22段,23段,24段,25段,26段 >>test.txt
  echo 31段-32段-33段-34段-35段-36段 >>test.txt

pause
復制代碼

想列出當前目錄下所有的文本文件嗎?請用下面的代碼

@echo off
for %%i in (*.txt) do echo "%%i"
pause

想列出只用兩個字符作為文件名的文本文件嗎?(注:實際上這個代碼是輸出少於或等於兩個字符作為文件名的文本文件)請用下面的代碼:

@echo off
for %%i in (??.txt) do echo "%%i"
pause

題外話:

  1、列出當前目錄下各種文件的方法,最簡單的還是用dir命令,但是,從以上代碼中,各位可以加深對for語句執行流程的理解(用到了通配符*和?);
  2、注意:以上代碼不能列出含有隱藏或系統屬性的文件;(注:這里其實有一個很有趣的現象,windows中的系統文件一般具備兩種屬性——隱藏和系統;但是你如果測試的話就會發現,加上+s屬性,但是不加+h的文件是可以被簡單的for顯示出來的。
例如:

@echo off
attrib +s 1.txt
For %%i in (*.txt) do Echo %%i
pause

這里的1.txt在結果中顯示出來了。所以“以上代碼不能列出含有隱藏或系統屬性的文件”是不准確的,而因該說成“以上代碼不能列出含有隱藏屬性的文件”)

 

三、文本解析顯神威:for /f 用法詳解

前言
 for /f 是個十分強大的家伙。
 如果說,for語句是批處理中最強大的語句的話,那么,for /f 就是精華中的精華。
 for /f 的強大,和它擁有眾多的開關密切相關。因為開關眾多,所以用法復雜,本章將分成若干小節,為大家逐一介紹強大的 for /f 語句。

(1)為解析文本而生:for /f 的基本用法

  所有的對象,無論是文件、窗體、還是控件,在所有的非機器語言看來,無外乎都是形如"c:\test.txt"、"CWnd"之類的文本信息;而所有的對象,具體的如ini文件中的某條配置信息、注冊表中的某個鍵值、數據庫中的某條記錄…都只有轉化為具有一定格式的文本信息,方可被代碼識別、操 控。可以說,編程的很大一部分工作,都是在想方設法絞盡腦汁如何提取這些文本信息。

  而提取文本信息,則是for /f的拿手好戲:讀取文件內容;提取某幾行字符;截取某個字符片段;對提取到的內容再切分、打亂、雜糅……只要你所能想到的花樣,for /f 都會想方設法幫你辦到,因為,for /f 就是被設計成專門用於解析文本的。

先來看個例子。

假如有個文本文件test.txt,內容如下:
[txt1]

論壇的目標是:不求最大,但求最好,做最實用的批處理論壇。
論壇地址:bbs.bathome.net。
這里是:新手晉級的福地,高手論劍的天堂。

那么,將如下代碼保存為test.cmd,並放在test.txt同一目錄下運行,將會在屏幕上原樣顯示test.txt的內容:
[code4]

@echo off
for /f %%i in (test.txt) do echo %%i
pause

  這段代碼,主要是讓你樹立這樣一種觀念:讀取文本文件的內容(注:改為“逐行分析文本文件的內容”,因為讀取文本文件內容的方法命令有很多,比如重定向輸入,又比如type/more/find/sort等命令),請使用 for /f 語句!

進階話題:for /f 語句是把整個test.txt一次性顯示出來的?

在這段代碼中,雖然執行結果是把test.txt中的所有內容都顯示出來了,貌似 for /f 語句是把整個test.txt一次性顯示到屏幕上,實際上並非如此。

  無論for語句做何種變化,它的執行過程仍然遵循基本的for流程:依次處理每個元素,直到所有的元素都被處理為止。只不過在for /f語句中,這里的元素是指文件中的每一行,也就是說,for /f 語句是以行為單位處理文本文件的。這是一條極為重要的規則,在上一章中也強調過它的重要性,希望在接下來的學習過程中,你能時刻牢記這一原則,那么,很多問題將會迎刃而解。以下是驗證這一說法的演示代碼(在[code4]的基礎上添加了&pause語句):
[code5]

@echo off
for /f %%i in (test.txt) do echo %%i&pause
pause

(2) 切分字符串的利器:delims=

還是[txt1]這段文本,把[code4]改造一下:
[code6]

@echo off
for /f "delims=," %%i in (test.txt) do echo %%i
pause

再次運行test.cmd,看到什么變化了嗎?
[result2]

論壇的目標是:不求最大
論壇地址:bbs.bathome.net。
這里是:新手晉級的福地
請按任意鍵繼續...

  結果,你驚奇地發現,每行第一個逗號之后的所有內容都不見了(如果有不存在逗號的行,則保留原樣),也就說,你成功地提取到了每行第一個逗號之前的所有內容!

  如果別人給了你一個軟件清單,每行都是"英文軟件名(逗號)中文軟件名"的格式,而你卻只想保留英文名的時候,這段代碼將是多么有用啊!再假設,有 這么一個IP文件,第一列是數字格式的IP地址,第二列是具體的空間地址,列與列之間用逗號分隔,而你想提取其中數字格式的IP,呵呵,我不說你也知道該 怎么辦了吧?

要是文本內容不是以逗號分隔,而是以其他符號分隔,那么,把"delims=,"的逗號換成相應的符號就可以了。

在這里,我們引入了一個新的開關:"delims=,",它的含義是:以逗號作為被處理的字符串的分隔符號。

在批處理中,指定分隔符號的方法是:添加一個形如 "delims=符號列表" 的開關,這樣,被處理的每行字符串都會被符號列表中羅列出來的符號切分開來。

需要注意的是:如果沒有指定"delims=符號列表"這個開關,那么,for /f 語句默認以空格鍵或跳格鍵作為分隔符號。請把[txt1]中不同位置上的標點符號改為空格或跳格,再運行[code4]試試。

進階話題:如果我要指定的符號不止一個,該怎么辦?

在上面的講解中,我提到了指定分隔符號的方法:添加一個形如"delims=符號列表"的開關。不知道你注意到沒有,我的說法是"符號列表"而非"符號",這是大有講究的,因為,你可以一次性指定多個分隔符號!

還是以[txt1]為例,把[code6]再改造一下
[code7]

@echo off
for /f "delims=.," %%i in (test.txt) do echo %%i
pause

結果顯示:
[result3]

論壇的目標是:不求最大
論壇地址:bbs
這里是:新手晉級的福地
請按任意鍵繼續...

這樣,第一個點號或第一個逗號之前的內容都被提取出來了。

[code7]的執行過程是:逐行讀取test.txt中的內容,以點號和逗號切分每一行的內容(不存在點號和逗號的行,則不再切分,為了描述的方便,我們把被點號或逗號切分的一個一個的字符串片段,稱之為節),然后,for /f 會提取第一節的內容作為最終結果,顯示在屏幕上。需要注意的是,在這里,所有行的字符串被切分成了兩個以上的節,但是,[code7]的代碼只會提取第一節字符串的內容,因為 for /f 語句默認只提取第一節的符串。

 

(3) 定點提取:tokens=

上一節在講解 delims= 的時候,我一再強調 for /f 默認只能提取到第一節的內容,現在我們來思考一個問題:如果我要提取的內容不在第一節上,那怎么辦?

這回,就該輪到 tokens= 出馬了。

tokens= 后面一般跟的是數字,如 tokens=2,也可以跟多個,但是每個數字之間用逗號分隔,如 tokens=3,5,8,它們的含義分別是:提取第2節字符串、提取第3、第5和第8節字符串。注意,這里所說的“節”,是由 delims= 這一開關划分的,它的內容並不是一成不變的。

下面來看一個例子:
[txt2]

尺有所短,寸有所長,學好批處理沒商量,考慮問題復雜化,解決問題簡潔化。

對[txt2]這段文本,假設它們保存在文件test.txt中,如果我想提取“學好批處理沒商量”這句話,該如何寫代碼呢?

我們稍微觀察一下[txt2]就會發現,如果以逗號作為切分符號,就正好可以把“學好批處理沒商量”化為單獨的一“節”,結合上一節的講解,我們知 道,"delims=," 這個開關是不可缺少的,而要提取的內容在以逗號切分的第3節上,那么,tokens= 后面的數字就應該是3了,最終的代碼如下:
[code8]

@echo off
for /f "delims=, tokens=3" %%i in (test.txt) do echo %%i
pause

如果我們現在要提取的不只一個“節”,而是多個,那又怎么辦呢?比如,要提取以逗號切分的第2節和第5節字符串,是寫成這樣嗎?
[code9]

@echo off
for /f "delims=, tokens=2,5" %%i in (test.txt) do echo %%i
pause

運行批處理后發現,執行結果只顯示了第2節的內容。

原來,echo 后面的 %%i 只接收到了 tokens=2,5 中第一個數值2所代表的那個字符串,而第二個數值5所代表的字符串因為沒有變量來接收,所以就無法在執行結果中顯示出來了。

那么,要如何接收 tokens= 后面多個數值所指代的內容呢?

for /f 語句對這種情況做如下規定:

如果 tokens= 后面指定了多個數字,如果形式變量為%%i,那么,第一個數字指代的內容用第一個形式變量%%i來接收,第二個數字指代的內容用第二個形式變量%%j來接收,第三個數字指代的內容用第三個形式變量%%k來接收……第N個數字指代的內容用第N個形式變量來接收,其中,形式變量遵循字母的排序,第N個形式變量具體是什么符號,由第一個形式變量來決定:如果第一個形式變量是%%i,那么,第二個形式變量就是%%j;如果第一個形式變量用的是%%x,那么,第二個 形式變量就是%%y。

現在回頭去看[code9],你應該知道如何修改才能滿足題目的要求了吧?修改結果如下:
[code10]

@echo off
for /f "delims=, tokens=2,5" %%i in (test.txt) do echo %%i %%j
pause

如果有這樣一個要求:顯示[txt2]中的內容,但是逗號要替換成空格,如何編寫代碼?

結合上面所學的內容,稍加思索,你可能很快就得出了答案:
[code11]

@echo off
for /f "delims=, tokens=1,2,3,4,5" %%i in (test.txt) do echo %%i %%j %%k %%l %%m
pause

寫完之后,你可能意識到這樣一個問題:假如要提取的“節”數不是5,而是10,或者20,或者更多,難道我也得從1寫到10、20或者更多嗎?有沒有更簡潔的寫法呢?

答案是有的,那就是:如果要提取的內容是連續的多“節”的話,那么,連續的數字可以只寫最小值和最大值,中間用短橫連接起來即可,比如 tokens=1,2,3,4,5 可以簡寫為 tokens=1-5 。

還可以把這個表達式寫得更復雜一點:tokens=1,2-5,tokens=1-3,4,5,tokens=1-4,5……怎么方便就怎么寫吧。

大家可能還看到一種比較怪異的寫法:
[code12]

@echo off
for /f "delims=, tokens=1,*" %%i in (test.txt) do echo %%i %%j
pause

結果,第一個逗號不見了,取代它的是一個空格符號,其余部分保持不變。

其中奧妙就在這個星號上面。

tokens=后面所接的星號具備這樣的功能:字符串從左往右被切分成緊跟在*之前的數值所表示的節數之后,字符串的其余部分保持不變,整體被*所表示的一個變量接收。

理論講解是比較枯燥的,特別是為了嚴密起見,還使用了很多限定性的修飾詞,導致句子很長,增加了理解的難度,我們還是結合[code12]來講解一下吧。

[txt2] 的內容被切分,切分符號為逗號,當切分完第一節之后,切分動作不再繼續下去,因為 tokens=1,* 中,星號前面緊跟的是數字1;第一節字符串被切分完之后,其余部分字符串不做任何切分,整體作為第二節字符串,這樣,[txt2]就被切分成了兩節,分別 被變量%%i和變量%%j接收。

以上幾種切分方式可以結合在一起使用。不知道下面這段代碼的含義你是否看得懂,如果看不懂的話,那就運行一下代碼,然后反復揣摩,你一定會更加深刻地理解本節所講解的內容的:
[code13]

@echo off
for /f "delims=, tokens=1,3-4,*" %%i in (test.txt) do echo %%i %%j %%k %%l
pause

(4) 跳過無關內容,直奔主題:skip=n

  很多時候,有用的信息並不是貫穿文本內容的始終,而是位於第N行之后的行內,為了提高文本處理的效率,或者不受多余信息的干擾,for /f 允許你跳過這些無用的行,直接從第N+1行開始處理,這個時候,就需要使用參數 skip=n,其中,n是一個正整數,表示要跳過的行數。例如:
[code14]

@echo off
for /f "skip=2" %%i in (test.txt) do echo %%i
pause

這段代碼將跳過頭兩行內容,從第3行起顯示test.txt中的信息。

 

(5) 忽略以指定字符打頭的行:eol=

 在cmd窗口中敲入:for /?,相關的解釋為:

eol=c    -指一個行注釋字符的結尾(就一個)
FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do @echo %i %j %k
會分析 myfile.txt 中的每一行,忽略以分號打頭的那些行……

第一條解釋狗屁不通,頗為費解:行注釋字符的結尾是什么意思?“(就一個)”怎么回事?結合第二條解釋,才知道eol有忽略指定行的功能。但是,這兩條解釋是互相矛盾的:到底是忽略以指定字符打頭的行,還是忽略以指定字符結尾的行?

實踐是檢驗真理的唯一標准,還是用代碼來檢驗一下eol的作用吧:
[code15]

@echo off
for /f "eol=;" %%i in (test.txt) do echo %%i
pause

結果,那些以分號打頭的行沒有顯示出來。

由此可見,第二條解釋是正確的,eol= 的准確含義是:忽略以指定字符打頭的行。而第一條的“結尾”純屬微軟在信口開河。

那么,“(就一個)”又作何解釋呢?

試試這個代碼:
[code16]

@echo off
for /f "eol=,;" %%i in (test.txt) do echo %%i
pause

此時,屏幕上出現“此時不應有" ;"。”的報錯信息。可見,在指定字符的時候,只能指定1個——在很多時候,我對這樣的設計頗有微詞而又無可奈何:為什么只能指定1個而不是多個?要忽略多個還得又是if又是findstr加管道來多次過濾,那效率實在太低下了——能用到的功能基本上都提供,但是卻又做不到更好,批處理,你的功能為什么那么弱?

不知道大家注意到沒有,如果test.txt中有以分號打頭的行,那么,這些行在代碼[code14]的執行結果中將憑空消失。

原來,for /f 語句是默認忽略以分號打頭的行內容的,正如它默認以空格鍵或跳格鍵作為字符串的切分字符一樣。(注:eol=;這種默認設置,在delims=;時變得無效。)

很多時候,我們可以充分利用這個特點,比如,在設計即將用for讀取的配置文件的時候,可以在注釋文字的行首加上分號,例如在編寫病毒文件查殺代碼的時候,可以通過for語句來讀取病毒文件列表,那么,病毒文件列表.ini這個配置文件可以這樣寫:

;以下是常見的病毒文件,請見一個殺一個
;copyleft:沒有
qq.exe
msn.exe
iexplore.exe

如果要取消這個默認設置,可選擇的辦法是:
  1、為eol=指定另外一個字符;
  2、使用 for /f "eol=" 語句,也就是說,強制指定字符為空,就像對付delims=一樣。

 

(6)如何決定該使用 for /f 的哪種句式?(兼談usebackq的使用)

for /f %%i in (……) do (……) 語句有好幾種變形語句,不同之處在於第一個括號里的內容:有的是用單引號括起來,有的是用雙引號包住,有的不用任何符號包裹,具體格式為:

1、for /f %%i in (文件名) do (……)
2、for /f %%i in ('命令語句') do (……)
3、for /f %%i in ("字符串") do (……)

看到這里,我想很多人可能已經開始犯了迷糊了:如果要解決一個具體問題,面對這么多的選擇,如何決定該使用哪一條呢?

實際上,當我在上面羅列這些語句的時候,已經有所提示了,不知道你是否注意到了。

如果你一時無法參透其中奧妙,那也無妨,請聽我一一道來便是。

    1、當你希望讀取文本文件中的內容的話,第一個括號中不用任何符號包裹,應該使用的是第1條語句;例如:你想顯示test.txt中的內容,那么,就使用 for /f %%i in (test.txt) do echo %%i;
  2、當你讀取的是命令語句執行結果中的內容的話,第一個括號中的命令語句必須使用單引號包裹,應該使用的是第2條語句;例如:你想顯示當前目錄下文件名中含有test字符串的文本文件的時候,應該使用 for /f %%i in ('dir /a-d /b *test*.txt') do echo %%i 這樣的語句;
  3、當你要處理的是一個字符串的時候,第一個括號中的內容必須用雙引號括起來, 應該是用的是第3條語句;例如:當你想把bbs.bathome.net這串字符中的點號換為短橫線並顯示出來的話,可以使用 for /f "delims=. tokens=1-3" %%i in ("bbs.bathome.net") do echo %%i-%%j-%%k 這樣的語句。

很顯然,第一個括號里是否需要用符號包裹起來,以及使用什么樣的符號包裹,取決於要處理的對象屬於什么類型:如果是文件,則無需包裹;如果是命令語句,則用單引號包裹;如果是字符串,則使用雙引號括起來。

當然,事情並不是絕對如此,如果細心的你想到了批處理中難纏的特殊字符,你肯定會頭大如斗。

或許你頭腦中靈光一閃,已經想到了一個十分頭痛的問題:在第1條語句中,如果文件名中含有空格或&,該怎么辦?

照舊嗎?

拿個叫 test 1.txt 的文件來試試。

你很快寫好了代碼,新建文件-->碼字-->保存為批處理,前后費時不到1分鍾:
[code17]

@echo off
for /f %%i in (test 1.txt) do echo %%i
pause

你興沖沖地雙擊批處理,運行后,屏幕上出現了可恥的報錯信息:系統找不到文件 test 。

當你把 test 1.txt 換成 test&1.txt 后,更怪異的事情發生了:CMD窗口在你眼前一閃而過,然后,優雅地消失了。

你可能覺得自己的代碼寫錯了某些符號,你再仔細的檢查了一次,確認沒有筆誤,然后,你再次雙擊批處理,結果問題照舊;你開始懷疑其他程序對它可能有影響,於是關掉其他窗口,再運行了一次,問題依舊;你不服氣地連續運行了好幾次,還是同樣的結果。

怪哉!

你一拍大腿,猛然想起了一件事:當路徑中含有特殊字符的時候,應該使用引號把路徑括起來。對,就是它了!

但是,當你把代碼寫出來之后,你很快就焉了:for /f %%i in ("test 1.txt") do echo %%i,這不就是上面提到的第3條 for /f 命令的格式嗎?批處理會把 test 1.txt 這個文件名識別為字符串啊!

你百無聊賴地在CMD窗口中輸入 for /? ,並重重地敲下了回車,漫無目的地在幫助信息中尋找,希望能找到點什么。

結果還真讓你到了點什么。

你看到了這樣的描述:

usebackq     - 指定新語法已在下類情況中使用:
               在作為命令執行一個后引號的字符串並且一個單引號字符為文字字符串命令並允許在 filenameset 中使用雙引號擴起文件名稱。

但是,通讀一遍之后,你卻如墜五里霧中,不知所雲。

還好,下面有個例子,並配有簡單的說明:

FOR /F "usebackq delims==" %i IN (`set`) DO @echo %i
會枚舉當前環境中的環境變量名稱。

你仔細對比了for /f語句使用usebackq和不使用usebackq時在寫法上的差別,很快就找到了答案:當使用了usebackq之后,如果第一個括號中是一條命令語句,那么,就要把單引號'改成后引號`(鍵盤左上角esc鍵下面的那個按鍵,與~在同一鍵位上)。

回過頭去再看那段關於usebackq的描述,字斟句酌,反復揣摩,終於被你破譯了天機:usebackq 是一個增強型參數,當使用了這個參數之后,原來的for語句中第一個括號內的寫法要做如下變動:如果第一個括號里的對象是一條命令語句的話,原來的單引號 '要改為后引號`;如果第一個括號里的對象是字符串的話,原來的雙引號"要改為單引號';如果第一個括號里的對象是文件名的話,要用雙引號"括起來。

驗證一下,把[code17]改寫成如下代碼:
[code18]

@echo off
for /f "usebackq" %%i in ("test 1.txt") do echo %%i
pause

測試通過!

此時,你很可能會仰天長嘆:Shit,微軟這該死的機器翻譯!

至於把[code17]代碼中的空格換成&后,CMD窗口會直接退出,那是因為&是復合語句的連接符,CMD在預處理的時候,會優 先把&前后兩部分作為兩條語句來解析,而不是大家想象中的一條完整的for語句,從而產生了嚴重的語法錯誤。因為牽涉到預處理機制問題,不屬於本 節要討論的內容,在此不做詳細講解。

這個時候,我們會吃驚地發現,區區一條for語句,竟然有多達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發展而來,他們有這樣的對應關系:1-->4、2-->5、3-->6。

好在后3種情形並不常用,所以,牢牢掌握好前三種句型的適用情形就可以了,否則,要在這么多句型中確定選擇哪一條語句來使用,還真有點讓人頭腦發懵。

至於 for /f 為什么要增加usebacq參數,我只為第4條語句找到了合理的解釋:為了兼容文件名中所帶的空格或&。它在第5、6條語句中為什么還有存在的必 要,我也不是很明白,這有待於各位去慢慢發現。(注:這種解釋雖然有點不靠譜,但也算一種解釋,大家將就看看吧。啟用usebackq選項的時候,“文件 名”取代了“字符串”,那么“字符串”只好改變為“命令語句”,“命令語句”只好用后引號重新表示——簡而言之,是“文件名”符號改變引起的蝴蝶效應。言 外之意:usebackq除了在處理帶空格的文件名時會用到外,根本就沒有其它的出場機會和存在價值。)

 

四、翻箱倒櫃遍歷文件夾:for /r

(一)for /r 的作用及用法

按照幫助信息里文縐縐的說法,for /r 的作用是“遞歸”,我們換一個通俗一點的,叫“遍歷文件夾”,它會遍歷指定目錄和子目錄下的所有文件和文件夾。

更詳細的解釋就是:在下面的語句中,如果“元素集合”中只是一個點號,那么,這條語句的作用就是:列舉“目錄”及其之下的所有子目錄,對這些文件夾都 執行“命令語句集合”中的命令語句。其作用與嵌套進 for /f 復合語句的 "dir /ad /b /s 路徑" 功能類似。如果省略了“目錄”,將在當前目錄下執行前面描述的操作。

for /r 目錄 %%i in (元素集合) do 命令語句集合

先來個代碼增強一下印象:
[code21]

@echo off
for /r d:\test %%i in (.) do echo %%i
pause

執行的結果如下所示:

d:\test\.
d:\test\1\.
d:\test\2\.
d:\test\3\.

效果就是顯示 d:\test 目錄及其之下是所有子目錄的路徑,其效果與 dir /ad /b /s d:\test 類似。若要說到兩者的區別,可以歸納出3點:
 1、for /r 列舉出來的路徑最后都帶有斜杠和點號,而 dir 語句則沒有,會對獲取到的路徑進行進一步加工產生影響;
 2、for /r 不能列舉帶隱藏屬性的目錄,而 dir 語句則可以通過指定 /a 后面緊跟的參數來獲取帶指定屬性的目錄,更加靈活;
 3、若要對獲取到的路徑進行進一步處理,則需要把 dir 語句放入 for /f 語句中進行分析,寫成 for /f %%i in ('dir /ad /b /s') do …… 的形式;由於 for /r 語句是邊列舉路徑邊進行處理,所以,在處理大量路徑的時候,前期不會感到有停頓,而 for /f 語句則需要等到 dir /ad /b /s 語句把所有路徑都列舉完之后,再讀入內存進行處理,所以,在處理大量路徑的時候,前期會感到有明顯的停頓。

第2點差別很容易被大家忽視,導致用 for /r 列舉路徑的時候會造成遺漏;而第3點則會讓大家有更直觀的感受,很容易感覺到兩者之間的差別。

要是“元素集合”不是點號呢?那又如何?

來看看這個代碼:
[code22]

@echo off
for /r d:\test %%i in (a b c) do echo %%i
pause

運行的結果是:

復制代碼
D:\test\1\a
D:\test\1\b
D:\test\1\c
D:\test\2\a
D:\test\2\b
D:\test\2\c
D:\test\3\a
D:\test\3\b
D:\test\3\c
復制代碼

原來,它的含義是:列舉 d:\test 及其所有的子目錄,對所有的目錄路徑都分別添加a、b、c之后再顯示出來。

再來看一個代碼:
[code23]

@echo off
for /r d:\test %%i in (*.txt) do echo %%i
pause

運行結果是:

復制代碼
D:\test\test.txt
D:\test\1\1.txt
D:\test\1\2.txt
D:\test\2\a.txt
D:\test\2\b.txt
D:\test\3\1.txt
復制代碼

這段代碼的含義是:列舉 d:\test 及其所有子目錄下的txt文本文件(以.txt結尾的文件夾不會被列出來)。

我們再回過頭來歸納一下這個語句的作用:

for /r 目錄 %%i in (元素集合) do 命令語句集合

上面語句的作用是:

  1、列舉“目錄”及該目錄路徑下所有子目錄,並把列舉出來的目錄路徑和元素集合中的每一個元素拼接成形如“目錄路徑\元素”格式的新字符串,然后,對每一條這樣的新字符串執行“命令語句集合”中的每一條命令;
  特別的是:當“元素集合”帶以點號分隔的通配符?或*的時候,把“元素集合”視為文件(不視為文件夾),整條語句的作用是匹配“目錄”所指文件夾及其所有子文件夾下匹配的文件;若不以點號分隔,則把“元素集合”視為文件夾(不視為文件);
 2、當省略掉“目錄”時,則針對當前目錄;
 3、當元素集合中僅僅是一個點號的時候,將只列舉目錄路徑;

(二)for /r 還是 dir /ad /b /s?列舉目錄時該如何選擇

前面已經說過,當列舉目錄時,for /r 和 dir /ad /b /s 的效果是非常類似的,這就產生了一個問題:當我要獲取目錄路徑並進行進一步處理的時候,兩者之間,我該如何選擇?

這個問題,前面其實已經有過一些討論,現在我們再來作詳細的分析。

我們來看一下兩者各自的優缺點:

1、for /r:

  1)優點:

    ① 只通過1條語句就可以同時實現獲取目錄路徑和處理目錄路徑的操作;
  ② 遍歷文件夾的時候,是邊列舉邊處理的,獲取到一條路徑就處理一條路徑,內存占用小,處理大量路徑的時候不會產生停頓感;

  2)缺點:

    ① 不能獲取到帶隱藏屬性的目錄,會產生遺漏;
  ② 不能獲取帶指定屬性的目錄

2、dir /ad /s:

  1)優點:

    ① 能一次性獲取帶任意屬性的目錄,不會產生遺漏;
  ② 能通過指定不同的參數獲取帶任意屬性的目錄,更具靈活性。

  2)缺點:

    ① dir /ad /s 語句僅能獲取到目錄路徑,若要實現進一步的處理,還需要嵌入 for /f 語句中才能實現,寫法不夠簡潔;
  ② 嵌入 for /f 語句之后,需要寫成 for /f "delims=" %%i in ('dir /ad /b /s') do …… 的格式,受 for /f 語句運行機制的制約,需要先列舉完所有的路徑放入內存之后,才能對每一條路徑進行進一步的處理,處理大量路徑時,內存占用量偏大,並且在前期會產生明顯的 停頓感,用戶體驗度不夠好;

綜合上述分析,可以做出如下選擇:
    1、若僅僅是為了獲取某文件夾及其所有子文件夾的路徑的話,請選擇 dir /ad /b /s 語句;
  2、若需要過濾帶隱藏屬性的文件夾的話,for /r 和 dir 語句都可以實現,但 for /r 內存占用小,處理速度快,是上上之選;
  3、若需要獲取所有文件夾,則除了 dir /ad /b /s 外,別無選擇,因為 for /r 語句會遺漏帶隱藏屬性的文件夾;

在實際的使用中,我更喜歡使用 for /f 和 dir 的組合,因為它不會產生遺漏,並能給我帶來更靈活的處理方式,唯一需要忍受的,就是它在處理大量路徑時前期的停頓感,以及在這背后稍微有點偏高的內存占 用;在我追求速度且可以忽略帶隱藏屬性的目錄的時候,我會換用 for /r 的方案,不過這樣的情形不多——有誰會願意為了追求速度而容忍遺漏呢?

 

五、僅僅為了匹配第一層目錄而存在:for /d

for /d 中 /d ,完整的含義是 /directory,本意是為了處理文件夾,它的完整語句應該是這樣的:

for /d %%i in (元素集合) do 命令語句集合

當“元素集合”中包含有通配符?或*時,它會匹配文件夾,但是,相比 for /r 而言,這個時候的for /d,其作用就小得可憐了:它僅能匹配當前目錄下的第一級文件夾,或是指定位置上的文件夾,而不能匹配更深層次的子文件夾。

例如:for /d %%i in (d:\test*) do echo %%i 這樣的語句 ,會匹配到形如:d:\test、d:\test1、d:\test2之類的文件夾,若不存在這樣的路徑,將不會有任何回顯。

當“元素集合”中不包含任何的通配符時,它的作用和 "for %%i in (元素集合) do 命令語句集合" 這樣的語句別無二致。

因此,for /d 的角色就變得很微妙了:當“元素集合”中包含通配符?或*時,它的作用就是匹配文件夾,此時,它僅能匹配當前目錄下的第一級文件夾,或是指定位置上的文件夾,在層次深度上不及 for /r,但和 for /r 一樣的壞脾氣:不能匹配帶隱藏屬性的文件夾;在靈活性上不及for /f和dir的組合;當“元素集合”中不包含任何統配符的時候,它完全是 "for %%i in (元素集合) do ……" 語句的翻版,但是又稍顯復雜。

for /d 的作用是如此有限,我使用的次數是如此之少,以至於我一度找不到它的用武之地,認為它食之無味,棄之可惜,完全是雞肋一塊。

某年某月,我在cmd窗口里寫下了這樣的代碼:
[code24]

for /d %i in (test*) do @echo %i

我的本意是想查看在我的臨時目錄下,長年累月的測試工作到底建立了多少測試文件夾,以便我隨后把echo換成rd刪除之。這個時候,我發現這條代碼 是如此 的簡潔,是 for /r 或 for和 dir /ad /b 的組合所無法替代的(echo換成rd就可以直接刪除掉這些測試目錄)。

簡潔的代碼給我帶來的喜悅僅僅持續了短短10幾秒的時間,我便開始了迷惘——能用到for /d的類似情形,貌似少之又少且乏善可陳啊。

(注:正如qzwqzw所言,for /r /d是可以一起使用的;【在for有限的4個參數中,據我所知只有/r /d可以一起使用】。
例如:

@echo off
For /r /d %%i in (*) do echo %%i
pause>nul

效果:
顯示當前目錄下所有的文件夾【包括子文件夾】;等價於 "dir /ad /s /b"。

for /r /d 其實是對 /d 參數的擴展,/d參數本身只能處理第一層文件夾,但是加上/r參數后就可以處理所有的子文件夾;

for /r /d依然不能處理隱藏文件夾。
這里給出使用for /r /d的一般條件:
1.要對文件夾進行操作(dir /ad /s /b可以顯示,但不能對文件夾進行操作);
2.不處理隱藏文件夾(說到底,還是for /f 和dir結合的命令更強大些)。

 

六、計數循環:for /l

/l 者,/loop的縮寫是也,從鳥語翻譯過來,loop就是循環的意思。實際上,所有的for語句,都可以看成是一種“循環”,只是在/l中,特指按照指定次數進行循環罷了。

for /l 語句的完整格式是這樣的:

for /l %%i in (x,y,z) do (……)

在這個語句中,x、y和z都只能取整數,正負皆可,x指代起始值,y指代步長,z為終止值,具體含義為:從x開始計數,以y為步長,直至最接近 z的那個整數值為止,這之間有多少個數,do后的語句就執行多少次。

舉個具體的例子:
[code25]

for /l %%i in (1,2,10) do echo bathome

在以上的代碼中,初始值是1,步長為2,終止值為10,表明計數從1開始,每隔2個數計算一次,直至最接近10的那個整數,羅列出來,就是1,3,5,7,9,再下一個就是11,超過10了,不再計算在內,所以,do后的語句只執行5次,將連續顯示5個bathome。

實際上,x,y和z的值可正可負,甚至為0,限制非常寬松:
 1、步長y的值不能為0;
 2、當步長y的值為正整數時,終止值z不能小於初始值x;
 3、當步長y的值為負整數的時候,終止值z不能大於初始值x。

換而言之,必須保證in和do之間能取到一個有效的數組序列。

例如:
[code26]

for /l %%i in (-1,2,5) do echo bathome

[code27]

for /l %%i in (5,-2,-1) do echo bathome

以上兩條代碼的功能完全一樣,都將顯示4次bathome,區別就在於[code26]是正序計算,而[code27]是逆序計數而已。

以下幾條代碼都是有問題的:
[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的限制,將陷入無限循環中;[code29]和[code30]都犯了同樣的錯誤:無法獲得有效的數列元素,導致in和do之間取到的值為空元素,從而使得整條for語句無從執行。

當大家明白了 for /l 的具體功能之后,是否會想到了與它有異曲同工之妙的goto循環語句呢?似乎,for /l 和 goto 循環語句可以相互替換?

一般而言,for /l語句可以換成goto循環,但是,goto循環並不一定能被 for /l 語句替換掉。具體原因,請大家仔細想想,我在此不再詳細解說,只是就大家非常關心的一個問題提供一個簡潔的答案,那就是:什么時候該用 for /l 計數循環,而什么時候又該用goto條件循環?

答案非常簡單:當循環次數確定的時候,首選 for /l 語句,也可使用goto語句但不推薦;當循環次數不確定的時候,用goto語句將是唯一的選擇,因為,這個時候需要用if之類的條件語句來判斷何時結束goto跳轉。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM