zoukankan      html  css  js  c++  java
  • 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操作文件的机制、如何读取不同的数据格式并生成自己的数据,以及如何使用正则表达式从文本块中获取数据。说明了如何操作文件,包括引用、处理和保存文件等。并且说明了可以在不同平台之间转换文件的常见字符编码,以及如何使用正则表达式提取文本文件中的内容。

    赛迪网地址:http://tech.ccidnet.com/art/302/20100716/2118503_1.html

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

  • 相关阅读:
    react路由组件&&非路由组件
    react函数式组件(非路由组件)实现路由跳转
    react使用antd组件递归实现左侧菜单导航树
    【LeetCode】65. Valid Number
    【LeetCode】66. Plus One (2 solutions)
    【LeetCode】68. Text Justification
    【LeetCode】69. Sqrt(x) (2 solutions)
    【LeetCode】72. Edit Distance
    【LeetCode】73. Set Matrix Zeroes (2 solutions)
    【LeetCode】76. Minimum Window Substring
  • 原文地址:https://www.cnblogs.com/fuhj02/p/1897379.html
Copyright © 2011-2022 走看看