Power(Shell) 还权于民

减少输入,编写更简洁的脚本,跨平台一致运行,以及其他让 Linux 和 OS X 用户爱上 PowerShell 的理由。
444 位读者喜欢这篇文章。
One lightbulb lit out of several

Opensource.com

今年早些时候,PowerShell Core 在一个开源(MIT)许可下正式发布。PowerShell 并不是一项新技术。自 2006 年首次为 Windows 发布以来,PowerShell 的创建者试图融合 Unix shell 的强大和灵活性,同时弥补其被认为的不足之处,特别是需要进行文本操作才能从组合命令中获得价值。

经过五个主要版本的发布,PowerShell Core 允许相同的创新 shell 和命令环境在所有主要操作系统(包括 OS X 和 Linux)上本地运行。有些人(实际上是*几乎所有人*)可能仍然对这个 Windows 原生的闯入者敢于向那些自古以来(至少在千禧一代的定义中)就拥有强大 shell 环境的平台提供自身服务感到嘲笑和/或鲁莽。在这篇文章中,我希望证明 PowerShell 甚至可以为经验丰富的用户提供优势。

跨平台一致性

如果你计划将你的脚本从一个执行环境移植到另一个环境,你需要确保你只使用有效的命令和语法。例如,在 GNU 系统上,你将如下获得昨天的日期:

date --date="1 day ago"

在 BSD 系统(如 OS X)上,上述语法将不起作用,因为 BSD date 实用程序需要以下语法:

date -v -1d

由于 PowerShell 在宽松许可下授权并构建用于所有平台,你可以将其与你的应用程序一起发布。因此,当你的脚本在目标环境中运行时,它们将在与你测试脚本的环境相同的 shell 上运行,并使用相同的命令实现。

对象和结构化数据

*nix 命令和实用程序依赖于你使用和操作非结构化数据的能力。那些已经使用 sed grepawk 多年的人可能不会对这个说法感到困扰,但是有一种更好的方法。

让我们在 PowerShell 中重做昨天的日期示例。要获取当前日期,请运行 Get-Date cmdlet(发音为“commandlet”)

> Get-Date                         

Sunday, January 21, 2018 8:12:41 PM

你看到的输出实际上不是一个文本字符串。相反,它是 .Net Core 对象的字符串表示形式。就像任何其他 OOP 环境中的任何其他对象一样,它有一个类型,并且通常有你可以调用的方法。

让我们证明这一点

> $(Get-Date).GetType().FullName
System.DateTime

$(...) 语法的行为与你期望的 POSIX shell 完全一样——括号中命令的评估结果将替换整个表达式。但是,在 PowerShell 中,$ 在此类表达式中是严格可选的。而且,最重要的是,结果是一个 .Net 对象,而不是文本。因此,我们可以调用该对象的 GetType() 方法来获取其类型对象(类似于 Java 中的 Class 对象),并使用 FullName 属性来获取类型的完整名称。

那么,这种面向对象的特性如何让你的生活更轻松?

首先,你可以将任何对象管道传输到 Get-Member cmdlet,以查看它提供的所有方法和属性。

> (Get-Date) | Get-Member
PS /home/yevster/Documents/ArticlesInProgress> $(Get-Date) | Get-Member         


   TypeName: System.DateTime

Name                 MemberType     Definition                                 
----                 ----------     ----------                                 
Add                  Method         datetime Add(timespan value)               
AddDays              Method         datetime AddDays(double value)             
AddHours             Method         datetime AddHours(double value)            
AddMilliseconds      Method         datetime AddMilliseconds(double value)     
AddMinutes           Method         datetime AddMinutes(double value)          
AddMonths            Method         datetime AddMonths(int months)             
AddSeconds           Method         datetime AddSeconds(double value)          
AddTicks             Method         datetime AddTicks(long value)              
AddYears             Method         datetime AddYears(int value)               
CompareTo            Method         int CompareTo(System.Object value), int ...

