有一堆的文件,名字是這樣的:[abcd]2008.01.01.測試.txt,打算將收尾去掉,改成這個樣子:2008.01.01.txt。在linux下應該很簡單,大不了寫個腳本就搞定了,不過我想在windows下搞定。一直想研究一下PowerShell,這回打算用這個工具實現我的目的。
誰知折騰了兩天,居然還沒搞定,倒是PowerShell略微入門了一些。感覺上PowerShell確實強大,跟通常意義上的腳本有很大區別。最根本之處在於其所有Cmdlet的輸出都是對象,這個概念剛開始恐怕不好理解,不過深入了解後就會發現其靈活之處。不象其他腳本的命令輸出是字符串,管道之後的命令只能處理前面輸出的字符串,PowerShell的Cmdlet輸出的是對象,管道後面可以充分利用對象的豐富信息來工作。比如說dir(其實就是get-childitem命令),輸出的是一堆的文件或者目錄的對象,文件的話是System.IO.FileInfo對象,目錄的話是 System.IO.DirectoryInfo對象,這些都是標准的.NET對象,所以可以使用這些對象的屬性和方法,如果對.NET/C#編程熟悉的話就更有優勢了。舉個例子來說,如果想列出當前目錄的只讀文件,可以這樣:
dir|where-object{$_.IsReadOnly}
列出目錄是這樣:
dir|where-object{$_.PSIsContainer}
如果想知道目錄下有多少文件
get-childitem|where-object{$_.PSIsContainer}|foreach-object{"$_.fullname: " + $_.getfiles().length}
前面說了,PowerShell的輸出一切皆對象,而Get-Member可以知道輸出的每一個項是什麼對象以及對象有什麼屬性和方法。
話說回來,PowerShell也有讓人費解的地方,跟其他微軟產品似乎風格不同,PowerShell跟有點像*NIX的東西,很多讓人眼花缭亂的縮寫跟Windows SDK的長長的函數名成鮮明對比,象ls、cp這些*NIX下的命令居然也可以用,當然只不過是別名而已,讓人瞠舌的是幾乎每個內置的命令都有別名,有的還有不止一個的別名,比如Get-ChildItem的別名就有gci、ls、dir三個,而?是Where-Object的別名,%是Foreach-Object的別名,這些恐怕就會讓初學者相當迷惑了。來看前面的例子,取當前目錄的每個子目錄下的文件數,如果改成簡單的方式,就是下面這樣子:
ls|?{$_.PSIsContainer}|%{$_.fullname: " + $_.getfiles().length}
看上去是不是跟Perl有的一拼了?再看下面的例子,又有點awk的味道,這是計算所有子目錄的文件數(當然也許有更簡單的辦法,這只是為了示例):
ls|?{$_.PSIsContainer}|%{$count=0}{$count+=$_.getFiles().length}{"Total Files:" + $count}
搞出這個例子之後我似乎對PowerShell又有所領悟,有點佩服微軟了,PowerShell確實是一個強大的腳本語言。
好,閒話少說,最開始的要求還沒有解決呢。開始我用的rename-item,誰知對方括號不太感冒,折騰了半天也沒搞定,總是報這樣的錯誤:
PS F:/temp> Rename-Item 'F:/temp/`[10`]' 'F:/temp/10'
Rename-Item : 指定路徑 F:/temp/`[10`] 下的對象不存在。
所在位置 行:1 字符: 12
+ Rename-Item <<<< 'F:/temp/`[10`]' 'F:/temp/10'
直到某一刻網上搜到可以用move-item,才最後搞定,各種酸辛就不道了,總之這是rename-item的一個bug,不支持方括號,只能用move-item。倒~
最後的結果是
ls *.avi|%{ if($_.fullname -match '(.*)/[.*/](/d/d/d/d/./d/d/./d/d).*avi'){mv -literalpath $_.fullna
me "$($matches[1]+$matches[2]).avi" }}
還是那句話,實踐是學習知識的最好途徑,特別是碰到bug的時候。似乎偶下次面試的時候也可以寫上“精通PowerShell”了啊,吼吼。