脚本块和参数:再一次自己解决问题

[原文作者]:Jared Parsons

[原文链接]:Script Blocks and Arguments: Figuring it out for myself … again

    脚本块是一个为了存储一个表达式或者一个声明的分组的powershell构架。它和C#/F#/VB中的Lambda表达式其实是一样的。最近,我需要去用一个脚本块,但我却发现我忘了如何去读取一个在这个脚本块中已经传递过的参数。

    忘记如何使用一项功能其实算不上一篇好帖子。但是,这个问题我至少已经遇到了四回,所以,想写个帖子来给自己长点记性,或者至少能从google上得到一个满意的搜索结果。

    通常当我在PowerShell方面遇到问题时我会走以下几个步骤:

    1. 使用内置的帮助命令;

    2. 在我的脚本库中搜索之前的关于这个样式的用法;

    3. Google一下,找个解决办法;

    4. 自己动手做些小测试来确定正确的用法

    问题只有在第四步亲自去做时才会出现,脚本块也是如此。

    其实,想弄明白说明文档通常是件很杂的活。脚本块相关文档在about_script_block下面,这与这个内置的类型【scriptblock】有些矛盾。这导致我要试好几次才能得到想要的。而内部网上的资料用处也不大,那只有些基本的样本。

    我的内部库脚本中使用了大量的脚本块,但是由于所谓的精简的句法规则而使得搜索通过没什么成果可言。毕竟他们仅仅需要一副在PowerShell脚本中很普通的支架。

    所以我不得不做第三步:Google一下了。当然,我绝不是一位Google忍者。事实上我经常会被没什么技术含量的妻子羞辱,说我只会去Google资料。偶尔有几回,我甚至会去咨询她我应该使用什么搜索关键词,天哪!真是太丢脸了!

    当使用“script”,“block”和“argument”关键词时,我能得到好的结果的机会相当的小。最近随着关于PowerShell的帖子的不断增多,事情有了些转机,但是仍然很难得到一篇好的关于脚本块的文章。

    现在我们来自己动手做个实验了。大部分脚本在PowerShell中的入口点都有权限去使用内置的$args变量,如这两个方法:Scripts和filters。

     PS> function test() { $args.Count } 
     PS> test 42
     1
     PS> test 42 "astring"
     2

    可能与脚本块相同。

     PS> $a = { $args.Count }
     PS> & $a 42
     1
     PS> & $a 42 "astring"
     2
     PS>

    成功了!下回我应该能记住了。

PowerShell LINQ: Skip-While

[原文作者]:Jared Parsons

[原文链接]:PowerShell LINQ: Skip-While

     在PowerShell LINQ这个系列中,接下来要介绍的就是SkipWhile。这个LINQ函数带有一个枚举类型的实例和一个判断条件。这个函数中的判断条件以当前元素的值来作为参数进行判断,只要判断条件为true就跳过该元素,直到判断条件为false时,将剩余的元素全部返回。

     LINQ版本以Func<T,TResult>的形式传入判断条件。在PowerShell中等价于delegate的是一个脚本块,但是它和.Net delegate不同,它没有什么途径可以使得Skip-While函数接受特殊的数字或者特殊的参数类型,不过在调用该函数时,将会获得相关契约的暗示。

    这个函数会自动匹配SkipWhile 的LINQ版本的相关契约,而不需要给它绝对的输入。

    #============================================================================

    # Skip while the condition is true

    #============================================================================

    function Skip-While() {

    param ( [scriptblock]$pred = $(throw "Need a predicate") )

    begin {

    $skip = $true

    }

    process {

    if ( $skip ) {

    $skip = & $pred $_

    }

    if ( -not $skip ) {

    $_

    }

    }

    end {}

    }

    用例如下:

    PS) 1..10 | Skip-While { $args[0] -lt 6 }

    6

    7

    8

    9

    10

    PS)

在PowerShell中模拟闭包

[原文作者]:Jared Parsons

[原文链接]:Simulating Closures in PowerShell

 

    在之前的博文中我曾经提到过Power Shell缺少在脚本块中对闭包的支持.这对我正在研究的开发针对PowerShell的LINQ, 比如DSL, 是个很大的障碍。想象下下面的语句:

     $a = from it in $source where {$it -gt 5 }

    它大致相当于以下的C#代码

     var a = from it in source where it > 5;

    在C#中这段代码是能正常运行的,因为追根究底其中的WHERE语句“it > 5”被转化成了Lambda表达式。它要获取的变量在Lambda表达式中是通过闭包来实现的。为了在PowerShell中实现相似的功能,值$it在“where”句块执行的时候必须被转换。

    所幸PowerShell的灵活性出乎意料的好。当一个脚本块执行的时候,它会通过遍历各个不同的域并尝试转换所有的变量。第一被搜索的域是脚本块,然后是脚本块执行所在的本地代码块。使用new-variable,我们能生成和脚本块所寻求的变量名相同的变量。

