[原文作者]: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>
成功了!下回我应该能记住了。
[原文作者]: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)
[原文作者]: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的实际操作。
[原文作者]: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
[原文作者]: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
[原文作者]:Jared Parsons
[原文链接]:If you implement IEquatable<T> you still must override Object’s Equals and GetHashCode
CLR2.0(公共语言运行时2.0)包含了一个接口IEquatable<T>,用来进行类型安全的比较操作。之前可用的最好方法是用虚方法Equals来比较,这个方法类型较松散,因为它将对象作为参数传入,当然,对于在类型适当要求不高的客户端来讲,这个方法足够了
class Student {
public override bool Equals(object obj) {
var other = obj as Student;
if (other == null) {
return false;
}
// rest of comparison
}
}
IEquatable<T>中一个非常显著的改进是它提供了强类型的比较方法,防止了调用者和被调用方传递无法比较的对象类型,当然也消除了值类型装箱的开销。
这些优点很吸引人,但是当你执行IEquatable<T>接口时,必须重载Equals(Object参数)和GetHashCode方法。否则你会付出些代价,在之前的文章中我简短的介绍了下这方面的内容,但是今天就几个例子我想深入讨论下这个话题。
在谈论技术细节之前,我们来想象一个场景,执行IEquatable<T>相当于陈述了“该对象明白相等的含义”,即在你的类中声明了对象之间如何比较是否相等,你的对象应该实现这个功能,防止使其他不熟悉这个对象的程序员迷惑,这可是件麻烦事。
问题1:IEqualityComparer<T>依赖于GetHashCode()
强类型的集合,例如Dictionary<TKey,TValue> 和HashSet<T>,必须比较对象是否相等来实现其功能,从BCL(Base class library)2.0开始,由接口IEqualityComparer<T>来比较对象之间语义是否相等。这个接口在集合之外的很多地方得到运用,但是集合相关的类最能够体现该接口的意义。
下面是IEqualityComparer<T>的定义
public interface IEqualityComparer<T> {
bool Equals(T x, T y);
int GetHashCode(T obj) ;
}
默认的定义来自BCL中的GenericEqualityComparer<T>类,IEqualityComparer<T>的默认实现依赖于IEquatable<T>的实现。
但是要实现IEquatable<T>的话要怎么实现GetHashCode()呢? 很简单,使用Object.GetHashCode()。这意味着一个对象想正常运行的话必须在IEqualityComparer<T>调用的地方实现IEquatable<T> 和GetHashCode()。
但是等一下,我没有明显的实现IEqualityComparer<T>所以我可以摆脱那些麻烦了吗?不是这样,很少有人真正实现IEqualityComparer<T>,而是用EqualityComparere<T>来替代,这个函数用来默认读取一个给定类型的IEqualityComparer<T>接口。
public static class Example {
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source) {
return Distinct(source, EqualityComparer<T>.Default);
}
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer) {
// implementation
}
}
事实上,采用IEqualityComparer<T>接口的方法有个标准模式,就是EqualityComparer<T>采用重载而不是直接调用,这个模式默认如下
public static class Example {
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source) {
return Distinct(source, EqualityComparer<T>.Default);
}
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer) {
// implementation
}
}
如果你的对象实现了IEquatable<T>,那么会创建一个GenericEqualityComparer<T>的实例,对象也因此依赖于GetHashCode方法(比较时要求用到)。
问题2:弱类型集合及框架不使用IEquatable<T>
IEquatable<T>接口仅提供强类型的相等比较,弱类型访问接口时相等比较始终是棘手的问题。考虑实例化最初1.0版本的集合类:ArrayList, Hashtable,等等,这些基于对象的集合都无法使用IEquatable<T>接口。因此这些集合必须依赖于比较内部对象的方法。
如果不实现Object.Equals和Object.GetHashCode方法,你的类型可能无法进行任何比较操作。这样会导致类中对象相等等操作的不正确结果。
class Person : IEquatable<Person> {
public readonly string Name;
public Person(string name) {
Name = name;
}
public bool Equals(Person other) {
if (other == null) {
return false;
}
return StringComparer.Ordinal.Equals(Name, other.Name);
}
}
static void EqualityCheck() {
var p = new Person("Bob");
var list = new ArrayList();
list.Add(p);
Console.WriteLine(list.Contains(p)); // Prints: True
Console.WriteLine(list.Contains(new Person("Bob"))); // Prints: False
}
这和我们想要的相去甚远。这个例子中,两个Person的实例定义相同,但是在Contains方法中会产生错误。实现Object.Equals 和Object.GetHashCode两个方法会移除这个问题。
以下框架仍然是用松散类型集合:WinForms, WPF, WebForms,等等,因此你肯定会在工程中遇到需要松散类型的情况。
问题3:判断对象是否相等与哈希码在BCL中是密不可分的
无论如何,判断对象是否相等与哈希码在BCL中是密不可分的,如果一个对象可以用来比较那么它一定可以生成一个哈希码。这种隐秘的联系存在于框架内部的许多地方。
如上文所述,实现IEquatable<T>接口之后填充Object.Equals()是铁板钉钉的事实,Object.GetHashCode可能有点麻烦因为GetHashCode有许多隐秘的内部联系。频繁变化的对象很难提供一个有效的哈希机制,这种情况下就返回1。这样所有关于GetHashCode()的内部联系都可以运行,且耗时很少。也许这种方法会让Dictionary更像一个链表,但是总比不能运行好吧。
[原文作者]: Jared Parsons
[原文链接]: LINQ like functions for PowerShell: Skip-Count
PowerShell 管线,和C#/VB的LINQ很像,都是通过一系列的转换过滤一组数据,然后生成一系列新的数据,详细的内容我会在将来的帖子里细述。
当我使用PowerShell的时候,我一直把使用LINQ的经验用在使用管线上。不幸的是,很多LINQ的经验都不适用于PowerShell。大部分是简单易写的,但是一部分有一点麻烦。在任何情况下,我们不需要明白两种。所以我将开始一系列可以使用在PowerShell上的LINQ表现。
同时,我以后的帖子会有些长。我们现在让帖子更短一点。
今天说的相当于Enumerable.Skip。这个操作将调过枚举中的“count”元素。在PowerShell中, 相当于跳过管线中的“count”元素。
#============================================================================
# Skip the specified number of items
#============================================================================
function Skip-Count() {
param ( $count = $(throw "Need a count") )
begin {
$i = 0
}
process {
if ( $i -ge $count ) {
$_
}
$i += 1
}
end {}
}
Example:
PS:) 1..10 | skip-count 5
6
7
8
9
10