你可以快速看到 DateTime 对象有一个 AddDays,你可以快速使用它来获取昨天的日期

> (Get-Date).AddDays(-1) 

Saturday, January 20, 2018 8:24:42 PM

为了做一些稍微更令人兴奋的事情,让我们调用 Yahoo 的天气服务(因为它不需要 API 令牌)并获取你当地的天气。

$city="Boston"
$state="MA"
$url="https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22${city}%2C%20${state}%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys" 

现在,我们可以用老方法,直接运行 curl $url 来获取一大块 JSON,或者……

$weather=(Invoke-RestMethod $url) 

如果你查看 $weather 的类型(通过运行 echo $weather.GetType().FullName),你将看到它是一个 PSCustomObject。它是一个动态对象,反映了 JSON 的结构。

PowerShell 将很乐意帮助你使用它的 Tab 补全功能浏览它。只需键入 $weather. (确保包含“.”),然后按 Tab。你将看到所有根级别的 JSON 键。键入一个,后跟一个“.”,再次按 Tab,你将看到它的子项(如果有)。

因此,你可以轻松导航到你想要的数据

> echo $weather.query.results.channel.atmosphere.pressure                                                              
1019.0

> echo $weather.query.results.channel.wind.chill                                                                       
41

如果你有 JSON 或 CSV 文件(或由外部命令返回)作为非结构化数据,只需将其管道传输到 ConvertFrom-JsonConvertFrom-CSV cmdlet 中,你就可以将数据放入整洁的对象中。

计算与自动化

我们使用 shell 有两个目的。一个是计算,运行单个命令并手动响应它们的输出。另一个是自动化,编写执行多个命令并以编程方式响应其输出的脚本。

我们大多数人已经学会忽略的一个问题是,这两个目的对 shell 提出了不同且相互冲突的要求。计算要求 shell 简洁。用户可以逃避的击键次数越少越好。用户键入的内容对另一个人来说是否难以辨认并不重要。另一方面,脚本是代码。可读性和可维护性是关键。在这里,POSIX 实用程序经常让我们失望。虽然某些命令确实为其某些参数提供简洁且可读的语法(例如,-f--force),但命令名称本身倾向于简洁,而不是可读性。

PowerShell 包含几种消除浮士德式权衡的机制。

首先,Tab 补全消除了键入参数名称。例如,键入 Get-Random -Mi,按 Tab,PowerShell 将为你完成参数:Get-Random -Minimum。但如果你真的想简洁,你甚至不需要按 Tab。例如,PowerShell 会理解

Get-Random -Mi 1 -Ma 10

因为 MiMa 各自具有唯一的补全。

你可能已经注意到所有 PowerShell cmdlet 名称都具有动词-名词结构。这有助于脚本的可读性,但你可能不想在命令行中一遍又一遍地键入 Get-。所以不要这样做!如果你键入一个没有动词的名词,PowerShell 将查找带有该名词的 Get- 命令。

注意:虽然 PowerShell 不区分大小写,但最好在打算使用 PowerShell 命令时将名词的首字母大写。例如,键入 date 将调用你系统的 date 实用程序。键入 Date 将调用 PowerShell 的 Get-Date cmdlet。

如果这还不够,PowerShell 还有别名来创建简单的名称。例如,如果你键入 alias -name cd,你将发现 PowerShell 中的 cd 命令本身是 Set-Location 命令的别名。

所以回顾一下——你可以获得强大的 Tab 补全、别名和名词补全来保持你的命令名称简短,自动和*一致的*参数名称截断,同时仍然享受丰富、可读的脚本编写语法。

所以... 朋友们?

这只是 PowerShell 的一些优点。还有更多我没有讨论的功能和 cmdlet(如果你想让 grep 哭泣,请查看 Where-Object 或其别名 ?)。嘿,如果你真的感到怀旧,PowerShell 会很乐意为你启动你的旧本地实用程序。但是给自己足够的时间来适应 PowerShell 的面向对象的 cmdlet 世界,你可能会发现自己选择忘记回去的路。

