代码改变世界

Windows PowerShell 2.0命令别名技巧与内置别名

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

命令别名可以通过简化的命令引用让用户大大减少击键的次数,PowerShell为其冗长的cmdlet名提供了更短的别名,而且鼓励用户定义自己的别名。然而如果不合理地使用别名,将会带来与其相关的问题。别名是很友好的移植工具,可以用其提供用户熟悉的操作系统环境,并且便于程序的移植。

别名提示、技巧和缺陷

别名的出现确实简化了用户的输入,但与此同时也降低了脚本的可读性并增加了用户对命令的记忆量,因为别名的出现强迫用户记忆每个别名指向的命令。所以别名的使用应该寻找简化输入与便于记忆的折中点,这样才能发挥别名最大的功效。对比下面的代码片段:

Get-ChildItem *.txt | Where-Object { $_.Length -gt 5KB} | `
ForEach-Object { $_.Name }

以及下面的代码片段:

gci *.txt | ? { $_.Length -gt 5KB} | % { $_.Name }

前一段便于理解,更接近平常的自然语言。而后一段即便是熟练的脚本编写者也需要花时间考虑?是Where-Object的别名,而%是ForEach-Object的别名。通常来说,如果是需要多次重复使用的脚本,尤其是可能对外发布的脚本,最好是使用完整的命令来书写,这样的脚本可读性强;如果只是自己或者在较小范围内使用的脚本,则可以根据个人爱好编写。

1.1 名称冲突

对于别名来说,名称应该是其最重要的特征,所以在定义别名时尤其要留意,因为别名会屏蔽同名的其他命令和函数。下面创建名为“Get-Command”的别名:

PS C:\> Set-Alias Get-Command dir
PS C:\> Get-Command

    Directory: C:\

Mode         LastWriteTime          Length   Name
----           -------------            ------    ----
d----         2008-6-10     21:43            Documents and Settings
d----         2008-9-23     20:41            Downloads
d----         2008-9-22     23:52            PowerShell
d----         2008-9-18      8:32            Program Files
d----         2008-9-24     19:57            WINDOWS
…

内置的cmdlet中已有名为“Get-Command”的cmdlet,这里再次定义与其同名的别名。并且起作用的是别名,而不是原有内置的Get-Command。由此可以推测同名情况下别名的优先级高于同名cmdlet。定义的别名已经屏蔽了cmdlet的执行,键入的Get-Command调用的是其指向的dir命令,这是在PowerShell语言设计时定义的优先级顺序,当在控制台键入某个命令后,PowerShell解析器会分优先级在不同的范围内搜索该命令。

(1)别名:Shell会首先搜索别名,如果发现对应的别名,则执行并停止搜索。

(2)函数:在函数范围内查找对应的定义。

(3)Cmdlet:搜索已经注册的cmdlet。

(4)可执行文件:在当前目录及在System的PATH环境变量中指定目录中查找通常会包含后缀为.exe、.cmd和.bat等的可执行文件。

(5)脚本:查找包含ps1后缀的PowerShell脚本文件。

(6)普通文件:尝试运行已有打开方式的普通文件。

例如,创建名为“Get-ChildItem”的别名,让该别名指向Write-Host执行与其相同的操作,已经指向Get-ChildItem的别名dir也将会受到影响,如下例所示:

PS C:\> Set-Alias Get-ChildItem Write-Host
PS C:\> dir C:\
C:\
PS C:\>

正如所看到的,指向Get-ChildItem的别名dir已经被转向执行Write-Host的操作。内部的操作,如内置的Tab自动补全功能已经失效。很显然,这个特性基于Get-ChildItem,这样PowerShell原有的功能已经被破坏,所以在定义别名时要特别注意,一旦新别名错误地覆盖已有的命令,很可能会带来意想不到的异常,这种情况被称之为“名称冲突”。

此类名称冲突的发生一般是无意的唯一能察觉到此类问题的方法是通过Get-Alias命令查看是否有命令被通过别名指向到非预期的位置,如:

PS C:\> Get-Alias Get-ChildItem

CommandType    Name                               Definition
-----------         ----                                 ----------
Alias              Get-ChildItem                        Write-Host
PS C:\>

这样指向的别名削弱了PowerShell本身的功能,前面曾经通过删除alias:\驱动器中别名的形式释放被错误别名指向的命令,具体操作如下例所示:

PS C:\> Set-Alias Get-Alias Write-Host
PS C:\> Get-Alias Get-Alias
Get-Alias
PS C:\> Set-Alias Get-ChildItem Write-Host
PS C:\> dir alias:\Get-Alias
alias:\Get-Alias
PS C:\> Remove-Item alias:\Get-ChildItem
PS C:\> Remove-Item alias:\Get-Alias
PS C:\> Remove-Item alias:\Get-Command

需要强调的是前面的dir命令只输出其的输入,是因为它被通过别名指向了Write-Host。能够看到将Get-Alias也指向到Write-Host之后调用Get-Alias,也执行Write-Host的操作。后面通过Remove-Item命令操作别名驱动器alias:\中对应的别名记录移除冲突的别名,将Shell恢复到正常的状态下。一种极端的情况是alias:\驱动器被错误指向,而且New-PSDrive命令被指向其他命令,这个已没有办法通过移除别名驱动器alias:\中的别名的方法来清除被恶意覆盖别名。此时必须需要重启PowerShell的进程让内置的别名和命令恢复到初始状态,当然在重启进程之前还需要检查$PROFILE,要预防配置文件中已经写入了别名重新指向的命令。由此可见别名的指向非常重要,设置不当将会对PowerShell的正常运行造成很大影响。

1.2 复杂别名

别名提供了从命令名映射到另外一个别名的机制。在某些环境下可能需要在执行前将复杂的命令,甚至是相关的参数作为别名。PowerShell并不支持带参数的别名,如,如果需要定义通过递归调用列出所有子目录文件的别名dir-recurse,则不能按照下面的定义方式操作:

PS C:\> Set-Alias dir-recurse "dir -recurse"
PS C:\> dir-recurse
Cannot resolve alias 'dir-recurse' because it refers to term 'dir -recurse', which is not recognize
d as a cmdlet, function, operable program, or script file. Verify the term and try again.
At line:1 char:12
+ dir-recurse <<<<
    + CategoryInfo          : ObjectNotFound: (dir-recurse:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : AliasNotResolvedException

需要注意的是尽管输入的别名指向命令并不存在,但Set-Alias命令并没有抛出错误,而是在尝试调用别名时出现错误。可以猜测在通过Set-Alias设置别名时,并不会验证别名指向的命令是否存在。首先移除已经出错的别名,随后定义调用dir命令并传递-recurse参数的函数:

PS C:\> del Alias:\dir-recurse
PS C:\> function dir-recurse
>> {
>>     dir -recurse
>> }
>>
PS C:\> dir-recurse

    Directory: C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----         2008-6-10     21:43            Documents and Settings
d----         2008-9-23     20:41            Downloads
d----         2008-9-22     23:52            PowerShell
d----         2008-9-18      8:32            Program Files
d----         2008-9-24     19:57            WINDOWS
…

可以通过设置别名屏蔽内置的cmdlet,从而通过定义与内置cmdlet同名函数的方法来对内置cmdlet功能不够完善处注入重载。函数是支持通过别名重新指向的,下例是将别名dirr指向dir-recurse函数的方法:

PS C:\> Set-Alias dirr dir-recurse
PS C:\> dirr

    Directory: C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----         2008-6-10     21:43            Documents and Settings
d----         2008-9-23     20:41            Downloads
d----         2008-9-22     23:52            PowerShell
d----         2008-9-18      8:32            Program Files
d----         2008-9-24     19:57            WINDOWS
…

1.3 删除破损别名

设置别名时使用的命令Set-Alias和New-Alias,但是PowerShell未提供在创建别名时验证命令定义有效性的方法。下面创建一个完全错误的别名:

PS C:\> Set-Alias Wrong-Alias Something-Completely-Broken
PS C:\> Wrong-Alias
Cannot resolve alias 'Wrong-Alias' because it refers to term 'Something-Completely-Broken', which i
s not recognized as a cmdlet, function, operable program, or script file. Verify the term and try a
gain.
At line:1 char:12
+ Wrong-Alias <<<<
    + CategoryInfo          : ObjectNotFound: (Wrong-Alias:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : AliasNotResolvedException

PS C:\>

能够看到在定义别名的过程中并没有报错,只是在尝试调用创建的别名时得到错误信息。随着时间的增长,可能会在积累大量破损别名。可以通过检查别名的Definition属性,并尝试将其值作为命令解析,如果得到错误,则说明发现破损别名。下例检查某个别名:

PS C:\> Get-Command (Get-Alias Wrong-Alias).Definition
Get-Command : The term 'Something-Completely-Broken' is not recognized as a cmdlet, function, opera
ble program, or script file. Verify the term and try again.
At line:1 char:12
+ Get-Command <<<<  (Get-Alias Wrong-Alias).Definition
    + CategoryInfo          : ObjectNotFound: (Something-Completely-Broken:String) [Get-Command],
   CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCom
   mand

PS C:\>

由于别名Wrong-Alias指向的命令Something-Completely-Broken并不存在,因此抛出了错误。这样可以通过验证每个别名的Definition属性检查别名的有效性,方法如下所示:

PS C:\> Get-Alias | Where { !(Get-Command $_.Definition `
>> -ErrorAction SilentlyContinue) }
>>

