1. 程式人生 > >PowerShell讀取不同資料格式並生成自己資料

PowerShell讀取不同資料格式並生成自己資料

【賽迪網報道】由程式生成的資料通常會比生成它的程式有更長的存活期,文字檔案能夠很容易地從一個系統傳輸到另一個系統。本文將探討PowerShell操作檔案的機制、如何讀取不同的資料格式並生成自己的資料,以及如何使用正則表示式從文字塊中獲取資料。

1 讀取內容

在PowerShell中,Get-Content和Set-Content這兩個cmdlet分別用於獲取和設定原始二進位制檔案。預設情況下,這兩個命令用於操作文字檔案。圖1所示為使用Get-Content獲取檔案內容。

Get-Content以行為單位返回一個字串陣列,每個陣列元素中包含一行內容。圖2所示為返回5個元素的字串陣列的檔案內容。

從圖中可以看到,返回值為陣列形式。如果操作的檔案內容為每行一個條目存在,則使得記錄操作非常方便。

如果需要獲取整個檔案內容並轉換為一個獨立的字串形式,則使用[string]::Join()靜態方法。這個方法將字串陣列用分隔符連線為單個字串,這裡的分隔符為換行符。不同系統會使用不同的字元來作為換行符,如Windows使用回車換行符(在PowerShell中使用`r`n作為轉義字元),在Unix系統中使用換行符(`r),蘋果機上使用回車符(`n)。當前PowerShell僅應用在Windows系統中,可以使用`r`n作為換行的分隔符。但是最好從系統中獲取分隔符,如果將來PowerShell在其他作業系統中有相應的解釋引擎,則現在編寫的所有指令碼均可在其中無障礙地應用。從.NET環境中獲取換行符需要訪問靜態屬性[System.Envoironment]::NewLine。

下面建立一個通用指令碼Get-ContentAsString.ps1,以字串形式讀取檔案內容:

Param($Path)

$lines = Get-Content -Path $Path
$newLine = [System.Environment]::NewLine

$content = [String]::Join($newLine,$lines)
$content

圖3所示為使用上述指令碼讀取infoOfPeople.txt檔案並檢查其中返回值的方法。

在讀取的檔案中包含52個字元,其中前7個字元用換行符分隔組成字串“Sun WuK”。

Get-Content能夠同時從多個檔案中獲取文字,當為Get-Content傳遞的物件是檔案集合時,則返回所有的檔案內容,內容是經過合併後的檔案內容。圖4所示為使用這個cmdlet獲取當前目錄中所有*.txt檔案的內容,在獲取之前列出目錄。可以看到當前目錄下有兩個txt檔案,分別是前面演示的例項檔案infoOfPeople.txt和test.txt。

也可以通過傳遞萬用字元給Get-Content讀取同樣的內容,如圖5所示。

由於前面建立的Get-ContentAsString.ps1是基於Get-Content的功能封裝的,所以它具有Get-Content的所有屬性,為其傳遞萬用字元給引數$Path的結果如圖6所示。

前面使用Get-Content獲取的是字串的集合,這裡返回的是單一的字串。為了驗證二者的不同,構造兩個如圖7所示的表示式。

2 寫入內容

Set-Content的-Value引數指定要寫入檔案的物件(另一個-Path引數指定儲存檔案的路徑),可以用管道輸出代替。如果指定的引數是物件,則會呼叫其中內建的ToString()方法轉換為字串儲存到檔案中;如果指定的是集合,則會列舉其中的所有成員並分別賦值給單獨的物件。圖8所示為將兩個檔案的內容寫入到一個檔案中。

能夠看到從dir命令獲取的FileInfo物件用其中內建的ToString()方法返回了檔案的全路徑。

需要注意的是Set-Content具有破壞性,如果要寫入的檔案已經存在,則將被新寫入的內容覆蓋。所以在使用Set-Content之前一定要確認目標路徑是否存在同名檔案,如果存在,則確認檔案是否覆蓋;否則將會丟失原有資料。

要在已有檔案尾新增新的內容,需要使用到Add-Content這個cmdlet,其引數與Set-Content相同。圖9所示為使用Add-Content在texts.txt尾新增新的內容。

上例中直接將dir命令的輸出用管道傳遞給Add-Content,相當於將dir命令的輸出賦值給-Value引數並執行Add-Content操作。

PowerShell支援兩種輸出重定向操作符,允許使用者將命令輸出儲存在檔案中。>操作符將會傳遞當前物件給檔案並覆蓋其原有檔案的內容,示例如圖10所示。

>>操作符的功能與>類似,但它在原有檔案尾新增新的內容,其示例如圖11所示。

能夠看到>和>>操作符的功能分別與Set-Content和Add-Content類似,不同在於重定向操作將輸入物件用管道傳遞給PowerShell的格式化機制,而Set-Content和Add-Content用其本身具有的ToString()方法。

3 指定編碼方式