User profile image.
软件工程师,具有开源治理倾向。偶尔的开发者推广者。所有观点都是我自己的。大脑碎片在这里。

23 条评论

很棒的文章!在我担任系统管理员的日子里,如果我有一个比文本字符串更丰富的数据方法,脚本编写会容易得多(尽管文本字符串也有一些优点)。但是由于大多数 *nix 命令只或主要在文本字符串上运行,那么与 Python、Ruby 等相比,使用 PowerShell 有什么优势?

好问题。在喜欢一种工具胜过另一种工具时,存在主观因素。对我来说,PowerShell 的优势在于它作为命令 shell 和脚本语言的特定、有意的二元性。例如,在 Ruby 中删除文件比在 PowerShell 中需要更多的输入。而且,Tab 补全和统一的命令名称风格将不存在。

我发现将 PowerShell 视为自动化 DSL 而不是编程语言很有帮助。你不会使用 PowerShell 来编写 Web 应用程序。你可能会使用 PowerShell 自动化在你系统上配置 Web 应用程序或将 Web 应用程序部署到公共云(使用供应商提供的模块)。

这有帮助吗?

回复 ,作者是 bcotton

这太荒谬了。Powershell 是不一致的 - 一些 cmdlet 能正确进行 try/catch 操作,而另一些则不能(即:返回一个异常)。
你最好使用像 Python 这样的东西....

我没有遇到你描述的不一致情况。你能给出一些例子吗?

回复 ,作者是 joeb (未验证)

不用了,谢谢。我不知道该说什么才能保持礼貌。

厌倦了 Microsoft 和 Windows 试图 "模仿" 开源方式!...哈哈!看来他们终于撞上了 "墙",意识到他们做事的方式只能走这么远,现在他们想成为开源 "崭新创新者"?不,他们曾经有机会和 Unix 和 Linux 好好相处,但他们搞砸了。以至于我们在开源世界中拥有我们需要的一切,从媒体播放器到网络浏览器,再到办公套件和 PDF 阅读器,以及 BASH 脚本和命令行界面等等。甚至没有真正的原因去尝试混合两者。让那些想尝试的人继续尝试吧,但我对我的 Linux 命令行语法和功能非常满意。抱歉,但不是每个人都想将 Windows 与他们喜欢的操作系统 "融合"。

我是一名 Microsoft 员工(以我个人名义发言,不代表公司),我已经超过十年没有在我拥有的机器上运行 Windows 了。我也对我的 Linux 设置非常满意。 你说得对,Microsoft 意识到他们对待开源的方式是错误的。 但是说没有真正的理由混合两者,完全忽略了很多人确实运行混合环境(无论是自愿还是雇主的强制要求)。 能够在多个平台上使用相同的工具对系统管理员来说是有价值的。 你说得对,不是每个人都想将 Windows 与他们喜欢的操作系统融合。 但显然有些人确实想这样做。

回复 ,作者是 guest (未验证)

哦,我完全同意你的看法。很可能有人想同时运行它们,但对我来说? 我决定不再使用 Windows。 理解一下...我从 99 年开始在 I. T. 领域工作,并且从 3.1 版本开始使用 Windows。所以我了解它的来龙去脉。 虽然大众可能被它 "新" 的感觉所迷惑,但事实并非如此。 它和 Windows 95 是一样的...它是 Windows 98 的前身...它是 Windows NT 4.0 的前身,它导致了 Windows 2000.....然后是 2003......2007.....Vista.....XP......Win7....Win8...以及最近的 Win10。 所以我知道,虽然事情可能被赋予不同的名称,并且事情可能会获得新的图标和系统中的新位置,但它仍然是一个严重依赖 ".exe's" 的系统,其中可以注入各种各样的不良内容! 即使 PowerShell 没有运行 Windows 的大部分代码,它仍然 "附加" 到它,对我来说,这不是我想尝试的东西。 此外,我自 2001/'02 年以来一直在使用 Linux,并且已经学会了如何使用 Linux 命令在终端中移动,我为什么要 "学习" MS 的语法? 对于需要它的系统管理员...我说试试看...但我永远不需要 Windows 了...所以我选择消除来自该平台的任何东西干扰或影响我的 Linux 服务器和数据库的任何可能性。 但我确实理解你的观点,Windows 管理员/开发人员可能需要在 Linux 盒子上运行它,以便查看它是否/如何针对他们的代码运行,或者查看从 Linux 运行某些东西的结果以及它如何移植到 Windows。 我只是不需要它,对我来说...整个 "Microsoft 喜欢 Linux" 并不成立,我知道仍然有关于专利的诉讼案件可能会持续到未来十年或更长时间...所以不,不用了...我不需要 Windows。

