Powershell的函数返回值与其他脚本语言存在较大差别,我也是在研究了好一阵子才明白是怎么回事。
第一要搞明白什么是“默认输出”,默认输出就是没有指定任何特定输出设备的语句所要输出到的地方。相当于传统编程语言或者shell的stdout,其实就叫标准输出也行。像如下语句:
PS C:/Users/Administrator> 1+1
2
PS C:/Users/Administrator>
1+1是个表达式,它的结果是2,我们并没有指定把结果输出到哪里,则系统就丢到“默认输出”或“标准输出”里,默认输出的行为就是简单地把它回显出来。
再如同:
PS C:/Users/Administrator> $x="Hello World!"
PS C:/Users/Administrator> $x
Hello World!
PS C:/Users/Administrator>
同样$x变量没有指定任何左值,所以也是把它丢到标准输出去,即显示变量的值。
明白了这个再来看Powershell中函数返回值的问题。Powershell的函数返回值是函数体里面所有输出的值,而并不是return语句指定的那个值。
也就是说,函数体里面任何到标准输出的值都是返回值的一部分。
比如这个函数:
function test1()
{
"abc"
}
如果按照传统编程或脚本的思路来看,这里没有任何return语句,理应没有返回值,但运行这个函数的结果却是:
PS C:/Users/Administrator> test1
abc
PS C:/Users/Administrator>
按照传统思路来看这样的搞法就很纠结了,而且似乎也没什么必要这么玩,玩起来也会有无数问题,咱们一个一个道来。
第一个问题:返回值应该只有1个才对,按照这样的方法来搞,岂不是我放几个变量里面,就会有几个返回值吗?这样混乱的函数返回,后续应该怎么处理啊?
答案:确实只有一个返回值,但也确实你输出什么他就返回什么,所以结果呢,就是返回值是一个大Array,Array里面包含了函数里所有的输出。不信看这个函数test2:
function test2()
{
"abc"
"def"
123
}
看这个函数执行的结果:
PS C:/Users/Administrator> test2
abc
def
123
PS C:/Users/Administrator>
我们还可以把执行结果保存到变量,并通过调用GetType()方法来得知返回的对象类型:
PS C:/Users/Administrator> $result=test2
PS C:/Users/Administrator> $result.GetType()IsPublic IsSerial Name BaseType
——– ——– —- ——–
True True Object[] System.Array
可以看到确实是返回了Array。
第二个问题:有办法指定返回值类型吗,我还指望通过返回来的数据继续处理呢,这么一个大包Array丢过来,后续工作岂不是很麻烦?
答案:一点都不麻烦,其实返回回来的数据,都完整的保存了原有的数据类型,处理起来没有任何障碍,请看刚才的test1和test2函数:
PS C:/Users/Administrator> $result=test1
PS C:/Users/Administrator> $result.GetType()IsPublic IsSerial Name BaseType
——– ——– —- ——–
True True String System.ObjectPS C:/Users/Administrator> $result=test2
PS C:/Users/Administrator> $result | % { $_.GetType()}IsPublic IsSerial Name BaseType
——– ——– —- ——–
True True String System.Object
True True String System.Object
True True Int32 System.ValueType
test1只有一个返回项,所以可以直接GetType(),可以看到String被传回来,而test2因为Array里面有3项,所以遍历整个Array,可以看到2个String和1个Int32也都完整保存下来了。其实这里可以传递任何.net类库中的object类型甚至是自定义的对象类型,完全没有问题。
第三个问题:有些输出我不想让它放到返回值怎么办?比如下面一个函数:
function test3()
{
$due=1000
Write-Output "您的预付款余额是:"
Write-Output $due
}
输出结果自然就是:
PS C:/Users/Administrator> test3
您的预付款余额是:
1000
如果想把1000从函数结果当初提取出来,非要解开Array取第二个值,那是在太麻烦了,还不如不用函数算了。就像必须这样来拿数据:
PS C:/Users/Administrator> $x=test3
PS C:/Users/Administrator> $x
您的预付款余额是:
1000
PS C:/Users/Administrator> $x[1]
1000
这么搞太挫了,我们要既允许函数输出信息,又要让最终返回只要我需要的值,去掉提示信息,那怎么办?
答案:函数这么写,把输出语句用Write-Host代替:
function test3()
{
$due=1000
Write-Host "您的预付款余额是:"
Write-Output $due
}
再来看函数输出:
PS C:/Users/Administrator> test3
您的预付款余额是:
1000
PS C:/Users/Administrator> $x=test3
您的预付款余额是:
PS C:/Users/Administrator> $x
1000
PS C:/Users/Administrator>
可以看到提示语句是函数运行时输出的,但并不包含在返回值当中。其实这里Write-Host指的就是在屏幕输出,而Write-Output指的是输出到标准输出,当然如果省略Write-Output也是输出到标准输出。
第四个问题:有些Cmdlet/Fuction/Method本身具有返回值,我们有时只是需要执行这个Cmdlet/Function/Method本身而已,并不需要它的返回值,此时返回值会自作聪明的被我们外层Function捕获到而又被返回上去,怎么办?比如有这么一个function test4,我希望取得当前目录,并列一下目录里面的文件,这个很简单:
function test4()
{
pwd
dir
}
可问题来了,pwd返回的是当前目录的Directory object,dir也返回当前目录的object,这样返回值就有了2个当前目录,而我们只要一个,怎么办?想当然的改成Write-Host dir,结果也很喜剧,直接输出三个字母"dir”给你看。
答案:稍微一想就有解决方案了:
function test4()
{
pwd
$temp=dirWrite-Host $temp
}
这都要临时变量,是不是太傻了点?而且如果这里不是dir而是一个耗时很长才能完成的一个调用,并有很长输出的调用,这里完全是用了一个一点用处都没有的变量,确实太傻了。
不过如果不是Cmdlet的话,像一些方法之类的东西,可以在方法调用时前面加上[void]强令其无返回值,也可以解决,其实大部分情况,碰到的应该都是这种类型。为了说明这个用法需要稍微复杂一点的例子,前面那些愚蠢的用法解释不了这个问题,我借用了Powershell Cookbook中关于performance counter调用的一个例子,如下:
Function getcounter()
{
$arguments="System","System Up Time"
$counter= New-Object System.Diagnostics.Performancecounter $arguments
[void] $counter.NextValue()
New-Object TimeSpan 0,0,0,$counter.NextValue()}
根据.net类库,NextValue调用必须使用2次才能得到正确结果,所以我们可以在第一次调用前加一[void]来避免额外的返回值。
第五个问题:我强烈要求继续使用return关键字来限定返回值,有办法吗?
答案:很不幸这个没有解决方案,虽然Powershell支持return关键字,但函数的行为是不会受它影响的。比如:
function test5()
{
"abc"
$x="def"
return $x
}
test5的运行结果如下:
PS C:/Users/Administrator> test5
abc
def
PS C:/Users/Administrator>
还是将前面的abc以及return中的变量全部输出,其实这里的return跟Write-Oupput没有任何区别。
这就是Powershell中函数一个让人不怎么爽的特性,说实话,如果第一次接触函数,这么搞应该会挺舒服,但是对于有其他程序和脚本经验的人,这个特性就有些蹩脚了。