計算機首選的文字格式如美國資訊交換標準碼(ASCII)只是適用於用單位元組標識字元,從而限制了能夠表達的字元數量。這種編碼只是用於標識拉丁字元,而對於其他字元,如古斯拉夫文字、希伯來文字、阿拉伯文字、中文、日文和韓文等則無能為力;這就是Unicode標準化組織要制定新的一套文字編碼標準來表示所有字符集的原因。

Get-Content和Set-Content這類cmdlet支援通過使用-Encoding引數指定編碼方式來讀寫檔案,該引數的取值如下。

(1)Unkonwn:未指定編碼方式,預設為Unicode編碼方式。

(2)String:內容作為Unicode字串處理,等同於Unicode編碼方式。

(3)Unicode:在.NET框架和PowerShell中將位元組順序(endianness)為低地址存放最低有效位元組的16位的Unicode編碼作為預設字串格式(這裡的位元組順序Little Endian取決於底層硬體架構採用Intel的x86系列CPU,如果採用Motorola的PowerPC系列CPU,則使用Big Endian方式,即低地址存放最高有效位元組。請參閱相關文件),本章後面將會詳細介紹不同位元組順序下的Unicode編碼方式。

(4)Byte:返回位元組陣列,適用於以二進位制形式讀寫一個檔案。

(5)BigEndianUnicode:類似Unicode,但是使用Big Endian位元組順序。

(6)UTF8:使用Unicode統一編碼標準的UTF-8編碼方式。

(7)UTF7:使用Unicode統一編碼標準的UTF-7編碼方式。

(8)Ascii:每字元使用8位ASCII編碼返回檔案內容,僅用於英文字元。

其中的常用值是Byte、Unicode、UTF8和Ascii。

3.1 獲取二進位制內容

操作檔案的有效方法是位元組陣列方式,這種方式允許使用者修改檔案中的任何的內容。圖12所示為ASCII方式寫字串序列到檔案中,然後以位元組方式讀出。

這裡將字串序列“aaaabbb”以特定編碼方式寫入到檔案中,當以位元組方式讀出檔案內容後顯示的內容分別是4個a和3個b的ASCII碼值,即添加了兩個原來並未寫入的ASCII值分別為10(回車)和13(換行)的字元。這兩個字元由Set-Content自動新增,以標識檔案結束。

【提示】

Set-Content自動新增換行符對於文字檔案的影響可能並不明顯,但是如果需要檔案內容無換行,則需要注意,唯一能夠避免Set-Content自動新增換行符的方法是使用位元組(Byte)編碼方式並以位元組陣列的形式賦值檔案內容。

當需要以十六進位制形式顯示位元組值時會經常操作位元組,另外所有關於二進位制資料檔案的操作均使用十六進位制處理。下面建立一個名為“Format-AsHex.ps1”的指令碼,使用-f字串格式化操作符格式化所有用管道傳遞的數字,程式碼如下:

process
{
		"{0:X}" -f $_
}

使用這個指令碼處理之後的ASCII資料如圖13所示。

可以看到十六進位制的a是61,b是62,回車符是D,換行符是A。

3.2 不同Unicode編碼

本節將使用不同編碼儲存檔案,然後使用工具來檢視二進位制資料,這樣即可看到.NET中不同編碼之間的關係。首先從.NET標準的Unicode編碼開始,儲存字串序列到檔案中並讀出,執行結果如圖14所示。

根據標準,Unicode格式的文字檔案始於由位元組順序唯一標識的位元組序列,稱為“位元組順序標誌”(Byte Order Mark,BOM)。BOM是必要的,因為有些系統使用兩個位元組表示一個數據,如字元“A”的碼值以0061的形式表示,如同正常的數學符號;另一種情況反轉了位元組的順序,將其表示為6100,這兩種表示方式分別稱為“Big Endian”和“Little Endian”。基於Intel系列CPU和.NET框架均使用後者,在.NET中稱為“採用Unicode編碼”。與此相對應的方式是Big Endian Unicode,檔案開始包含的BOM的內容是FFFE。

下面檢視相同序列如何採用Big Endian Unicode編碼方式,以及其中的如何組織位元組,如圖15所示。

BOM中包含FEFF,字元“A”被表示為0061。

Unicode和Big Endian Unicode編碼使用兩個位元組編碼每個字元,從而在表示英文字元時不可避免地浪費一個位元組。UTF-8的編碼使用一個位元組編碼一個英文字元,使用兩個或多個位元組編碼其他語言字元解決這個問題。圖16所示為採用UTF8編碼方式儲存檔案。

這樣縮短了檔案長度,在aaabb之前新增的3位稱為“UTF-8的BOM序列”。UTF-8格式不存在順序問題,但是需要指定BOM序列,這樣程式可以用其檢測文字檔案是否使用UTF-8編碼。

【提示】

UTF-8的BOM的3個符號曾在網頁和文字檔案中出現過,即。EFBBBF這個序列是個可印刷字元,不知道如何處理UTF-8編碼的程式通常將其顯示給使用者。

