Windows PowerShell 2.0之输入输出


由程序生成的数据通常会比生成它的程序有更长的存活期,文本文件能够很容易地从一个系统传输到另一个系统。本文将探讨PowerShell操作文件的机制、如何读取不同的数据格式并生成自己的数据,以及如何使用正则表达式从文本块中获取数据。

1 读取内容

在PowerShell中,Get-Content和Set-Content这两个cmdlet分别用于获取和设置原始二进制文件。默认情况下,这两个命令用于操作文本文件。图1所示为使用Get-Content获取文件内容。

clip_image002

图1 使用Get-Content获取文件内容

Get-Content以行为单位返回一个字符串数组,每个数组元素中包含一行内容。图2所示为返回5个元素的字符串数组的文件内容。

clip_image004

图2 通过Get-Content返回以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文件并检查其中返回值的方法。

clip_image006

图3 使用Get-Content以字符串形式读取文件的方法

在读取的文件中包含52个字符,其中前7个字符用换行符分隔组成字符串“Sun WuK”。

Get-Content能够同时从多个文件中获取文本,当为Get-Content传递的对象是文件集合时,则返回所有的文件内容,内容是经过合并后的文件内容。图4所示为使用这个cmdlet获取当前目录中所有*.txt文件的内容,在获取之前列出目录。可以看到当前目录下有两个txt文件,分别是前面演示的实例文件infoOfPeople.txt和test.txt。

clip_image008

图4 使用Get-Content获取当前目录中所有*.txt文件的内容

也可以通过传递通配符给Get-Content读取同样的内容,如图5所示。

clip_image010

图5 向Get-Content传递通配符读取多个文件

由于前面创建的Get-ContentAsString.ps1是基于Get-Content的功能封装的,所以它具有Get-Content的所有属性,为其传递通配符给参数$Path的结果如图6所示。

clip_image012

图6 为Get-ContentAsString.ps1传递通配符给参数$Path的结果

前面使用Get-Content获取的是字符串的集合,这里返回的是单一的字符串。为了验证二者的不同,构造两个如图7所示的表达式。

clip_image014

图7 两个表达式

2 写入内容

Set-Content的-Value参数指定要写入文件的对象(另一个-Path参数指定保存文件的路径),可以用管道输出代替。如果指定的参数是对象,则会调用其中内置的ToString()方法转换为字符串保存到文件中;如果指定的是集合,则会枚举其中的所有成员并分别赋值给单独的对象。图8所示为将两个文件的内容写入到一个文件中。

clip_image016

图8 用Set-Content转换对象将两个文件的内容写入到一个文件中

能够看到从dir命令获取的FileInfo对象用其中内置的ToString()方法返回了文件的全路径。

需要注意的是Set-Content具有破坏性,如果要写入的文件已经存在,则将被新写入的内容覆盖。所以在使用Set-Content之前一定要确认目标路径是否存在同名文件,如果存在,则确认文件是否覆盖;否则将会丢失原有数据。

要在已有文件尾添加新的内容,需要使用到Add-Content这个cmdlet,其参数与Set-Content相同。图9所示为使用Add-Content在texts.txt尾添加新的内容。

clip_image018

图9 使用Add-Content在texts.txt尾添加新的内容

上例中直接将dir命令的输出用管道传递给Add-Content,相当于将dir命令的输出赋值给-Value参数并执行Add-Content操作。

PowerShell支持两种输出重定向操作符,允许用户将命令输出保存在文件中。>操作符将会传递当前对象给文件并覆盖其原有文件的内容,示例如图10所示。

clip_image020

图10 使用>输出重定向操作覆盖文件原有文件内容的示例

>>操作符的功能与>类似,但它在原有文件尾添加新的内容,其示例如图11所示。

clip_image022

图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方式写字符串序列到文件中,然后以字节方式读出。

clip_image024

图12 用不同编码方式读写文件

这里将字符串序列“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所示。

clip_image026

图13 结果之后的数据

可以看到十六进制的a是61,b是62,回车符是D,换行符是A。

3.2 不同Unicode编码

本节将使用不同编码保存文件,然后使用工具来查看二进制数据,这样即可看到.NET中不同编码之间的关系。首先从.NET标准的Unicode编码开始,保存字符串序列到文件中并读出,执行结果如图14所示。

clip_image028

图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。

clip_image030

图15 采用Big Endian Unicode编码方式

Unicode和Big Endian Unicode编码使用两个字节编码每个字符,从而在表示英文字符时不可避免地浪费一个字节。UTF-8的编码使用一个字节编码一个英文字符,使用两个或多个字节编码其他语言字符解决这个问题。图16所示为采用UTF8编码方式保存文件。

clip_image032

图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所示为检测之前生成的文本文件的编码方式。

clip_image034

图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所示为执行结果。

clip_image036

图18 执行结果

该脚本中操作的文本文件只要包含数字,即可过滤。

4.2 查找文件中的字符串

PowerShell中的Select-String是一个内置cmdlet,用其可以查找文件中的字符串或满足特定正则表达式的记录。熟悉Unix的读者可以使用grep的工具,在Windows下则可以使用findstr工具。

Select-String的参数-Pattern用于指定要查找的字符串或满足特定正则表达式的记录,-Path参数指定要查找的文件名或通配符。为了验证这个cmdlet的功能,搜索当前目录中所有包含“if”字符串的脚本文件,如图19所示。

clip_image038

图19 使用Select-String搜索当前目录中所有包含“if”字符串的脚本文件

获取的结果是一个MatchInfo的对象集合,其中包含发现匹配存在的文件和行数信息,图20所示为获取的信息行、文件名和路径。

clip_image040

图20 获取的信息行、文件名和路径

正则表达式是个非常有用的工具,尽管存在一定的复杂性,但是对于以后高效使用PowerShell很有帮助。而且一旦掌握了某门语言的正则表达式,则很容易掌握其他语言的正则表达式。

5 总 结

本文探讨了PowerShell操作文件的机制、如何读取不同的数据格式并生成自己的数据,以及如何使用正则表达式从文本块中获取数据。说明了如何操作文件,包括引用、处理和保存文件等。并且说明了可以在不同平台之间转换文件的常见字符编码,以及如何使用正则表达式提取文本文件中的内容。

 

作者: 付海军
版权:本文版权归作者所有
转载:欢迎转载,为了保存作者的创作热情,请按要求【转载】,谢谢
要求:未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
个人网站: http://txj.shell.tor.hu/


发表回复