需求
在Windows系統(tǒng)中主儡,以命令行方式奖唯,找出當(dāng)前目錄下所有拓展名不是.md
、.js
糜值、.bat
的文件丰捷。
解決方法一
> dir /a-d /b | findstr /v ".*\.bat$ .*\.js$ .*\.md$"
分析
dir命令
首先使用dir
命令,找到當(dāng)前目錄下所有文件:
> dir /a-d /b
命令行中使用> help dir
可以查看該命令的參數(shù)詳解寂汇。
對上面這個命令病往,/a
表示 顯示具有指定屬性(attribute)的文件,其后緊跟屬性:-
表示“否”骄瓣,而d
表示目錄文件(即文件夾)停巷。所以/a-d
表示非文件夾的文件。
/b
表示使用空格式(沒有標(biāo)題信息或摘要)榕栏。
C:\Users\Berlin\Desktop\demo>dir /a-d
驅(qū)動器 C 中的卷沒有標(biāo)簽畔勤。
卷的序列號是 F2B9-76C6
C:\Users\Berlin\Desktop\demo 的目錄
2018/04/16 11:21 263 override.ts
2018/04/14 10:37 525,295 package-lock.json
2018/04/14 10:37 1,328 package.json
.....
16 個文件 536,101 字節(jié)
0 個目錄 83,691,253,760 可用字節(jié)
C:\Users\Berlin\Desktop\demo>dir /a-d /b
override.ts
package-lock.json
package.json
...
findstr命令
基本用法就是findstr <模板字符串> <待查找字符串>
。這里模板字符串默認(rèn)是使用正則表達(dá)式的扒磁,可以使用通配符等庆揪,但是不能用圓括號的捕獲組。
例如為了匹配以.md
渗磅、.js
嚷硫、.bat
為拓展名的文件,以JS風(fēng)格的正則表達(dá)式可能會寫成:.*\.(md|js|bat)$
始鱼,注意第二個點字符要轉(zhuǎn)義仔掸,第一個點字符表通配符。
然而在這里將無法使用上述風(fēng)格的正則表達(dá)式医清,為了能夠匹配起暮,我們需要寫成
".*\.bat$ .*\.js$ .*\.md$"
即一個字符串,使用空格作為分隔,那么它就會匹配以.md
负懦、.js
筒捺、.bat
為拓展名的文件。
假設(shè)有一個叫temp file.txt
和another file.txt
的文件纸厉,而只想匹配前者系吭,則表達(dá)式"^te.* e\.txt$"
會把兩個文件都匹配到,而我們的初衷可能是希望匹配 以te開頭颗品、后跟任意多個字符肯尺、加一個空格、以e.txt 結(jié)尾的文件躯枢,anthore file.txt
不符合這個規(guī)則则吟,不應(yīng)該被匹配到。
實際上锄蹂,因為這個表達(dá)式有空格氓仲,所以實際上會被拆分為兩個表達(dá)式:^te.*
和e\.txt$
,顯然后者表達(dá)式可以把another file.txt
匹配到得糜。
所以為了只匹配temp file.txt
敬扛,需要寫成^te.*e\.txt
,即點通配符也包含了空格朝抖。
另一種需求是舔哪,如果想把引號中的所有字符解釋為一個整體,那么可以使用/c:
選項槽棍,例如,/c:".txt .bat"
抬驴,這意味著檢索時將".txt .bat"看作是整體炼七,并且點字符就是點字符,不再具有正則表達(dá)式的通配符功效布持。也就是說豌拙,/c:
選項后跟的字符串是一個普通字符串,并且是一個整體不分割题暖。
/c:
選項可以使用多個按傅。例如,想檢索包含.js
和.md
的文件名胧卤,可以使用命令dir /a-d /b | findstr /c:".js" /c:".md"
完成唯绍。但問題在于,由于他解釋為普通字符串而不是正則表達(dá)式枝誊,所以類似package.json
的文件也會被匹配到况芒,而我們本意是找到以它們結(jié)尾。此時$
符也無法使用了叶撒。
最后绝骚,findstr
命令有一個選項是/v
耐版,它表示“只打印不包含匹配的行⊙雇簦”粪牲。而這個功能正是我們需要的。
綜上所述止剖,使用dir
和findstr
腺阳,利用管道,我們在命令行中完成了需求滴须。
解決方案二
這里使用bat文件來完成舌狗,并且使用另一種思路,即用FOR循環(huán)和IF語句完成扔水。重點是理解setlocal enableDelayedExpansion
的用途痛侍。
@echo off
setlocal enableDelayedExpansion
for %%I in (*.*) do (
set /a res=0
if "%%~xI" == ".md" set /a res+=1
if "%%~xI" == ".js" set /a res+=1
if "%%I" == "%~nx0" set /a res+=1
if !res! equ 0 echo %%I
)
endlocal
分析
注意循環(huán)變量I
的寫法:在命令行中是%I
,而在批處理文件中是%%I
魔市。并且在這里主届,循環(huán)變量只能是一個大寫或小寫字母組成。
使用FOR循環(huán)待德,在當(dāng)前目錄下所有的文件中(*.*
)中遍歷君丁。每次循環(huán)時,設(shè)置一個變量res
并初始化為0.
set
的/a
選項表示將等號右邊的字符解釋為數(shù)學(xué)表達(dá)式将宪。如果沒有/a
绘闷,那么就會把字符0賦給res
。使用/a
選項可以完成后續(xù)的+=
等數(shù)學(xué)運(yùn)算较坛。具體使用help set
查看幫助印蔗。
如果當(dāng)前文件的拓展名等于.md
,則res
自增1丑勤。
~x
表示顯示拓展名华嘹,~n
表示文件名,~nx
表示文件名和拓展名法竞。例如耙厚,對于遍歷到package.json
時,相關(guān)輸出如下:
echo %%I => package.json
echo %%~xI => .json
echo %%~nI => package
echo %%~nxI => package.json
而
%0
表示批處理文件本身(即其值為字符串"foo.bat"
)
做字符串比較時岔霸,例如
"%%~xI" == ".md"
薛躬,要把變量I
放到雙引號中,否則可能無法比較
當(dāng)三個if都完成后呆细,如果拓展名不是.md
泛豪、.js
、.bat
,那么res
的值就為0诡曙,則輸出臀叙。
啟用延遲環(huán)境變量擴(kuò)展
語句
setlocal enableDelayedExpansion
將啟用延遲環(huán)境變量擴(kuò)展。為什么要這樣做呢价卤?
詳細(xì)講解見參考資料[1][3]劝萤。簡單來說,批處理程序是逐條執(zhí)行批處理腳本的慎璧,它會先讀入一條語句床嫌,然后對該語句里的變量作替換,然后開始解釋這個語句胸私。
例如:
set a=4
set a=5&echo %a%
echo %a%
- 第一條語句執(zhí)行完后厌处,局部變量
a
的值為4。 - 接下來岁疼,處理程序先讀入第二條語句(
set a=5&echo %a%
)阔涉,然后對變量進(jìn)行替換,此后就變成了set a=5&echo 4
捷绒,替換完成后才開始執(zhí)行這個語句瑰排,因此最終結(jié)果是輸出4而不是5. - 第二條執(zhí)行完后,變量
a
變成了5暖侨,所以第三句輸出5
這里的關(guān)鍵在于椭住,語句執(zhí)行之前,變量被替換為當(dāng)前的值字逗。因此在本條語句執(zhí)行過程中京郑,該變量的變化是不會被檢測到的(它會在之后的語句中生效,例如例子中的第三句)
為了能使得局部變量的變化能被檢測到葫掉,那么在執(zhí)行之前就不能對其進(jìn)行值替換傻挂,也就是延遲賦值。這就是為什么要使用setlocal enableDelayedExpansion
的原因:
setlocal enableDelayedExpansion
set a=4
set a=5&echo !a!
echo %a%
上面的批處理運(yùn)行結(jié)果是輸出兩個5. 我們注意到第三句中的變量a
使用感嘆號進(jìn)行取值而不是百分號挖息。
由于啟動了變量延遲,所以批處理能夠感知到動態(tài)變化兽肤,即不是先給該行變量賦值套腹,而是在運(yùn)行過程中給變量賦值,因此此時a的值就是5了
啟用延遲賦值后资铡,應(yīng)該在適當(dāng)?shù)胤绞褂?code>endlocal命令進(jìn)行關(guān)閉电禀。
沒有使用
endlocal
語句停用延遲環(huán)境變量擴(kuò)展功能,則MS-DOS解釋器會在程序的末尾自動調(diào)用endlocal
命令重置MS-DOS環(huán)境默認(rèn)值笤休。
回到本方案代碼中去尖飞,在最后一句IF判斷中,使用了
if !res! equ 0 echo %%I
即res
使用了延遲賦值(兩個感嘆號)。因此在之前的IF中如果res
發(fā)生了變化政基,則此處也會被檢測到并使用贞铣。
作為一個反例,我們來看看下面的代碼:
@ECHO OFF
set res=Bonjour
for %%I in (A B C) do (
set /a res=99
echo %res%
)
在FOR循環(huán)開始之前對局部變量res賦值為Bonjour(因為沒有使用/a
沮明,故認(rèn)為是字符串)辕坝。在FOR循環(huán)中,每一輪都將res
賦值為數(shù)值99荐健,我們可能認(rèn)為會連續(xù)輸出三次99酱畅,但事實是,連續(xù)輸出三次字符串“Bonjour”:
C:\Users\Berlin\Desktop\demo>set_local.bat
Bonjour
Bonjour
Bonjour
這是因為江场,F(xiàn)OR循環(huán)的循環(huán)體使用括號括起來的纺酸,因此整個循環(huán)體語句實際上是一條語句!址否!餐蔬。因此在執(zhí)行FOR循環(huán)之前res
已經(jīng)是“Bonjour”了,在執(zhí)行FOR的時候在张,自然會做變量替換用含,因此它等價于下面的程序
@ECHO OFF
set res=Bonjour
for %%I in (A B C) do (
set /a res=99
echo Bonjour
)
解決辦法:
@ECHO OFF
setlocal enableDelayedExpansion
set res=Bonjour
for %%I in (A B C) do (
set /a res=99
echo !res!
)
endlocal
輸出:
C:\Users\Berlin\Desktop\demo>set_local.bat
99
99
99
【要點】:
- 使用
setlocal enableDelayedExpansion
開啟延遲賦值 - 局部變量使用
!foo!
進(jìn)行引用,而不是%foo%