天天看點

黑客必修9:批處理進階語句——for循環語句(2)

作者:魚躍刺桐

(三) 定點提取: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]

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    

(四) 跳過無關内容,直奔主題: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中的資訊。

(五) 忽略以指定字元打頭的行: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 語句是預設忽略以分号打頭的行内容的,正如它預設以空格鍵或跳格鍵作為字元串的切分字元一樣。

  很多時候,我們可以充分利用這個特點,比如,在設計即将用for讀取的配置檔案的時候,可以在注釋文字的行

首加上分号,例如在編寫病毒檔案清除代碼的時候,可以通過for語句來讀取病毒檔案清單,那麼,病毒檔案清單.ini

這個配置檔案可以這樣寫:

;以下是常見的病毒檔案,請見一個殺一個^_^

;copyleft:沒有

qq.exe

msn.exe

iexplore.exe  

如果要取消這個預設設定,可選擇的辦法是:

  1、為eol=指定另外一個字元;

  2、使用 for /f "eol=" 語句,也就是說,強制指定字元為空,就像對付delims=一樣。

(六)如何決定該使用 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.cn這串字元中的點号換為短橫線并顯示出來的話,可以使用

for /f "delims=. tokens=1-3" %%i in ("bbs.bathome.cn") 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  

測試通過!

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

  至于把[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條語句中為什麼還有存在的必要,我也不是很明白,這有待于各位去慢慢發現。