回复 ,作者是 bcotton

Edward,

PowerShell 6 构建在 .Net Core 之上,.Net Core 是一个新的 .Net 模块化运行时,它为每个目标平台原生构建。 没有 exes,没有模拟器,没有 Windows。

回复 ,作者是 Edward G OConnor (未验证)

虽然不是程序员,但我不得不学习 Shell 脚本,才能成功且胜任地完成许多关键的脚本任务。

在许多情况下,PowerShell 在功能方面根本无法与 BASH 相提并论。 以简单的 ls 命令为例,至少有 8 - 10 个参数用于查看/列出 PowerShell 缺少的信息。

最后,如果 Powershell 如此 "出色",那么为什么 Microsoft 将完整的 Ubuntu BASH Shell 脚本工具集集成到 Windows 10 中,以及 ISO Python、openSSH 和几乎所有其他 UNIX/Linux 工具?

没有哪个工具适用于所有用例。 如果 Bash 如此出色,为什么 Fedora 在其存储库中包含 ZSH?

回复 ,作者是 W. Anderson (未验证)

PowerShell 确实包含 “dir” cmdlet (Get-ChildItem 的别名),它用于实现 “ls” 的目的。 最初的 Windows 版本的 PowerShell 实际上也将其别名为 “ls”。 我假设删除该别名是为了不与 Posix 平台上的本机 “ls” 命令冲突。

您可以在此处阅读 Get-ChildItem (又名 “dir”) 如何与文件一起工作: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell…

使用 PowerShell,您不需要那么多参数来指定要返回的信息,因为您得到的是 .Net 对象,您可以使用 Where-Object (又名 “where”,又名 “?”) cmdlet 查询其属性。

不要假设一个解决方案不存在,仅仅因为它不是立即显而易见的。

回复 ,作者是 W. Anderson (未验证)

> 因为 PowerShell 是根据宽松许可授权的,并且为所有平台构建,所以您可以将其与您的应用程序一起发布。

您也可以发布任何其他 FLOSS 许可的语言。 使用真正的编程语言而不是 bash 的原因是错误处理,而不是易用性。 向我展示一个用 power shell 编写的、良好的错误处理代码是什么样的,我可以考虑一下。 否则没有理由切换到另一个混合体,它假设开源意味着每个人都应该切换到它。

顺便说一句,还有 python shell。 并且可能还有一大堆其他 shell 计划来 *修复* 一些东西。 特别是当我读到大小写不敏感、试图将命令与某些前缀匹配、猜测部分命令参数以及其他 *有用的* 功能时...这使得静态分析变得不可能,降低了代码可读性并且容易出错。 此外,这些部分参数匹配绝对不是面向未来的,因为添加新参数很容易破坏现有的假设。

Alexander,

这是一个教程,描述了 PowerShell 的所有异常处理功能。 https://www.vexasoft.com/blogs/powershell/7255220-powershell-tutorial-t…. 这篇文章显然是在 Windows 上编写的,因为它使用了 Windows 路径,但所有错误处理机制在每个平台上都以相同的方式工作。