PS) $sb = { write-host $it }
PS) & $sb
 
PS) new-variable "it" 42 -scope local
PS) & $sb
42

    成功了!现在我们唯一要做的就是通过生成一个函数Run-Scriptblock来概括这种行为。这个函数涉及到以下两点:

    1.需要执行的脚本块;

     2.一系列名字/值对。每一对都代表执行脚本块所必需的一个变量。

     代码:

#============================================================================
# Runs a script block.  The $list parameter must be a list of string, value
# combinations.  The script block will be executed with variables of the 
# specified name and value in scope
#============================================================================
function Run-Scriptblock() {
    param ( [scriptblock] $sb = $(throw "Need a script block"), 
            [object[]]$list= $(throw "Please specify the list of names and values") )
 
    for ( $i = 0; $i -lt $list.Length; $i = $i+2 ) {
        $name = [string]($list[$i])
        $value = $list[$i+1]
        new-variable -name $name -value $value -scope "local"
    }
 
    & $sb
}

    Example Usage:

PS) $sb = { write-host $it }
PS) run-scriptblock $sb "it",42
42
PS) $it
 

     现在我们已经有执行一个“where”语句的方法了。下次我们将讨论如何在PowerShell中定一个LINQ DSL的实际操作。

BclExtras 类库

[原文作者]:Jaredpar
[原文链接]:BclExtras Library

    今天我在Code Gallery上发布了一个名叫BclExtras的.NET实用类库。这是一些类的集合,标准.NET基类库的扩展。BclExtras类库专注于功能编程、多线程编程、LINQ扩展、单元测试和类型判断。

    该项目由我自己的许多项目演变而来。在过去一年里,我一直将其作为单独的测试类库。开始的时候,我写了大量关于多线程的代码,但是最近又加入了许多功能性API与集合。

    我发布的这个类库即有源代码也有二进制文件,可用于.NET 2.0和.NET 3.5。该类库的.NET 2.0版本中包含了一些2.0SP1 CLR没有实现的特性,例如:Func<T>、 Action<T>、扩展属性和LINQ枚举类的一部分功能。这使得基于.NET 2.0的应用程序可以使用大部分LINQ表达式。为了避免冲突,我在类库的.NET 3.0版本中移除了这些类型。

    以前我也发布过该类库,叫做RantPack。起初作为我个人的类库,名称让人难以理解。但是对我来说,RantPack也没有什么特别的意义。所以我给它取了一个对大多数人有意义的名称。

标签:DotNet, C#, RantPack, BclExtras

System.Diagnostics.Process: 重定向标准输入、输出和错误

[原文作者]:Lucian Wischik