CommandType     Name                                      Definition
-----------     ----                                      ----------
Alias           Wrong-Alias                            Something-Completely-Broken

PS C:\>

同样可以在查找到破损的别名时,通过ForEach-Object将对应的别名删除,以保证现有别名的有效性。执行的代码如下所示:

PS C:\> Get-Alias | Where { !(Get-Command $_.Definition `
>> -ErrorAction SilentlyContinue) } | ForEach-Object `
>> { Remove-Item "alias:\$($_.Name)" }
>>
PS C:\> Get-Alias Wrong*
PS C:\>

这样处理之后系统中剩余的别名都是经过有效指向的,可以通过查看alias:\驱动器找出别名对应的路径。

内置别名

PowerShell在发布时已经内置了大量的别名,创建这些别名的目的在于熟悉其他Shell的用户可以很快地上手。下面检查PowerShell内置的别名:

PS C:\PowerShell\Chapter08> (Get-Alias).length
136

能够看到在PowerShell CP3的版本中包含136个内置别名,可以将这些别名划分为如下3个类别。

(1)兼容的别名:此类别名是为了照顾用户的操作习惯。

(2)Unix Shell兼容的别名:尝试模拟在Unix Shell中类似bash的命令。

(3)便捷别名:为了便于用户操作而创建的最常见的别名。

类cmd.exe别名

在PowerShell中可以使用常见的文件操作命令,如dir、cd及copy等cmd.exe内置的命令。它们是PowerShell的cmdlet创建的别名,上述的3个命令分别对应PowerShell中内置的Get-ChildItem、Set-Location和Copy-Item 3个cmdlet。表8-1 所示为模拟cmd.exe的内置别名。

表8-1 模拟cmd.exe的内置别名

别 名 对应cmdlet

cls Clear-Host

copy Copy-Item

dir Get-ChildItem

type Get-Content

move Move-Item

popd Pop-Location

pushd Push-Location

rd Remove-Item

rmdir Remove-Item

del Remove-Item

ren Rename-Item

chdir Set-Location

cd Set-Location

set Set-Variable

echo Write-Output

PowerShell的设计者将每个cmdlet的功能尽可能地简单,这样每个cmdlet相互之间的耦合性会很小,便于它们联合构成复杂的脚本。如dir别名不支持用于递归的/s命令开关,而起到相同作用的是-Recurse、-rec或-r开关另并且使别名不支持根据指定属性对排序记录,排序需要将结果通过管道传递给Sort-Object完成。没有强制将文件删除和目录删除分开,因为可以erase、delete、rd和rmdir都是同一个cmdlet,即Remove-Item的别名。需要强调的是Set-Variable的别名是set,但其不同于cmd.exe中的set myvar=something的语法形式,而是使用PowerShell中特殊的set myvar something的语法。尤其需要注意的是前一种语法在PowerShell中执行的过程中不会抛出任何的错误或警告。它不执行任何操作,如下例所示:

PS C:\PowerShell\Chapter08> Set myvar=something
PS C:\PowerShell\Chapter08> $myvar
PS C:\PowerShell\Chapter08> Set myvar something
PS C:\PowerShell\Chapter08> $myvar
something

下例在cmd.exe下设置变量并获取其中的内容:

C:\>SET NAME=PowerShell
C:\>echo "Hello, %NAME%"
"Hello, PowerShell"

上述语法只是在cmd.exe下的批处理起作用,在PowerShell中的变量均以美元符开头。下例是在PowerShell中执行相同操作的方法:

PS C:\PowerShell\Chapter08> set NAME PowerShell
PS C:\PowerShell\Chapter08> echo "Hello, $NAME"
Hello, PowerShell

上面的代码看起来很笨拙,在PowerShell中可以完全不用set别名,而直接通过变量赋值的形式实现相同的功能,如:

PS C:\PowerShell\Chapter08> $NAME = "PowerShell"
PS C:\PowerShell\Chapter08> echo "Hello, $NAME"
Hello, PowerShell

除了这些差异以外,PowerShell在变量声明、赋值和引用语法上均与cmd.exe相似。读者可以对比学习二者,以便尽快掌握。

类Unix别名

为了Unix用户能更方便地掌握和使用PowerShell,在别名系统中也添加了相应的类Unix别名。表8-2所示为类Unix命令的内置别名。

表8-2 模拟Unix命令的内置别名

其中包含所有文件定位操作,如使用ls替代dir也能够执行。在Unix中的基于文字的比较命令diff在PowerShell下可以用于比较对象及其属性,也可用于比较文件。唯一不同是输出不是统一、标准的Unix下diff输出格式,而是PowerShell对象比较的输出方式。

PowerShell中New-PSDrive cmdlet的别名mount和Unix文件系统中的mount功能类似。但添加新驱动器及访问的语法却完全不同,而且也没有内置相对应的umount别名映射到Remove-PSDrive。PowerShell和操作系统使用的驱动器是完全不同的概念,请参阅第9章。

2.3 便捷别名

PowerShell内置命令的便捷别名如表8-3所示。

表8-3 PowerShell内置命令的便捷别名

其中别名%映射到ForEach-Object,映射到Where-Object。同样,select,where和sort分别是Select-Object、Where-Object和Sort-Object的别名。注意foreach作为ForEach-Object的别名,由PowerShell的解析器会根据上下文决定foreach是作为循环语句,还是cmdlet的别名。

总 结

本文着重介绍了PowerShell创建和使用别名时的相关技巧,如何解决名称冲突,创建复杂别名以及删除破损别名,并创建自己的别名。创建的别名仅在当前会话内有效,若要创建持久性别名,则将其添加到PowerShell的配置文件中。通过使用别名,具有DOS和Unix Shell编程经验的用户即可更加便捷和快速地掌握PowerShell的使用方法。本文还介绍三种类型的内置别名,便于用户区别记忆和使用。

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

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