我并不是建议你使用 PowerShell 来代替 Python 来编写应用程序代码。 我建议你在以前可能选择编写 bash shell 脚本的地方使用 PowerShell。 即使你选择用实际的编程语言编写实现逻辑,用 PowerShell 而不是 Posix shell 编写包装器脚本,可以通过我在文章中描述的功能提供更流畅的用户体验。

请记住,PowerShell 运行在 .Net Core 平台之上,并与 .Net 类型系统原生交互。 因此,如果你愿意,你可以用 C#、F# 或任何其他构建符合 .Net Standard 的库的语言实现实际的应用程序逻辑,然后通过 PowerShell 调用该逻辑并接收结构化对象作为输出,以便于后续操作。

回复 ,作者是 Aleksandar Kos…

PowerShell 使我能够轻松地修改目录树中所有文件夹的 "修改日期"(因此我可以按日期查看我的项目文件夹,以查看我上次处理每个项目的时间)。 我最终能够用 PowerShell 中的一行代码来完成此操作。 这是因为 recurse 以及管道和过滤返回对象的能力。 这是一个 Windows PC,因此不确定是否有其他方法可以在其他系统上执行相同的操作。

该脚本深入每个文件夹,并将每个文件夹的修改日期设置为该文件夹中包含的任何文件或文件夹的最新修改日期。 我在单行版本中硬编码了路径。 较长的版本会提示您输入目标文件夹,并提供进度条(并在 PowerShell IDE 中报告错误)。

Get-ChildItem "H:\DropBox\FolderDateFix\Test1" -recurse | Where-Object {$_.PsIsContainer} | ForEach-Object {$_.LastWriteTime = ($_ | Get-ChildItem -recurse | Where-Object {!$_.PsIsContainer} | Sort-Object LastWriteTime | Select-Object -last 1).LastWriteTime}

如果你想了解每个对象的作用,我会在一篇博文中详细解释每一行。

http://fettricks.blogspot.com/2014/08/useful-folder-modification-dates-…

这是 PowerShell 能够轻松完成的一个很好的例子!我认为 Get-ChildItem、Select-Object 和 Where-Object 是少数几个别名比全名更直观的 cmdlet。所以我建议分别用“cd”、“select”和“?”(或“where”)替换它们。

您是否尝试过使用 Measure-Object -Maximum 获取最新的文件修改日期,而不是对整个目录进行排序?

回复 作者 Shane Trent (未验证)

我担任 Windows 管理员已经超过 10 年了。很长一段时间以来,我一直抵制使用 Powershell。而是使用 Python。例如,通过使用 Python 的 ctypes 调用 Win32 API。

主要是因为我发现 Powershell 与 Windows 安装(以及 Windows 附带的某些框架)之间的链接不够透明。Powershell 曾经是 Windows 中“不可删除”的一部分。每个 Windows 都附带一个不同的 Powershell 版本,这使得脚本高度不兼容并且难以针对这些各种目标进行测试(如果我在 Windows 10 上使用最新的 Powershell 版本来开发一个足够复杂的脚本,我确信我的脚本无法在原生的 Windows 7 上运行)。更新 Powershell 意味着您必须安装 Windows 更新(它的行为与常规 MSI 安装程序不同 - 在许多方面更难在大量计算机上部署 - 即,它需要重新启动等)。此更新是可选更新 - 通常在大型组织中从 WSUS 服务器上的自动批准中排除。我见过不止一个企业环境,其中每台计算机都安装了不同的 Powershell 版本。

使用 Python,事情就容易得多了(我可以将 Python 安装放在网络共享上或将其与我的脚本捆绑在一起)。

快进到 2018 年初

