代码改变世界

Windows PowerShell 2.0之函数和脚本块共存

2010-11-24 00:10 by @天行健中国元素, ... 阅读, ... 评论, 收藏, 编辑

将函数和脚本块结合起来是很强大的编程方法,通过合并函数和动态特性的脚本块创建新的命令可接近于内置的PowerShell语法结构。调用接受单个脚本块的函数类似使用if,while或者switch语句,这样做的好处在于能够使代码便于阅读、理解和维护。加之语法的有效性,使脚本块成为了通过函数执行封装操作和策略的好工具。

函数和脚本块

实现新的控制结构

通常情况下,循环需要有使程序逐步趋于结束的退出条件。循环的规范是按照下面的形式使用while和for循环:

PS> while ($true)
{
		Write-Host “Looping…”
		#something triggers the exit condition
		Break
}

PS> for (;;)
{
		Write-Host  “Looping…”
		#something triggers the exit condition
		Break
}

为了便捷,其中引入一个新的语句loop。这个语句可以有条件地重复执行一个脚本块,在内部使用隐藏的while循环多次执行脚本块,如:

PS> function loop($code)
{
		While($true)
		{
			&$code
}
}
PS> loop{
	Write-Host “Looping…”
	break;
}

这样的语法结构与正常的控制流语句相似,不同是需要把执行语句写到同一行或使用反引号(`)将多行语句转义为一行,这样Shell将执行脚本块并将其传递到loop函数中。实际脚本块在while循环中执行,这样允许使用与循环相关的工作流控制语句,如break和continue。

另外一个方便的扩展是定义能静默忽略所有错误的操作。如下代码定义一个ignore函数,在执行脚本块时捕获所有脚本块抛出的异常并静默继续。其中会分别演示容错和未容错处理的执行结果:

PS C:\> function ignore($code)
>> {
>>         trap
>>         {
>>             continue
>>         }
>>         &$code
>> }
>>
PS C:\> $numbers = 2..0
PS C:\> foreach ($number in $numbers)
>> {
>>      ignore {
>>             5 / $number
>>      }
>> }
>>
2.5
5
PS C:\> foreach ($number in $numbers)
>>      { 
>>        5 / $number
>>      }
>>
2.5
5
Attempted to divide by zero.
At line:2 char:11
+      { 5 / <<<<  $number
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

上述函数中创建了异常捕获陷阱,当错误发生时不处理异常而继续执行。其中使用ignore函数忽略在除法操作时发生的错误,在结果中能看到两个返回值。因为第3个运算发生了零除错误,因而抛出异常并被异常陷阱所捕获而未现实出错信息。更多详细的异常处理和调试的讨论请参见第11章。

另外一种有用的扩展是创建能帮助用户获取某个命令的诊断信息。很多情况下,为了确认一段代码确实执行,需要在代码段前后分别增加Write-Host语句。则如果执行代码块前的该语句,肯定要执行其后的代码块;如果执行了后面的语句,则其前面的代码块没有发生任何错误。也可以通过如下函数来添加调试信息,这个函数带有两个参数,即当前操作的描述及操作本身的代码。并在调用被调试程序前后分别打印描述信息:

PS C:\> function trace($description, $code)
>> {
>>     Write-Host "Before:$description"
>>     &$code
>>     Write-Host "After:$description"
>> }
>>
PS C:\> trace "dir Command"{
>>         dir *.txt
>> }
>>
Before:dir Command

    Directory: C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---          2009/1/3     14:31         12 digit.txt
-a---          2009/1/3      6:06      12100 largetext.txt
-a---          2009/1/3      6:07       6949 smalltext.txt
-ar--          2009/1/3      6:27         13 test.txt
-a---          2009/1/3      6:28          9 test2.txt
After:dir Command
PS C:\> trace "division" {
>>         $denominator = 0
>>         5 / $denominator
>> }
>>
Before:division
Attempted to divide by zero.
At line:3 char:12
+         5 / <<<<  $denominator
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

After:division

trace函数使得调试信息出现在脚本块中任何语句的两侧,这些代码在执行时会将日志输出到控制台中。如果使用嵌套形式组合ignore和trace,则可追踪除零操作并忽略所有错误,如:

PS C:\> trace "division" {
>>     ignore {
>>     $denominator = 0
>>     5 /  $denominator
>>     }
>> }
>>
Before:division
After:division

在调试过程中可以使用类似trace函数的方式写入文件,从而检测函数的执行。

脚本块策略

PowerShell在集合及操作管道方面的功能十分强大。很多时候需要处理复杂方式组织的数据,对集合进行完全的遍历并不是解决问题的最好办法。假设当前需要遍历文件系统或者XML文档树,这两个对象拥有层次结构,最好通过递归来实现。但是PowerShell不提供对树的内置遍历算法。假设这里需要遍历一个很大的XML文件,并从中提取数所需特定条件的不同节点的数据。为了便于演示,在当前目录中创建一个类似HTML文档的名为“data.xml”的XML文件,其代码如下:

<body>
some text and an unordered list
<ul>
<li><a href="doc1.xml">doc1</a></li>
<li><a href="doc2.xml">doc2</a></li>
<li>EMPTY</li>
</ul>
</body>

可以使用下面的命令快速导入XML文档:

PS C:\PowerShell\CHAPTER07> $doc = [xml](Get-Content data.xml)
PS C:\PowerShell\CHAPTER07> $doc

body
----
Body

如果要获取第1个包含标签的

  • 元素,则创建一个递归函数从元素开始遍历其所有子元素。然后遍历子元素的子元素,直到找出要查找的元素为止。以下是函数的代码:
    PS C:\PowerShell\CHAPTER07>function Find-LiAnchor($root)
    >> {
    >> 	$name = $root.PSBase.Name
    >> 	if ($name -eq "li" -and $root.a -ne $null)
    >> 	{
    >> 		return $root
    >> 	}
    >> 	else
    >> 	{
    >> 		foreach ($child in $root.PSBase.ChildNodes)
    >> 		{
    >> 			$result = Find-LiAnchor -root $child -strategy $strategy
    >> 			if ($result)
    >> 			{
    >> 				return $result
    >> 			}
    >> 		}
    >> 	}
    >> }
    >> 
    PS C:\PowerShell\CHAPTER07> find-lianchor($doc.body)
    
    a
    -
    a

    该函数将会返回发现的第1个XML元素的,其中通过检测子元素是否存在将要搜索条件的标准来确定是否退出递归。需要强调的是需要使用PSBase属性获取原始且未被XML类型适配器处理的XML元素对象及其属性。XML类型适配器不允许用户通过Name属性直接获取到标签名。为了能够搜索到第1个

  • 元素包含的 标签,可以看到嵌套部分的内容逻辑即是重复嵌套函数本身要执行的操作,这样嵌套的好处在于可以不用考虑查找的内容在第几层标签中出现。当发现查找内容时将会返回$true值;如果在整个文件中没有发现则查找目标,则返回$false。以下的代码时将搜索策略封装在一个脚本块中:

    PS C:\PowerShell\CHAPTER07> function Find-Node($root, $strategy)
    >> {
    >>     $foundNode = &$strategy -node $root
    >>     if ($foundNode)
    >>     {
    >>         return $root
    >>     }
    >>     else
    >>     {
    >>         foreach ($child in $root.PSBase.ChildNodes)
    >>         {
    >>             $result = Find-Node -root $child -strategy $strategy
    >>             if ($result)
    >>             {
    >>                 return $result
    >>             }
    >>         }
    >>     }
    >> }
    >>
    PS C:\PowerShell\CHAPTER07> $emptyLI = Find-Node -root $doc.body -strategy {
    >> param($node)
    >> return ($node.PSBase.Name -eq "li") -and ($node.a -eq $null)
    >> }
    >>
    PS C:\PowerShell\CHAPTER07> Write-Host $emptyLI
    li

    有了这个FindNode函数,搜索元素变得很简单,用户只需要将搜索条件确定到一两行的代码中即可。下例中查找第1个包含非空href属性的

    标签:

    PS C:\PowerShell\CHAPTER07> $anchor = Find-Node -root $doc.body -strategy {
    >> param($node)
    >> return ($node.PSBase.Name -eq "a") -and `
    >> ($node.PSBase.GetAttribute("href") -ne $null)
    >> }
    >>
    PS C:\PowerShell\CHAPTER07> Write-Host $anchor.href
    doc1.xml

    同样可以使用PSBase获取原始的Name属性及GetAttribute()方法,可以使用GetAttribute()方法检查元素是否包含特定的属性。

    总 结

    本文讨论了在PowerShell中将函数和脚本块结合起来使其发挥各自的长处,使函数的功能更加符合实际的应用环境,通过这样的手段用户可以封装与PowerShell内置函数极其相似的通用函数模块。将函数和脚本块结合起来是很强大的编程方法,通过合并函数和动态特性的脚本块创建新的命令可接近于内置的PowerShell语法结构。

    赛迪网地址:http://news.ccidnet.com/art/32857/20100617/2089233_1.html

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