下面編寫一個名為“Detect-Encoding.ps1”的指令碼讀取檔案開始的前3個位元組,並與已知的BOM標誌值比較。如果匹配某種特徵,則輸出相應的編碼名;否則作為ASCII檔案來處理。其程式碼如下:

param ($Path)

$start = Get-Content -Path $Path -Encoding Byte -TotalCount 3

$utf8BOM = "{0:X}{1:X}{2:X}" -f $start
$utf16BOM = "{0:X}{1:X}" -f $start

if ($uft8BOM -eq "EFBBBF")
{
	Write-Host "UTF-8"
	exit
}
if ($uft16BOM -eq "FFFE")
{
	Write-Host "Unicode"
	exit
}
if ($uft16BOM -eq "FEFF")
{
	Write-Host "Big Endian Unicode"
	exit
}

Write-Host "No BOM detected. Encoding is most likely ASCII."

程式碼中為Get-Content傳遞了-TotalCount引數,以僅提取前3個位元組,然後將其格式化為十六進位制並檢測序列是否滿足UTF8的BOM。如果不滿足,則格式化前兩個位元組並分別與Unicode和Big Endian Unicode的BOM序列比較。圖17所示為檢測之前生成的文字檔案的編碼方式。

【提示】

Unicode協會的主頁地址是http://www.unicode.org/,其中包含完整的Unicode標準的規範。該標準針對主題分別定義,所以非常冗長,不易閱讀。言簡意賅且淺顯易懂地介紹文字編碼主題的文章可以在Joel Spolsky的文章“The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)”中看到,這篇文章可以在http://www.joelonsoftware.com/articles/Unicode.html網頁中找到。需要為讀者介紹的另外一種程式設計方面的技能就是要學會如何通過Internet找到前人已經總結的經驗和規律,這些經驗和規律可能淺顯易懂。但是如果由讀者自己來驗證,則有一定難度。可以在他人的基礎上學習,這樣才能以最小的代價最快地達成目標。

4 從文字中提取資料

一般情況下很難分割和提取文字檔案中包含的資料,這是因為其中包含一定數量的空格或其他分隔符。使用正則表示式可以快速定位到目標字串,並提出相應的內容。

【提示】

關於正則表示式的更多資訊可以訪問線上資源http://www.regular-expressions.inf,其中涵蓋了多種語言和環境中正則表示式的使用方法。有關.NET正則表示式內容的線上資源可訪問http://www.regular-expressions.info/dotnet.html。儘管正則表示式在各個平臺上或多或少有些相似,但是在不同的語言中有很多細微的差別,在使用正則表示式之前需要仔細核對表達式和語言種類是否匹配。

4.1 查詢符合正則表示式的匹配

通過指定\d+為正則表示式來描述一個數字,這意味著“一個或多個數字元號”。呼叫正則表示式的Matches()方法將返回一個匹配物件的集合,可以通過檢測每個匹配中的Success屬性來判斷匹配成功與否。如果成功,則可以通過第1個Group物件的Value屬性得到匹配成功的具體值。下面建立一個名為“Extract-Numbers.ps1”的指令碼,程式碼如下:

$nums = .\Get-ContentAsString.ps1 .\test_numbers.txt
$numberMatcher = [regex] "\d+"

$matches = $numberMatcher.Matches($nums)
foreach ($match in $matches)
{
	if ($match.Success)
	{
		$number = $match.Groups[0].Value
		Write-Host "number:$number"
	}
}

需要強調的是第1個組的下標從索引0開始,圖18所示為執行結果。

該指令碼中操作的文字檔案只要包含數字,即可過濾。

4.2 查詢檔案中的字串

PowerShell中的Select-String是一個內建cmdlet,用其可以查詢檔案中的字串或滿足特定正則表示式的記錄。熟悉Unix的讀者可以使用grep的工具,在Windows下則可以使用findstr工具。

Select-String的引數-Pattern用於指定要查詢的字串或滿足特定正則表示式的記錄,-Path引數指定要查詢的檔名或萬用字元。為了驗證這個cmdlet的功能,搜尋當前目錄中所有包含“if”字串的指令碼檔案,如圖19所示。

獲取的結果是一個MatchInfo的物件集合,其中包含發現匹配存在的檔案和行數資訊,圖20所示為獲取的資訊行、檔名和路徑。

正則表示式是個非常有用的工具,儘管存在一定的複雜性,但是對於以後高效使用PowerShell很有幫助。而且一旦掌握了某門語言的正則表示式,則很容易掌握其他語言的正則表示式。

5 總 結

本文探討了PowerShell操作檔案的機制、如何讀取不同的資料格式並生成自己的資料,以及如何使用正則表示式從文字塊中獲取資料。說明了如何操作檔案,包括引用、處理和儲存檔案等。並且說明了可以在不同平臺之間轉換檔案的常見字元編碼,以及如何使用正則表示式提取文字檔案中的內容。