[原文链接]:System.Diagnostics.Process: redirect StandardInput, StandardOutput, StandardError

    有时我们运行一个外部适用程序,向里面输入数据然后获取它的输出。这往往很容易发生这样的死锁:

     ‘ BAD CODE

     Using p As New System.Diagnostics.Process

     p.StartInfo.FileName = "cat"

     p.StartInfo.UseShellExecute = False

     p.StartInfo.RedirectStandardOutput = True

     p.StartInfo.RedirectStandardInput = True

     p.Start()

     p.StandardInput.Write("world" & vbCrLf & "hello")

    ‘ 这里将发生死锁,如果P写到输出接口的量达到12k

     p.StandardInput.Close()

     Dim op = p.StandardOutput.ReadToEnd()

     p.WaitForExit()

     p.Close()

     Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

     End Using

    这段程序将发生死锁,因为“Cat”首先从标准输入接口中读取,然后写到标准输出接口,最后再读取它,这样一直循环下去直到没有任何东西可读取。但是如果它的标准输出接口已经填满了,并且没有对象去读取,这样就不能继续往里面写东西,从而出现阻塞。

    这里的12k仅是一个随机数,我不会关注于它…

     ‘ BAD CODE

     Using p As New System.Diagnostics.Process

     p.StartInfo.FileName = "findstr"

     p.StartInfo.UseShellExecute = False

     p.StartInfo.RedirectStandardOutput = True

     p.StartInfo.RedirectStandardError = True

     p.Start()

    ‘ deadlock here if p needs to write more than 12k to StandardError

     Dim op = p.StandardOutput.ReadToEnd()

     Dim err = p.StandardError.ReadToEnd()

     p.WaitForExit()

     Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

     Console.WriteLine("ERROR:") : Console.WriteLine(err)

     End Using

     MSDN文档中说:你可以通过异步读取操作来避免这些依赖和潜在的死锁;或者你可以通过创建两个线程,让其中一个独立线程来读取输出流来避免死锁。因此我们将这样来做:

     使用线程来重定向就不会死锁

     ‘  GOOD CODE: 这里不会发生死锁.

     Using p As New Diagnostics.Process

     p.StartInfo.FileName = "sort"

     p.StartInfo.UseShellExecute = False

     p.StartInfo.RedirectStandardOutput = True

     p.StartInfo.RedirectStandardInput = True

     p.Start()

     Dim op = ""

     ‘ do NOT WaitForExit yet since that would introduce deadlocks.

      p.InputAndOutputToEnd("world" & vbCrLf & "hello", op, Nothing)

     p.WaitForExit()

     p.Close()

     Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

     End Using

     ”’ <summary>

     ”’ InputAndOutputToEnd: a handy way to use redirected input/output/error on a p.

     ”’ </summary>

     ”’ <param name="p">The p to redirect. Must have UseShellExecute set to false.</param>

     ”’ <param name="StandardInput">This string will be sent as input to the p. (must be Nothing if not StartInfo.RedirectStandardInput)</param>

     ”’ <param name="StandardOutput">The p’s output will be collected in this ByRef string. (must be Nothing if not StartInfo.RedirectStandardOutput)</param>

     ”’ <param name="StandardError">The p’s error will be collected in this ByRef string. (must be Nothing if not StartInfo.RedirectStandardError)</param>

     ”’ <remarks>This function solves the deadlock problem mentioned at http://msdn.microsoft.com/en-us/library/system.diagnostics.p.standardoutput.aspx</remarks>  

     <Runtime.CompilerServices.Extension()> Sub InputAndOutputToEnd(ByVal p As Diagnostics.Process, ByVal StandardInput As String, ByRef StandardOutput As String, ByRef StandardError As String)

     If p Is Nothing Then Throw New ArgumentException("p must be non-null")

     ‘ Assume p has started. Alas there’s no way to check.

      If p.StartInfo.UseShellExecute Then Throw New ArgumentException("Set StartInfo.UseShellExecute to false")

      If (p.StartInfo.RedirectStandardInput <> (StandardInput IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Input only when StartInfo.RedirectStandardInput")

      If (p.StartInfo.RedirectStandardOutput <> (StandardOutput IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Output only when StartInfo.RedirectStandardOutput")

     If (p.StartInfo.RedirectStandardError <> (StandardError IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Error only when StartInfo.RedirectStandardError")

 

     Dim outputData As New InputAndOutputToEndData

     Dim errorData As New InputAndOutputToEndData

 

     If p.StartInfo.RedirectStandardOutput Then

     outputData.Stream = p.StandardOutput

     outputData.Thread = New Threading.Thread(AddressOf InputAndOutputToEndProc)

     outputData.Thread.Start(outputData)

     End If

     If p.StartInfo.RedirectStandardError Then

     errorData.Stream = p.StandardError

     errorData.Thread = New Threading.Thread(AddressOf InputAndOutputToEndProc)

     errorData.Thread.Start(errorData)

     End If

 

     If p.StartInfo.RedirectStandardInput Then

     p.StandardInput.Write(StandardInput)

     p.StandardInput.Close()

    End If

    If p.StartInfo.RedirectStandardOutput Then outputData.Thread.Join() : StandardOutput = outputData.Output

    If p.StartInfo.RedirectStandardError Then errorData.Thread.Join() : StandardError
= errorData.Output

    If outputData.Exception IsNot Nothing Then Throw outputData.Exception

    If errorData.Exception IsNot Nothing Then Throw errorData.Exception

    End Sub

    Private Class InputAndOutputToEndData

    Public Thread As Threading.Thread

    Public Stream As IO.StreamReader

    Public Output As String

    Public Exception As Exception

    End Class

    Private Sub InputAndOutputToEndProc(ByVal data_ As Object)

    Dim data = DirectCast(data_, InputAndOutputToEndData)

    Try : data.Output = data.Stream.ReadToEnd : Catch e As Exception : data.Exception = e : End Try

    End Sub