- Powershell 是开源的
- Powershell 已经与其或其 cmdlet 运行所需的各种框架分离
- 最重要的框架 .NET 不再是先决条件,但所需的 .NET core 运行时与 Powershell 捆绑在一起
- .NET core 本身已从 Windows 中“分离”出来,并成为跨平台的开源项目
- 这使得 Powershell 安装是独立的/可移植的(只需将 zip 文件解压缩到任何地方并运行 pwsh.exe)
- pwsh 仍然需要操作系统提供的一些外部框架。对于 Windows 管理员来说,最重要的一个是 WMI / CIM。虽然我现在可以将 Powershell 与我的脚本捆绑在一起,但令人烦恼的是,只有在本地 Windows 系统上安装了 Windows Management Framework 5+(也提供 Powershell 5.1 的那个),“get-wmiobject / "get-cimobject" 才能工作。然而,至少现在可以清楚地了解哪些框架实际上是 Windows 框架,而不是 Powershell+WMI+其他东西膨胀出来的。
- 一些有用的东西已经从 Powershell 6 中消失了,因为它使用仅限 Windows 的框架(即 out-gridview)。为了跨平台一致性而牺牲(没有仅限 Windows 的 cmdlet)。
- 通过开源实现透明:Powershell 6 是 github 上一个维护良好的项目。拥有非常友好的维护人员,他们热衷于回答问题等。以前从未见过微软提供如此出色的公共/免费支持。

总的来说,这解决了我在 Powershell 中的大部分痛点。剩下最大的痛点是“get-wmiobject”/“get-cimobject”,它们对于大多数 Windows 管理任务都非常重要,并且在大多数脚本中使用,仍然依赖于安装最新的 Windows Management Framework。

Powershell 比 Python 真正好得多的地方在于利用外部 C 和 .NET 库。使用 .NET 程序集相对容易并且是原生操作。(记住:Powershell 是一个 .NET 应用程序,带有一个 .NET JIT 编译器。您的 Powershell 代码会被动态编译成 .NET 字节码)。调用“经典”Win32 API(C 库)稍微复杂一些,但仍然比在 Python 中为 ctype 创建结构容易得多。

很高兴看到 Powershell 自 5 / 5.1 版本以来的发展方向。它比以往任何时候都更不像一个 shell,更像我更愿意称之为“.NET 脚本”的东西。
- 首先,Powershell 语言现在能够支持类 / OOP
- 然后,Powershell 现在能够运行和执行“C#”代码(无需将其编译为 .NET 字节码文件 - 它内置了一个 .NET JIT 编译器)

在使用 *nix shell 多年之后,我现在正在学习 PowerShell。虽然新的开源方法正在推动它,但我对文章的初始前提有一个问题——(1) 如果你使用一个特定的 shell(比如 BASH,或者 SH 如果你足够勇敢 ;),你确实可以在各个平台上保持一致 (2) 另一方面,阅读文章中引用的参考文献之一,它宣布某些功能在 4.0 版本中不再受支持。“随时/随地都一样”呢?

Bash/sh 可以在各个平台上提供一致性,但它们调用的实用程序有时不能。

我并不声称 powershell 从不弃用或修改功能,或者所有版本的语法都相同。但是,您可以通过简单地将正确版本的 Powershell(6.0 或更高版本)与您的脚本捆绑在一起来确保在执行中获得相同的功能和语法。换句话说,您的应用程序可以拥有其自动化环境,而不是依赖操作系统来提供它。这就是您保证一致性的方式。

回复 作者 Erez L. (未验证)

但是,即使微软可能会投资于从其自己的 repo 为 powershell 安装提供 .deb 和 .rpm,但只有当它可以从 DEBIAN/REDHAT/CENTOS 的 STANDARD/main REPOSITORY 本身获得时,组织和人们才会在认真的项目中使用更多数量。
例如:(非常非常重要)
看看 debian 维护人员对将 powershell 包含在 debian main 存储库中的一些担忧
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=834756#16

在上述问题得到解决并包含在 main repo 中之前,它不会确保/证明最终用户以开源兼容的方式,并且没有任何专利,达到最后一个构建链,满足了 powershell 编译的所有先决条件。

恕我直言,信任微软是对历史的直接打击。该公司现在“对开源友好”仅仅是因为微软时代正处于危险之中。最好相信其他所有人在自由软件不确定并且主要是一种道德哲学的时候就有了主动性,对于他们来说,*人*比*钱*更重要。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.