Powershell7 研讨会(一)

原文:annas-archive.org/md5/a7c5d98eb6dd2beb9d7fc74516c7725b

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

PowerShell 是一种免费的、强大且易于学习的编程语言。最初它作为 Windows 的脚本和管理工具编写,现在作为一种开源资源,几乎可以在所有笔记本和台式机上安装。我在过去十年里一直在向同事们教授 PowerShell,并且在空闲时间里,我还在本地学校教孩子们编程,主要是 Python。为什么不通过 PowerShell 来教编程呢?

许多关于 PowerShell 的书籍和课程假设读者能够访问多台机器、Active Directory 域以及各种其他企业环境。它们通常也会低估 PowerShell 中的传统编码元素。本书既不做这些假设,也尝试用类似于我们教授 Python 编程的方式来教授 PowerShell 编程。我受到了密歇根大学 Chuck Severance 博士的杰出工作的启发——如果你想学习 Python,他的 Python for Everybody 课程在 py4e.org 上非常出色。

本书分为三部分。第一部分,我们介绍传统的编码理论;从 PowerShell 作为一种语言的工作原理开始,探讨语言的基本构件,然后讨论如何将这些构件结合在一起形成程序流程。

在第二部分,我们开始将我们学到的原则整合到脚本和模块中,这样我们就可以共享和重用它们。

在本书的最后部分,我们将探讨 PowerShell 在不同环境中的工作原理,最后以一章关于如何访问 PowerShell 所基于的底层框架作结。

我在书中包含了许多有趣且多样的例子和练习。为了最大限度地从中受益,我鼓励你亲自输入代码,而不仅仅是阅读它;打字的实际行为能比单纯浏览它带来更深的参与感。尝试一下问题和活动,在跳到答案之前,好好思考这些问题。如果你需要稍微动脑筋,你会从练习中获得更多的收获。

我非常想听听你的看法,以及你对如何改进本书的任何建议。

本书适合谁阅读

本书是为那些想学习编写代码并希望使用 PowerShell 学习的人准备的。这可能是想尝试不同的学校学生、想提升技能的 IT 工程师、爱好者、创客……所有人都适用。如果你是一个有经验的程序员,想将 PowerShell 加入你的技能清单,那么这本书可能不适合你;如果你已经会编写 Java、C++ 或 C#,那么你可能更适合参考 Chris Dent 所著的 Mastering PowerShell Scripting,由 Packt 出版。

本书涵盖的内容

第一章PowerShell 介绍 – 它是什么以及如何获取它,解释了 PowerShell 7 的定义,描述了它的用途以及与 Windows PowerShell 的区别。它描述了如何获取它、如何安装它以及如何运行它,解释了用户模式和管理员模式的区别。它还介绍了如何运行 cmdlet,以及如何在 PowerShell 中获取帮助。

第二章探索 PowerShell Cmdlet 和语法,重点介绍了 PowerShell cmdlet 的工作原理、批准的动词、参数、如何通过 PowerShell Gallery 以及互联网其他地方查找新的 cmdlet,最后介绍如何与 PowerShell 进行交互式操作。

第三章PowerShell 管道 – 如何串联 Cmdlet,介绍了 PowerShell 管道是 PowerShell 中最重要的概念之一,与 Bash 和命令提示符中的管道工作方式有很大不同。本章将向你展示如何成功地将 cmdlet 串联在管道中以产生有用的结果。它将讨论过滤、输出、管道的工作原理,以及管道为什么有时无法正常工作!

第四章PowerShell 变量和数据结构,介绍了变量以及它们可能的不同类型,包括整数、浮动数,以及它们如何都是对象。我们将探索对象的概念及其重要性。我们将查看数据结构作为对象的集合,然后是数组和哈希表,最后介绍 splatting。

第五章PowerShell 控制流 – 条件语句和循环,介绍了条件流(*IF* 这个,*THEN* 那个)和循环,包括 foreachwhile 循环。通常,你不希望以线性方式处理 cmdlet——你只会在另一个条件成立时才执行某些操作,或者对管道中的所有对象执行某些操作。控制流正是实现这一点的方式。本章还将带领我们从执行交互式 cmdlet 转向在 VS Code 中编写非常简单的脚本。

第六章PowerShell 和文件 – 读取、写入和操作数据,教你如何从常见文件类型(如 CSV 和 TXT 文件)中提取数据并进行操作,以及如何将输出发送到文件,从而避免必须从屏幕上读取输出并不断输入的乏味。我们还将讨论如何输出到 HTML,这对于创建格式化报告和在网页托管的仪表板中显示实时数据非常有用。

第七章PowerShell 与 Web – HTTP、REST 和 JSON,探索了 PowerShell 与 Web 的关系。文件很好,但许多云端管理需要处理来自互联网的数据;为此,我们需要能够操作最常见的互联网数据类型——JSON。我们还需要操作云服务,因此需要能够使用 REST API 调用。本章将详细讲解这一部分内容。

第八章编写我们的第一个脚本 – 将简单的 Cmdlet 转化为可重用的代码,重点讲解了如何将几行代码转化为我们可以保存并反复运行的脚本。我们已经了解了如何在 IDE 中编写几行代码。接下来,如何将这些代码转化为我们希望反复运行的脚本,并使其对其他人有用?

第九章不要重复自己 – 函数与脚本块,介绍了 PowerShell 中的函数以及脚本块和 Lambda 表达式。当编写脚本时,我们经常会遇到需要多次运行相同几行代码的情况。将它们转化为脚本中的函数意味着我们只需要编写一次,之后每次需要时只需调用即可。

第十章错误处理 – 哎呀!出错了!,涵盖了我们可能遇到的两种主要错误类型——代码执行中遇到的问题和代码本身的问题。在本章的第一部分,我们将定义什么是错误,如何设置 PowerShell 来优雅地处理错误,以及如何理解这些错误。在第二部分,我们将探讨如何识别代码中的问题,并使用 VS Code 进行调试。

第十一章创建我们的第一个模块,探讨了如何将函数和脚本转化为可重用的模块,方便分发并能够整合到其他脚本中。现在我们已经有了一个包含一组函数的脚本,下一步是将它转化为其他人也能使用的工具。

第十二章保护 PowerShell,深入探讨了如何保护我们的 PowerShell 脚本和模块,并以安全的方式运行它们。PowerShell 是一个非常强大的工具,强大的力量伴随着巨大的责任。本章将涵盖脚本执行策略、代码签名、AppLocker 和其他一些安全功能。

第十三章在 PowerShell 7 和 Windows 上工作,探讨了如何在 Windows 上使用 PowerShell 7,我们何时需要使用 PowerShell 5.1,如何使用 WinRM 与远程机器交互,如何通过 CIM 管理机器,以及与 Windows 特性(如存储)的基本交互。PowerShell 起源于 Windows,PowerShell 7 的目标是最终取代 Windows PowerShell,但目前我们还没有完全过渡到那一步。

第十四章PowerShell 7 for Linux and macOS,解释了如何在 Linux 上安装 PowerShell,它与在 Windows 上运行 PowerShell 的区别,以及如何在 Linux 上使用 VS Code。它还解释了如何使用 OpenSSH 进行远程操作,如何运行脚本以及一些常见的管理任务。最后,它介绍了如何在 macOS 上安装和运行 PowerShell 及 VS Code。

第十五章PowerShell 7 与 Raspberry Pi,探讨了如何在 Raspberry Pi 上开始使用 PowerShell,让我们能够进行家庭自动化、创客项目等。它包括 PowerShell 和 VS Code 的安装,连接 Pi,以及运行脚本。Raspberry Pi 是大家最喜爱的单板计算机,我们可以将 PowerShell 技能转移到我们的 Pi 设备上。

第十六章与 PowerShell 和.NET 的工作,深入探讨了.NET,这是 PowerShell 7 构建的平台;它是免费的、开源的,并且与 VS Code 兼容。仅使用 PowerShell 我们无法轻松完成许多任务,但我们可以借助.NET 来实现这些目标。熟悉.NET 是每个高级 PowerShell 程序员的必备技能,本章将帮助你掌握这项技能。

如何从本书中获得最大收益

本书假设你有一台客户端计算机,可能是笔记本电脑或 PC,运行的是 Windows、Linux 或 macOS。然而,如果条件紧张,你也可以几乎用 Raspberry Pi 完成所有任务。它还假设你有互联网连接。大部分练习将在 Windows、Linux 和 macOS 上运行;如果有不兼容的地方,会特别标出。阅读第一章,它包含了 Windows 的安装说明,之后根据 Linux/macOS 或 Raspberry Pi 章节中的安装指南操作。

本书涵盖的软件/硬件操作系统要求
PowerShell 7.xWindows、macOS 或 Linux

如果你使用的是本书的电子版,我建议你自己输入代码。错误是学习的一部分,当我 意识到 自己做错了什么时,那种成就感是你不想错过的。

使用的约定

本书中使用了一些文本约定。

文本中的代码:表示文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号。例如:“应用程序默认安装在C:\Users\<username>\AppData\Local\Programs\Microsoft VS Code,并且仅对安装该应用程序的用户可用。”

一块代码如下所示:

$x = 5
if ($X -gt 4) {
    Write-Output '$x is bigger than 4'
}

当我们希望引起你对代码块中特定部分的注意时,相关的行或项目会以粗体显示:

$Array  = 1,2,3,4,5
switch ($Array) {
    1 {Write-Output '$Array contains 1'}
    3 {Write-Output '$Array contains 3'}
    6 {Write-Output '$Array contains 6'}
}

任何命令行输入或输出均如下所示:

sudo apt update
sudo apt install ./<filename>.deb

粗体:表示新术语、重要词汇,或您在屏幕上看到的词语。例如,菜单或对话框中的词语会以粗体显示。以下是一个例子:“在选择附加任务对话框中,选择是否需要桌面图标以及文件和目录上下文菜单选项,这样您就能直接在 VS Code 中打开文件和文件夹。”

提示或重要事项

以这种方式显示。

联系我们

我们总是欢迎读者的反馈。

一般反馈:如果您对本书的任何部分有疑问,请通过电子邮件联系我们:customercare@packtpub.com,并在邮件主题中提及书名。

勘误:尽管我们已尽力确保内容的准确性,但错误难免发生。如果您在本书中发现错误,我们将非常感激您能报告给我们。请访问www.packtpub.com/support/errata并填写表格。

盗版:如果您在互联网上发现任何我们作品的非法版本,我们将感激您提供该地址或网站名称。请通过 copyright@packt.com 与我们联系并提供该资料的链接。

如果您有兴趣成为作者:如果您在某个领域具有专业知识,并且有意为书籍写作或贡献内容,请访问authors.packtpub.com

分享您的想法

阅读完PowerShell 7 Workshop后,我们很乐意听取您的想法!请点击这里直接进入亚马逊评论页面分享您的反馈。

您的评论对我们和技术社区非常重要,它将帮助我们确保提供高质量的内容。

下载本书的免费 PDF 版本

感谢您购买本书!

您是否喜欢随时随地阅读,但又无法随身携带印刷书籍?

您的电子书购买是否与您选择的设备不兼容?

不用担心,现在购买每本 Packt 书籍时,您都可以免费获得该书的无 DRM PDF 版本。

在任何地方、任何设备上阅读。搜索、复制并粘贴您最喜爱的技术书籍中的代码,直接将其应用到您的程序中。

福利不仅仅止步于此,您还可以独享折扣、新闻通讯和每日发送到邮箱的精彩免费内容。

按照以下简单步骤获取这些福利:

  1. 扫描二维码或访问以下链接

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_QR_Free_PDF.jpg

packt.link/free-ebook/9781801812986

  1. 提交您的购买凭证

  2. 就是这样!我们会将您的免费 PDF 和其他福利直接发送到您的邮箱

第一部分:PowerShell 基础

在这一部分,我们将学习 PowerShell 的基础知识,包括语言语法如何运作,如何通过管道将一个操作的结果直接传递到另一个操作,PowerShell 如何使用对象、变量和数据结构,如何创建分支和循环代码,最后,我们如何读写存储文件。

本部分包含以下章节:

  • 第一章PowerShell 入门 – 它是什么以及如何获取它

  • 第二章探索 PowerShell Cmdlet 和语法

  • 第三章PowerShell 管道 – 如何将 Cmdlet 串联起来

  • 第四章PowerShell 变量和数据结构

  • 第五章PowerShell 控制流 – 条件语句与循环

  • 第六章PowerShell 与文件 – 读取、写入和操作数据

  • 第七章PowerShell 与 Web – HTTP、REST 和 JSON

第一章:PowerShell 7 介绍 – 它是什么以及如何获取它

简单来说,PowerShell 是一台时光机。不是那种科幻电影里的时光机,你可以回到过去见到自己的祖父,而是一台真正的、实用的时光机。如果你投入少量时间,那么 PowerShell 就像任何简单的机器一样,能够起到倍增作用;它会为你节省大量的时间。用个比喻,它是一把时间锤子,你投入学习 PowerShell 的时间将为你节省数十倍甚至上百倍的时间,一旦你开始将这些知识付诸实践。

本章是 PowerShell 7 的概述介绍。它将为你提供 PowerShell 的背景知识,并帮助你启动和运行。你将学习如何使用 PowerShell 以及一些典型的使用案例。我们将安装 PowerShell,你将选择一种或多种方式进行安装。安装完成后,我们将介绍如何运行命令(称为cmdlet),以及如何查找可以运行的 cmdlet。最后,也非常重要的是,我们将介绍如何获取帮助,包括获取 cmdlet 以及 PowerShell 主题和概念的帮助。

本章将涵盖以下主要内容:

  • PowerShell 7 是什么?

  • PowerShell 7 用于什么?

  • 获取 PowerShell 7

  • 运行 PowerShell 7

  • 获取帮助

技术要求

为了跟上本章的内容,你需要一个互联网连接和操作系统。如果你使用的是 Linux 或 macOS,安装说明可以在第十四章**, Linux 和 macOS 上的 PowerShell 7 中找到,因此跳过本章 如何获取 PowerShell 7 部分的详细安装说明。

本章假设你将使用运行在标准 64 位 x86 架构上的 Windows 10(版本 1709 或更高版本)。如果你不确定自己的系统是否符合这个要求,不用担心,应该是的。如果你是那种总是担心的人,可以打开 Windows 搜索框,输入 msinfo32,然后按 Enter系统信息应用程序将打开,在系统摘要下,你会看到三行相关信息:

  • 操作系统名称:希望是某种版本的微软 Windows 10;PowerShell 7.3 可在所有当前支持的 Windows 版本上使用。

  • 版本:你需要一个比 16299 更高的版本号。

  • 系统类型:可能是基于 x64 的 PC

以下截图显示了在系统摘要下的样子:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_001.jpg

图 1.1 – 系统信息应用程序(msinfo32)中的典型信息

如果你使用的是 Windows 11,那么恭喜你;你不需要做一些我们将讨论的事情,因为 Windows 11 提供了一些额外的功能。

PowerShell 7 是什么?

PowerShell 是一种脚本语言,是命令行接口的替代工具。PowerShell 是一个自动化工具,至少由三部分组成:

  • 一个 shell,类似于 Windows 中的命令提示符或 Linux 或 macOS 中的终端

  • 一个脚本语言

  • 一个名为 Desired State Configuration (DSC) 的配置管理框架

实际上,当我们谈论 PowerShell 时,通常是指脚本语言。正如我们所看到的,Shell 的使用对用户来说是直观的,尽管我们稍后会讨论 DSC,但根据我的经验,大多数人并没有像应该的那样充分使用它。

PowerShell 的第一个版本源自一个名为Monad的项目,这是 Jeffrey Snover 试图在 Windows 上复制 Unix 工具的尝试。他意识到 Unix 工具的一个基本缺点是它们输出的是字节流(通常是文本),因此在你能够对输出结果进行操作之前,很多努力都浪费在搜索、格式化和提取命令输出上。Monad 被写成输出对象,这些对象可以直接输入到另一个命令中。我们将在 第四章,《PowerShell 变量和数据结构》中详细讨论这一点。PowerShell 1.0 于 2006 年发布,但依我看,直到 PowerShell 2.0 在 2009 年发布,且微软开始重新设计主要软件(如 Exchange Server 2010)的管理界面以便利用 PowerShell 时,它才真正起步。其他看法也是存在的。

在撰写本文时,PowerShell 主要有两种版本Windows PowerShell,它随 Windows 的服务器版和桌面版捆绑在一起,以及PowerShell 7,它需要下载并安装。Windows PowerShell 的最新(且 allegedly 最终)版本 v5.1 构建在 .NET Framework 4.5 上,这是与 Windows 一起捆绑的专有软件框架,并支撑了微软的许多产品。PowerShell 7.0 是基于 .NET Core 3.1 构建的,这是一个简化版的、开源实现的 .NET。然而,自从 7.2 版本起,PowerShell 已经基于 .NET 6.0 构建。这个统一版本的 .NET 代替了 .NET Framework 和 .NET Core,并在 2020 年 11 月发布。

由于 Windows PowerShell 5.1 和 PowerShell 7.x 之间的根本差异,它们在 Windows 平台上的工作方式可能会有所不同。我们将在 第十三章,《在 PowerShell 7 和 Windows 中工作》中讨论这些差异。

我们将总结一些关键差异,并在以下表格中列出:

参数Windows PowerShellPowerShell 7.2
平台仅限 x64, x86x64, x86, arm32, arm64
操作系统WindowsWindows、Linux、macOS
.NET 版本.NET Framework 4.5.NET 6.0
许可证类型专有开源
本地命令数量1588(在原生 Windows 10 中)1574(在原生 Windows 10 中)290(在 Ubuntu 20.04 中)

表 1.1 – Windows PowerShell 和 PowerShell 7 之间的一些差异

在这一部分中,我们已经介绍了 PowerShell 是什么,以及它与 Windows PowerShell 的区别。在下一部分中,我们将探讨 PowerShell 7 的存在意义,并看看它有什么特别之处。

PowerShell 7 用于什么?

PowerShell 是为了快速完成任务。它适用于当你需要一个相对较短的代码片段,并且可以轻松地重用和重新设计来完成其他任务时。它适用于当你不想花几个月时间学习一门语言,再花更多的时间编写成千上万行代码时。这个语言至少可以有四种使用方式:

  • 你可以像在 Windows 命令提示符或 Linux 终端中一样,在 shell 中输入单行代码。如果你需要检查一个值、执行一个单一任务(如重启远程计算机)或抓取日志文件时,这非常有用。

  • 你可以编写一个脚本,比如在 Linux 中的 Bash 脚本或 Windows 中的批处理文件,用来完成多个子任务,例如从几台机器中收集事件日志和性能信息,并将它们汇总成一个单一的 HTML 报告。

  • 如果你编写了很多脚本或需要完成一些更复杂的任务,你可以将 PowerShell 用作一种过程式编程语言,使用多个封装的脚本,每个脚本描述一个单一的功能,并由主脚本调用。

  • 你可以将它作为一种面向对象的编程语言,封装一个完整的应用程序,该程序可以重新分发并由任何安装了 PowerShell 的人运行。

本书将重点讲解脚本和过程式编程,因为这是大多数人使用 PowerShell 的方式。这两者非常相似;不同之处在于,在脚本中,你使用的是已经为你编写的 cmdlet,而在过程式编程中,你正在创建自己的 cmdlet,可能是从现有的 cmdlet 中创建,或使用系统编程语言 C# 来创建。

脚本语言与系统编程语言

PowerShell 语言是一种脚本语言。它用于快速且轻松地将其他应用程序粘合在一起——有点像编程版的乐高。它依赖于一个底层的解释器:PowerShell 程序。如果没有安装 PowerShell,PowerShell 脚本无法运行。这与其他解释型语言(如 Python)类似,并且与系统编程语言(如 C 或 C++)形成对比,后者被编译成可执行文件。当你编译一个 C++ 程序时,它理论上可以在任何兼容的机器上运行。还有其他差异——以下是一些主要的区别:

  • 解释型语言比编译型语言效率低,因为每一行代码都必须在运行之前进行解释。这意味着它们比编译程序要慢。虽然有一些编程技巧可以加速,但在解释型语言中执行任务通常会比在编译型语言中执行要慢。

  • 解释语言在开发中比编译语言更有效率。它们可以用更少的代码完成相同的任务。这意味着编写、调试和重用这些代码会更快。它们也更容易学习。

  • 解释语言可以在多种架构上运行。正如本书中所示,使用 PowerShell 编写的代码可以在 Windows、Linux 或 macOS 上运行,只需进行最少的调整。而用 C++ 编写的程序只能在 Windows 上运行,或者在具有 Windows 模拟的机器上运行。它需要重新编写和重新编译以在不同平台上运行。

  • 解释语言生成协作可重用的程序。使用 PowerShell(或 Python),您可以生成人类可读且可编辑的代码。而使用编译语言,您生成的是无法轻易反编译为源代码以便重用的二进制文件。这意味着其他人可以为自己的目的重用您的代码。像 GitHub 这样的平台可以用来分发您的代码,其他人可以为其做出贡献、改进它、将其重用于其程序,并以一种普遍的社区方式行事。

归根结底是这样的:如果您想编写一个具有壮观图形的超快第一人称射击游戏,那么 PowerShell 可能不适合您。如果您想要自动化一些任务,无论是简单还是复杂,PowerShell 都是一个不错的选择。

获取 PowerShell 7

在本节中,我们将探讨将 PowerShell 安装到计算机上的一些方法、安装位置及其原因,以及如何控制安装的各个方面。本章仅涵盖 Windows 上的安装;有关 Linux、macOS 和 ARM 系统的详细安装,请参阅第十四章Linux 和 macOS 上的 PowerShell 7,或第十五章PowerShell 7 和树莓派,然后返回本章的后两节。

您的计算机上可能同时运行多个 PowerShell 版本 - 我通常一次运行三个版本:Windows PowerShell、PowerShell 7(当前版本)和 PowerShell 7 预览版。这不仅适用于我写书时使用 - 我们需要确保我们编写的脚本可以在不同的环境中运行,并在必要时重新编写它们。当您计划在尚未安装 PowerShell 的远程计算机上运行 PowerShell 时,控制安装也非常有用。Windows PowerShell 包含在 Windows 操作系统中,并安装在 \Windows\system32 文件夹中;它的存在位置不可更改。相比之下,PowerShell 7 可以根据需要安装在任何地方。我们将介绍安装的三种最常见方法:

  • 使用 Windows 安装程序从 .msi 文件安装

  • .``zip 文件安装

  • 使用 winget 安装

我们将简要介绍另外两种方法:从 Microsoft Store 安装和安装为 .NET 全局工具。

如果你想做些实验,并且你有 Windows 10 专业版或企业版,那么可以在 控制面板 | 程序和功能 | 启用或关闭 Windows 功能 中启用 Windows Sandbox 功能。

这将为你提供一个完全空白、安全的 Windows 环境供你尝试。请小心——当你关闭它时,它就会永久消失。下次启动时,你所有的更改将会丢失:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_002.jpg

图 1.2 – 开启 Windows Sandbox

有关运行 Windows Sandbox 的详细要求,请参见此链接:docs.microsoft.com/en-us/Windows/security/threat-protection/Windows-sandbox/Windows-sandbox-overview

让我们开始吧。请确保你已经满足章节开始部分列出的技术要求。

从 .msi 文件进行安装

所有官方的 PowerShell 发行版都可以在 PowerShell 的 GitHub 页面找到:github.com/PowerShell/PowerShell

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_003.jpg

图 1.3 – 从 GitHub 页面获取 PowerShell

如你所见,对于大多数操作系统和平台,有三种版本发布类型:LTS稳定版预览版LTS 代表 长期支持。LTS 版本的发布速度较慢,旨在确保在风险规避的环境中保持稳定,它们通常仅包含关键的安全更新和软件修复,而不会加入新功能。PowerShell 的 LTS 版本基于 .NET 的 LTS 版本。预览版是 PowerShell 的下一个版本,可能会有令人兴奋的新特性,但也可能不稳定,存在一些缺陷。稳定版每月更新一次,可能包含新功能,以及软件修复和安全更新。每个版本在发布下一个版本后,支持六个月。

让我们继续安装最常见的版本——适用于 Windows x64 的稳定版:

  1. 在这里浏览 PowerShell 的 GitHub 发布页面:github.com/PowerShell/PowerShell

  2. 点击下载适用于 Windows x64 的稳定 .msi 安装包。

  3. 在你的 Downloads 文件夹中找到 .msi 文件并运行它,这将启动安装向导。

  4. 你需要做的第一个选择是安装位置。默认情况下,它会安装到 C:\Program Files\PowerShell 下的一个编号文件夹中,其中编号与主版本号相匹配——在我们这个例子中是 7。如果你安装的是预览版,那么文件夹名会带有 -preview 后缀。这是一个相当不错的位置,但如果你同时运行多个版本的 PowerShell,可能会希望将其安装到其他位置。此次就接受默认设置吧:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_004.jpg

图 1.4 – 默认安装位置

  1. 现在我们进入了可选操作菜单:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_005.jpg

图 1.5 – 可选操作

这里有五个选项:

  • 使用 pwsh.exe 来运行此命令。

  • 注册 Windows 事件日志清单:您也应该启用此选项。这将创建一个新的 Windows 事件日志,名为PowerShell Core,并开始记录 PowerShell 事件。

  • 启用 PowerShell 远程功能:启用 PowerShell 远程功能将使计算机监听来自 PowerShell 会话的传入连接。显然,这在安全上存在一些漏洞,因此只有在需要时并且您的计算机处于私有网络中时才应启用它。您不需要启用它来连接到其他计算机的远程会话。

  • 在资源管理器中添加“在此打开”上下文菜单:这将允许您在文件资源管理器中的文件夹中打开 PowerShell 会话——PowerShell 会话将打开,并且路径会设置为您选择的文件夹。

  • 为 PowerShell 文件添加“使用 PowerShell 7 运行”上下文菜单:这将允许您右键单击文件并使用 PowerShell 7 打开它。由于后面我们将看到的原因,这可能并不总是可取的。

  1. 可选操作之后,我们进入了Microsoft 更新选项。您可以使用 Microsoft 更新来保持 PowerShell 的最新状态;强烈建议启用此选项,因为它可以自动为您下载安全补丁并根据现有的更新计划应用它们。请注意,如果您在域环境中工作,此设置可能会被组策略覆盖。有两个复选框,第一个用于启用 PowerShell 更新,第二个用于启用系统上的 Microsoft 更新。请注意,取消选中此框只会禁用 Microsoft 更新;如果您的环境使用了如Windows 软件更新服务WSUS)或系统中心配置管理器SCCM)等配置管理工具,它们仍会继续工作。

  2. 最后,我们准备通过点击安装按钮来安装。这个过程很短,应该在一两分钟内完成。点击完成,我们就完成了安装。

有一种替代方法,不使用 GUI。您可以通过命令行使用 msiexec.exe 运行 .msi 文件,具体文档见此处:docs.microsoft.com/en-gb/powershell/scripting/install/installing-powershell-on-Windows?view=powershell-7.2#install-the-msi-package-from-the-command-line

如您刚刚所示,要在 Windows 沙盒中静默安装 PowerShell,您可以运行以下命令:

msiexec.exe /package c:\Users\WDAGUtilityAccount\Downloads\PowerShell-7.2.1-win-x64.msi /quiet REGISTER_MANIFEST=1 USE_MU=1 ENABLE_MU=1

请注意,命令行没有启用或禁用 .msi 文件的属性,因此 PowerShell 会自动添加。由于我们使用了 /quiet 开关,此命令不会有输出,但如果成功,您将会在开始菜单中看到 PowerShell。

活动 1

如何在使用命令行从 .msi 文件安装 PowerShell 时启用文件上下文菜单?(提示:查看前面段落中的链接了解详情。)

.zip 文件安装

另一种流行的安装 PowerShell 的方式是通过 .zip 文件。使用这种方法,我们只是将二进制文件和相关文件提取到合适的文件夹中。缺点是,你将失去 .msi 安装时的前置条件检查和选项;例如,你不能自动将 PowerShell 添加到 PATH 环境变量中或启用 PowerShell 远程功能。优点是,它使得在 DevOps 或基础设施即代码管道中脚本化安装 PowerShell 变得更容易,并且可以在脚本中启用其他功能。

在 Windows 中,没有原生的方式通过脚本从互联网安装文件。你需要已经安装了 PowerShell(在 Windows 机器上,Windows PowerShell 会自动安装),或者安装一个像 curl 这样的工具。

这是你用 Windows PowerShell 执行的方式:

Invoke-WebRequest https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x64.zip

如果你运行前面的 cmdlet,你应该会看到如下输出。注意,这是一个 HTTP 响应,因此 StatusCode 结果为 200 是好的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_006.jpg

图 1.6 – 使用 Windows PowerShell 下载 PowerShell 7

你可以像这样通过四行代码运行整个过程:

New-Item -Path c:\scratch\myPowershell\7.2 -ItemType Directory
Invoke-WebRequest https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x64.zip -OutFile C:\scratch\myPowershell\7.2\PowerShell-7.2.1-win-x64.zip
Expand-Archive C:\scratch\myPowershell\7.2\PowerShell-7.2.1-win-x64.zip -DestinationPath C:\scratch\myPowershell\7.2\
Remove-Item C:\scratch\myPowershell\7.2\PowerShell-7.2.1-win-x64.zip

不要太担心前面的命令——我们稍后会逐一讲解。总的来说,第一行创建一个新文件夹,第二行从 GitHub 下载 .zip 包到你新建的文件夹中,第三行解压所有文件,准备好运行,第四行则删除下载的包。

你可能会遇到两个错误。首先,你可能会看到一个红色的错误信息:

Invoke-WebRequest : The request was aborted: Could not create SSL/TLS secure channel"

这是因为,默认情况下,Windows PowerShell 会使用 TLS v1.0,而许多网站不再接受此协议。如果你看到这个错误,运行以下 .NET 代码并再试一次:

[Net.ServicePointManager]::SecurityProtocol = "Tls, Tls11, Tls12, Ssl3"

另一个你可能会看到的错误信息是:

Invoke-WebRequest : The response content cannot be parsed because the Internet Explorer engine is not available, or Internet Explorer's first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again

在这种情况下,使用 –``UseBasicParsing 参数运行 Invoke-WebRequest cmdlet:

Invoke-WebRequest https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x64.zip -UseBasicParsing

将脚本中的第二行替换为这一行。它与之前的完全相同,但增加了 –``UseBasicParsing 参数。

使用 winget 安装

.exe.msi.msix 包 – 你不能用它来安装 .zip 版本。当你运行 winget 时,它会搜索、下载并安装你选择的 PowerShell .msi 版本。操作如下:

  1. 首先,运行 PowerShell 包的搜索。从 Windows PowerShell 命令提示符下,运行以下命令:

    winget search microsoft.powershell
    

这将返回 winget 可用的 PowerShell 版本。

  1. 然后,你需要安装一个包。我选择通过运行以下命令来安装预览版:

    winget install --id microsoft.powershell.preview --source winget
    

就是这样。这里有几点需要注意。首先,你正在安装 .msi 文件,因此除非你抑制它们,否则会看到多个图形界面消息。你可以使用 --silent 开关来抑制这些消息。除非你接受默认选择,否则你还需要一种方法来向调用的 .msi 文件传递参数。你可以使用 --override 开关,然后传递我们之前查看过的 .msi 包的命令行开关。其次,如果你启用了用户访问控制(UAC),你需要允许 PowerShell 安装。如果你使用 --silent 开关,那么你不会看到这个提示。如果你想进行静默安装,你需要以管理员权限运行 Windows PowerShell。

如果你以管理员权限从 Windows PowerShell 命令行运行安装命令,整个安装过程如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_007.jpg

图 1.7 – 使用 winget 静默安装 PowerShell

winget 的主要优势是它有自己的社区创建的包管理库;任何人都可以通过编写清单并上传来打包一个应用。该库使用微软的 SmartScreen 技术加以保护,以防恶意代码进入库中。关于 winget 还有很多内容,请参考这里:

docs.microsoft.com/zh-cn/Windows/package-manager/winget/.

总结来说,你通过 winget 所做的事情其实与之前通过运行 msiexec.exe 所做的没有什么不同,只是它稍微新一些、更酷,并且拥有一个有用的包管理库,使用起来也稍微容易一些。几年后,我们会想,曾经没有它时我们是怎么做的,尤其是如果它在 Windows 服务器上也可用的话。

其他安装方式

我们应该讨论的还有另外两种安装 PowerShell 的方式。两者都不太可能适用于我们。首先,如果你已经安装了 .NET 软件开发工具包SDK),那么我们可以使用它将 PowerShell 安装为一个全局工具。这个方法实际上只对软件开发人员有用,而且仅为了 PowerShell 安装 SDK 并不太有意义。

另一种在 Windows 上安装 PowerShell 的方式是通过微软商店作为应用安装。此方法的主要缺点是商店应用运行在一个沙盒环境中,这限制了对应用根文件夹的访问。这意味着一些 PowerShell 功能将无法正常工作。

注意

沙盒 不一定等同于 Windows 沙盒。术语“沙盒”通常指的是一个具有独立资源的安全计算环境,这意味着运行在其中的任何东西都不能干扰沙盒外的内容。Windows 沙盒是沙盒的一种特定实现。

运行 PowerShell 7

每个人运行 PowerShell 的第一种方式是通过捆绑的控制台(记住,PowerShell 不仅仅是一种语言,它还是一个 shell 和配置管理框架)。假设你使用之前的 .msi 方法进行安装,并将 PowerShell 添加到了 PATH 环境变量中。如果你已经这么做了,那么启动 PowerShell 只需要在 Windows 搜索框中输入 pwsh 并点击应用程序。或者,你也可以右键点击 开始 菜单,在 运行 框中输入 pwsh。或者,你也可以按住 Windows 键并按 R,这也会打开 运行 框。

如果你没有将 PowerShell 添加到路径中,你将需要输入完整的可执行文件路径 pwsh,例如 C:\program files\PowerShell\7\pwsh

如果你关注了前面的截图,或者跟随步骤操作,你首先会注意到控制台窗口的背景是黑色的。这将其与 Windows PowerShell 区分开来:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_008.jpg

图 1.8 – 两种不同版本的 PowerShell

如果你在 Linux 或 macOS 上安装了 PowerShell,那么请打开终端并输入 pwsh —— 终端会切换到 PowerShell 提示符。

在大多数编程书籍中,传统的第一步是让你的应用程序在屏幕上显示 "Hello World" 字样,我们也不例外。请在控制台中输入以下内容并按 Enter 键:

Write-Host "Hello World"

你应该会看到类似这样的内容:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_009.jpg

图 1.9 – 你好,自己

如果你完成了,恭喜你!你刚刚运行了第一个 PowerShell 命令,或者叫做 cmdlet。

注意,控制台会自动为它识别或期望的内容着色;cmdlet 是黄色的,字符串是蓝色的。它也非常宽容——如果你忘记了引号,"Hello World" 将不会是蓝色的,但 PowerShell 仍然会正确地解析它。

注意

小心使用这一点;虽然 PowerShell 非常智能,但它并不总是按你希望的方式解读输入。最好明确告诉它你正在输入的是什么类型的内容。稍后会详细讲解。

错误最常见的原因是你拼写错了 cmdlet 或者没有关闭引号,如下图所示。你会看到一个有帮助的红色错误信息,告诉你哪里出错了,并建议你如何修复它。我已经开始珍惜这些错误信息了:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_010.jpg

图 1.10 – 三种错误方式

在第三次尝试时,我没有关闭引号,因此 PowerShell 期望更多输入。它通过下面一行的 >> 告诉我们这一点。它还通过将命令提示符中的 > 标记为红色,告诉我们它认为命令行不会按你写的那样运行。

请注意,不像某些环境中那样,这里大小写不重要;write-hostWrite-Host 在功能上是相同的。

以管理员权限运行 PowerShell

默认情况下,PowerShell 会在启动它的帐户下运行,但它只会拥有标准用户权限。如果你需要访问通常需要管理员权限的本地机器,那么 PowerShell 会无法运行某些 cmdlet,或者会弹出 用户帐户控制UAC)提示。为了避免这种情况,你需要以管理员权限启动 PowerShell。这里有很多方法来实现,但以下是两种最常见的方法。

首先,你可以打开搜索栏,输入pwsh,然后右键点击 PowerShell 7 图标并选择以管理员身份运行

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_011.jpg

图 1.11 – 以管理员身份启动 PowerShell

第二种稍微更强大的方法是按下Windows键 + R 打开 pwsh,然后按住 Ctrl + Shift + Enter,这将以管理员身份启动 PowerShell。

PowerShell 会清楚地在窗口标题中显示它是否以管理员权限运行。以下是两个 PowerShell 会话运行相同 cmdlet 的情况。下方的窗口是以管理员权限运行的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_012.jpg

图 1.12 – 有时你需要是管理员

如果你经常需要使用管理员模式,最简单的办法是右键点击正在运行的 PowerShell 图标并选择固定到任务栏。然后,你可以在需要时右键点击固定的图标并选择以管理员身份运行

自动补全

到现在为止,你可能已经有些厌倦了不断输入大量长且不熟悉的 cmdlet。让我向你展示一个非常棒的 shell 功能:自动补全。试试这个——在你的 shell 中,输入以下内容,不要按 Enter

Stop-s

现在按下 Tab 键。酷吧?不过这还不是我们想要的 cmdlet。再按一次 Tab 键。此时你应该看到 Stop-Service cmdlet 完整地输入了。现在,添加一个空格,输入 -,然后再次按 Tab 键。继续按 Tab 键,直到你浏览完 Stop-Service cmdlet 所有可能的参数。完成后按 Esc 键。

这是一个很好的方法,可以避免输入大量字母,但它也是一个检查你所做操作是否有效的好方式。如果自动补全不起作用,那很可能是你想要的 cmdlet、参数或选项在这台机器上不可用。

在下一章,我们将探讨一些启动和使用 PowerShell 的其他方法,但现在,你已经准备好所需的一切了。

获取帮助

现在你已经安装了 PowerShell 并可以启动它,你需要用它来做一些事情。你肯定需要一些帮助。幸运的是,PowerShell 内置了三个非常有用的 cmdlet:Get-CommandGet-HelpGet-Member。这些 cmdlet 将告诉你一些有用的内容,并提供指导。我们先从 Get-Command 开始。

Get-Command

Get-Command 会给你一个 cmdlet 列表。如果你直接输入它,它会列出大约 1,500 个 cmdlet。当你开始安装和编写模块时,这个列表会大大增加。浏览成千上万的 cmdlet 以寻找可能的命令并不是很高效。你需要做的是搜索这个列表。

假设你需要调查正在你客户端上运行的某个特定进程。一个执行此操作的 cmdlet 很可能会在某个地方包含 process 这个词。试着在你的 shell 中输入以下内容:

Get-Command *process

你应该会看到类似这样的内容:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_013.jpg

图 1.13 – 搜索相关 cmdlet

该 cmdlet 会将 *process 解释为一个字符串,并搜索以 process 结尾的 cmdlet。* 是一个通配符字符。尝试像这样运行它:

Get-Command process

你可能会看到一个红色的错误。

这些 cmdlet 中有些看起来有点复杂,但也有一些特别突出——尤其是 Get-Process。试着运行它。你应该会看到一长串进程以及关于它们的一些信息。让我们看看一个我知道你正在运行的进程:pwsh。输入以下内容:

Get-Process pwsh

你应该看到有关你 PowerShell 进程的信息:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_014.jpg

图 1.14 – 我的 PowerShell 进程

这很不错,但它到底意味着什么呢?让我们来看一下我们的三个有用 cmdlet 之一:Get-Help

Get-Help

运行 Get-Help cmdlet 很简单;输入 Get-Help 后跟你希望获得帮助的 cmdlet 名称:

Get-Help Get-Process

然后你应该会看到类似这样的内容:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_015.jpg

图 1.15 – 第一次运行 Get-Help

这看起来不太有帮助。然而,如果你阅读 REMARKS 部分,会有一个解释。PowerShell 并没有自带完整的帮助文件;你需要下载并更新它们。要更新帮助文件,运行以下命令:

Update-Help

运行过程会稍微花一点时间,如果你安装了某些模块,可能并非所有模块的帮助文件都能在线获取,所以你会看到红色的错误信息,但经过一两分钟后,应该就能完成,接着你可以尝试再次获取 Get-Process 的帮助。

Get-Help Get-Process

PowerShell 相当偏向于 en-US 文化。这里的文化是指 .NET 和 PowerShell 等相关程序中的一个特定含义;它相当于 en-US,因此它可能不会下载所有相关的帮助文件。如果你发现没有获取到所有内容,试着运行以下命令:

Update-Help -UICulture en-US

然后,重新尝试。这特别影响 Linux 安装。

你应该能看到更多信息,包括一行的简介和详细描述。如果这还不够,那么在REMARKS部分,你将会看到一些获取更多 cmdlet 信息的其他方法。试试运行这个命令:

Get-Help Get-Process -Detailed

你将看到更详细的信息,包括如何使用该 cmdlet 的示例。要查看所有可用的信息,可以使用以下命令:

Get-Help Get-Process -Full

你将看到帮助文件中的所有内容,包括非常有用的NOTES部分,对于这个 cmdlet,它将告诉你如何解释输出中的一些值。

还有一种有用的方法可以使用Get-Help来查看 cmdlet 的帮助信息,方法是使用-online参数:

Get-Help Get-Process -Online

这将会在你的默认浏览器中打开该 cmdlet 的网页;它提供的信息与使用-Full参数时相同。

关于文件

Get-Help不仅帮助你了解 cmdlets,还能通过一套特殊的文件叫做ABOUT TOPICS提供许多关于 PowerShell 概念的有用信息。在撰写本文时,已经有超过 140 个此类文件。这些文件包含大量关于编程概念、构造以及常见查询的信息,例如 Windows 和非 Windows 环境的日志记录。通过运行以下命令,你可以自行查看:

Get-Help about*

我们来看一下其中一个文件:

Get-Help about_Variables

你应该能看到很多关于 PowerShell 中变量使用的有趣信息。

你也可以使用全文搜索来查询Get-Help。如果你查找的词不在帮助文件的文件名中,那么将会搜索文件的内容。这个过程稍微慢一些,但通常是值得的。试试输入以下命令:

Get-Help *certificate*

记住你得到的结果。现在,尝试输入certificates,复数形式:

Get-Help *certificates*

你将会得到一组不同的结果。第一组结果会找到文件名中包含certificate的帮助文件。当Get-Help产生第二组结果时,它找不到任何包含certificates的文件名,因此会执行全文搜索。请注意,如果搜索词出现在文件名中,那么全文搜索将不会执行。

我发现这些文件的唯一缺点是,假设你对 PowerShell 中的其他内容都很了解,只除非是当前讨论的主题。例如,ABOUT_VARIABLES在开头几段中提到了scope变量。尽管如此,如果你需要快速了解某个事物的工作原理,那么这些文件仍然是一个很好的资源。

Get-Member

本章我们将要看的一最后一个有用的 cmdlet 是Get-Member。在本章之前,我们讨论了 PowerShell 是如何生成对象,而不是像某些 shell 和脚本语言那样生成文本输出的。Get-Member允许你查看这些对象的成员、属性以及可用于它们的方法。通过展示而非描述来讲解会更容易理解,所以请继续在你的 shell 中输入以下命令:

Get-Process | Get-Member

注意

两个 cmdlet 之间的竖线称为管道字符 |。它不是小写字母 L——在我的 en-GB 标准 PC 键盘上,它位于左下角,靠近 Z 键;在标准的 en-US 键盘上,它位于 EnterBackspace 键之间。如果你的键盘没有实心的竖线 (|),那么破折竖线 (¦) 也可以使用。

你在这里做的是将 Get-Process cmdlet 的输出通过管道传递给下一个 cmdlet 作为输入,在这个例子中是 Get-Member。在后续的章节中,我们将进行大量关于管道的工作。Get-Member 会告诉你你提供的对象类型,在这种情况下是 System.Diagnostics.Process 对象,以及与该对象相关的方法、属性、别名属性和事件,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_01_016.jpg

图 1.16 – System.Diagnostics.Process 的一些成员

几页之前,在图 1.14中,我们查看了在您的机器上运行的 pwsh 进程的属性。这些是列出的属性:NPM(K)PM(M)WS(M)CPU(s)IDSIProcessName。如您现在所见,这些是 Non-Paged Memory (K)Paged Memory (M)Working Set (M)Session ID,它们都是别名,以便能够在屏幕上整齐地显示在表格中。CPU(s) 别名的衍生方式稍有不同——它不是在对象上设置的。ID 和进程名称不是别名。M 和 K 分别是 兆字节千字节 的缩写。这只是该对象上所有属性的一个非常小的子集。如您所见,还有可用于对对象执行操作的方法。

活动 2

看看这些方法。你可能会使用什么方法来强制立即停止一个进程?如果你卡住了,可以查看这里的这些方法:docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process

在本书的剩余部分,我们将多次回到 Get-Member,因为它是一个非常有用的 cmdlet。

总结

在本章中,我们做了很多工作。我们讨论了 PowerShell 是什么以及它适合用于什么,比如快速简便地生成小段自动化代码。我们已经以几种不同的方式下载并安装了它,具体来说,通过从 .msi 文件安装和从 .zip 文件提取来安装它。

我们尝试了使用内置 shell 启动它的几种不同方法,最后,我们查看了三个有用的 cmdlet:Get-Command,用于查找我们可能使用的 cmdlet,Get-Help,用于了解如何使用它们,以及 Get-Member,用于理解这些 cmdlet 输出的内容。

在下一章,我们将探索 cmdlet 如何工作,探索参数和语法,并查看一个有用的应用程序,用于与 PowerShell 进行交互式操作:Windows Terminal。

练习

  1. 你会使用哪个 cmdlet 来生成一个随机数?

  2. 你如何生成一个 1 到 10 之间的随机数?

  3. 你会使用哪个 cmdlet 来列出文件夹的内容?

  4. 如何同时获取子文件夹的内容?

  5. 你会使用哪个 cmdlet 来创建一个新文件夹?

  6. 哪个 cmdlet 可以告诉你计算机已经开机多久?

  7. 哪个 cmdlet 可以将输出重定向到文件?

  8. 你可以使用 Get-Credential cmdlet 做什么?

  9. 你如何使用 ConvertTo-HTML cmdlet?

进一步阅读

第二章:探索 PowerShell Cmdlet 和语法

现在我们已经安装了 PowerShell,是时候开始使用它了。PowerShell 的强大之处在于它提供了大量的 cmdlet。在本章中,我们将学习这些 cmdlet。首先,我们将了解命名约定如何工作,然后学习如何通过参数控制它们的行为。

我们将深入探讨语法,以便完全理解每个 cmdlet 可用的参数以及这些参数需要什么信息才能正常工作。然后我们将学习如何从本地计算机和外部资源获取更多的 cmdlet 进行使用。

最后,我们将讨论如何与 PowerShell 进行交互式操作,并将为 Windows 安装一个令人兴奋的新应用程序,帮助我们实现这一点。到本章结束时,您将能够自信地找到并使用不熟悉的 cmdlet,并能够使用多功能的 Windows Terminal 应用程序。

本章将涵盖以下主要内容:

  • 什么是 cmdlet?

  • 理解 cmdlet 语法

  • 如何查找更多的 cmdlet

  • 与 PowerShell 进行交互式操作

技术要求

本章假设您已经拥有客户端机器、互联网连接,并且安装了最新的稳定版 PowerShell 7。本章的最后两节—— Windows Terminal – 另一种终端模拟器从 Windows Store 安装 Windows Terminal——是专为 Windows 用户准备的。使用 Linux 和 macOS 的我们可以放心,因为我们不需要做这些部分,这些操作系统已经包含了多标签终端。

什么是 cmdlet?

Cmdlet(发音为 command-lets)是我们在 PowerShell 中使用的命令。它们可以通过终端应用程序或脚本输入。它们可能是 基于脚本 的,由其他 cmdlet 构建,或者可能是用 C# 编写的 二进制 cmdlet。默认安装的 cmdlet 通常是二进制文件。通常,我们自己编写或从互联网上下载的 cmdlet 将是基于脚本的。Cmdlet 从管道中获取 输入对象,对其执行操作,并通常返回一个对象以供进一步处理。我们将在 第三章中详细描述这一过程,PowerShell 管道 – 如何将 cmdlet 串联起来

探索 cmdlet 结构

我们已经使用了一些 cmdlet,您可能已经注意到它们有一个共同的结构:Get-HelpGet-CommandGet-Member。这是一个 动词-名词 结构,旨在帮助我们理解每个 cmdlet 的功能。开始的 动词 告诉我们,在每种情况下,这些 cmdlet 都是要 获取 某些东西:帮助、命令列表或成员列表。这里有一个批准的动词列表:docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7.2

为什么我们需要一个批准的动词列表?为了清晰起见;PowerShell 的主要目的之一是易于理解。通过限制动词的数量并明确定义每个动词的含义及其使用场景,我们可以立即了解一个 cmdlet 的作用。例如,一个名为 Delete-Item 的 cmdlet 可能和 Remove-Item 一样直观,但如果是 Eliminate-Item 呢?这可能意味着它会做一些不太愉快且具有终结性质的操作。大多数写得好的 cmdlet 都会遵循这个列表;希望我们编写的代码也能符合这一点。

请注意,列表中的并非所有动词在英语中都是实际的动词。New 不是一个动词,但为了 cmdlet 的目的,我们将其当作动词使用。

活动 1

我们什么时候使用 New,什么时候使用 Add?请查看前面的链接了解详细信息。

cmdlet 的第二部分是 Get-Process,而不是 Get-Processes。第二条规则是它们应该具有描述性;即,它们应该是立即可以理解的。这可能会导致一些较长的 cmdlet 名称,如 Get-NetAdapterAdvancedProperty。这虽然有点拗口,但很容易理解。我们在 第一章 中已经看过的自动完成功能,PowerShell 7 简介——它是什么以及如何获取,使得输入长 cmdlet 更加容易。只需输入名词的第一部分,按 Tab 键,再输入下一部分,再按 Tab 键,以此类推,直到完整输入 cmdlet。试试 Get-NetAdapterAdvancedProperty。我们只需输入 Get-NetA,然后按两次 Tab 键就能得到正确的 cmdlet。在 Windows 机器上,反复按 Tab 键会在合适的 cmdlet 之间循环,或者在 Linux 和 macOS 上呈现选项列表。

关于大小写也有一些标准;驼峰命名法是首选,即名词中的每个单词都要大写。这不仅使我们从屏幕上阅读更容易,而且还意味着屏幕阅读器等辅助工具可以正确处理它们。

正确使用别名

有一种方法可以避免繁琐的 cmdlet 名称:将 Get-Alias 输入命令行。你应该会看到一个相当长的别名列表,列出常用 cmdlet 的别名。例如,你应该能看到 manGet-Help 的别名。试试它;输入以下代码并查看发生了什么:

man Get-Command

有两种类型的别名,分别用于不同的目的。让我们在这里仔细看一下它们:

  • gciGet-ChildItem 的别名。这些别名通常要求你已经知道正确的 cmdlet,并且可能非常简短且不直观。它们是为那些懂 PowerShell 并希望节省时间的人准备的。

  • man 是一个 Unix 命令,调用的是 lsdir,它们都是 Get-ChildItem 的别名,并生成看起来像你熟悉的 ls Unix 命令或 dir Windows 命令的输出。这些是为了像我这样的老年人,以便不用重新学习多年的肌肉记忆。

注意

尽管我们可以在 PowerShell 中输入 dir 并获得相关输出,但我们并没有得到来自 dir 命令的输出。我们在 dir 中可能熟悉的命令选项在 PowerShell 中不起作用。我们实际上是在调用一个具有类似功能的 cmdlet。

我们还可以使用 Set-Alias cmdlet 为我们经常使用的 cmdlet 定义自己的别名。我们可以这样做:

Set-Alias -Name adapters -Value Get-NetAdapter

尝试一下。输入前面的 cmdlet,然后输入 adapters。你应该能看到类似这样的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_001.jpg

图 2.1 – 设置别名

这样做的缺点是,你设置的别名仅在当前的 PowerShell 会话中有效。当你关闭窗口时,所有自定义的别名都会丢失。你可以将它们保存在 PowerShell 配置文件中,但它们仍然仅在本地有效。我避免使用自定义别名,而是依赖自动补全。将别名用于脚本中也是不好的做法,因为这会影响代码的可读性。

现在我们了解了什么是 cmdlet,让我们看看使用它们时需要遵循的语法。

理解 cmdlet 语法

我们已经看到,你可以向 cmdlet 传递信息作为输入,或者修改输出。例如,在上一节中,我们输入了以下内容:

Set-Alias -Name adapters -Value Get-NetAdapter

这个 cmdlet 的名称是 Set-Alias,但后面有两部分信息:-Name-Value。这两部分被称为 -,它告诉 PowerShell 后面的字符直到下一个 空格 字符表示的是指令,而不是值。在前面的参数中,我们传递了一个字符串——我们告诉 Set-Alias-Name 的值是 adapters,而 -Value 的值是 Get-NetAdapter。现在,当我们在命令提示符下输入 adapters 时,PowerShell 会知道用 Get-NetAdapter 来替换它。

太好了,但我们如何知道一个 cmdlet 会接受哪些参数呢?好吧,我们将再次回到我们的朋友 Get-Help,它出现在 第一章,《PowerShell 7 入门——它是什么以及如何获取它》中。打开一个 PowerShell 提示符,输入以下内容:

Get-Help Get-Random

这将调出Get-Random的帮助文件。我们关注的是下面的SYNTAX部分:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_002.jpg

图 2.2 – Get-Random 语法

好的,这很棒,但这到底是什么意思呢?帮助文件告诉我们,Get-Random可以有三种不同的操作方式;每种方式都有一组参数。让我们专注于第一个参数集。记住,参数总是以短横线(-)开始,因此我们可以看到第一个参数集中有四个参数:-Maximum-Minimum-Count-SetSeed。正如DESCRIPTION部分所说:你可以使用 Get-Random 的参数来指定最小值和最大值、从集合中返回的对象数量,或者一个种子数字。希望你已经完成了《第一章》中的练习部分,PowerShell 7 简介–它是什么以及如何获取,因此你之前应该见过这个。让我们来看第二个参数集。那里有三个参数:-InputObject-Count-SetSeed。使用这些参数会让Get-Random做不同的事情。它将不再返回一个随机数字,而是返回你在第一个参数中给定的列表中的一个随机对象。让我们试试看。在 PowerShell 提示符下,输入以下内容:

Get-Random -InputObject "apples", "pears", "bananas"

希望Get-Random能够从这个列表中返回一个随机的水果项。逗号字符告诉 PowerShell 列表中还有更多项。

以下截图展示了你可能会犯的一些错误:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_003.jpg

图 2.3 – 错误传递项目列表

在第一个示例中,我只传递了一个包含大量双引号的单一项。

在第二个示例中,我只传递了一个项给-InputObject参数,PowerShell 将第二个字符串pears解释为另一个参数的输入,结果产生了混淆。

在第三个示例中,PowerShell 正在等待列表中的下一个项,用>>符号表示。如果你遇到这种情况而不确定接下来应该输入什么,按Ctrl + C可以中断操作。

SYNTAX部分告诉我们的不仅仅是参数的名称和集合。它还告诉我们哪些参数是必需的,哪些参数可以接受多个项,哪些参数需要显式指定名称,以及每个参数接受的值类型。请保持专注——接下来可能会有点复杂。让我们再次查看Get-Random的帮助文件中的SYNTAX部分,见下图。请留意方括号:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_004.jpg

图 2.4 – Get-Random 语法

我们的第一个参数集以[[-Maximum] <System.Object>]开始。-Maximum是参数名称,而<System.Object>-Maximum参数将接受的输入类型。

如果你输入 Get-Help Get-Random -Full,你会看到它只接受整数、浮动点数,或可以解释为整数的对象,例如字符串 two,如图 2.5所示。外部的方括号告诉我们 -Maximum 参数是可选的;我们不需要包括它就能得到一个随机数。参数名称周围的内部方括号 [-Maximum] 告诉我们,我们不需要包含参数名称来为 cmdlet 传递最大值。在以下截图中,我们可以看到该参数的位置信息是 0——这意味着第一个未命名的参数会被解释为属于 -Maximum 参数:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_005.jpg

图 2.5 – -Maximum-Minimum 参数的详细信息

我们可以看到,对于 [-Minimum <System.Object>] 参数,内部没有方括号;这意味着如果我们想使用 -Minimum 参数,它必须始终被命名;我们必须实际输入 -Minimum。对于 -Minimum-Maximum 参数,它们的 <System.Object> 周围没有方括号。这意味着如果我们使用这些参数,我们必须向它们传递一个 System.Object 类型的参数(具体来说,是一个可以被解释为数字的整数、浮动点数或字符串)。

我们来看第二组参数。它以 [-InputObject] <System.Object[]> 开头;这意味着如果我们想使用第二组参数,我们必须提供一些输入。[-InputObject] 周围的方括号告诉我们这个参数是可选的,不过,PowerShell 会将它接收到的第一个参数解释为输入。这和第一组参数有什么不同呢?仔细看看 <System.Object[]> 参数——它的末尾有一对方括号。这表示它可以包含多个值,用逗号分隔。试试看,输入以下内容:

Get-Random 10, 20, 30

希望你能得到其中一个值的返回。PowerShell 知道它已经接收到多个值,并且知道不会将这些值集合解释为 -Maximum 的参数,因为 -Maximum 只能包含一个单一的值。

活动 2

你怎么给 -InputObject 参数提供一个单一的数值?

现在我们来看第三组参数。乍一看,它们和第二组参数看起来一样。它们的开头是相同的,但注意结尾有一个参数:-Shuffle。它没有方括号,也没有参数。这是一个 switch 参数。如果我们使用它,我们就自动使用了第三组参数;这意味着 -Count 参数对我们不可用,因为它不在第三组参数中。它不接受任何参数,因为它告诉 PowerShell 以随机顺序返回整个列表。

每个参数集以 [<CommonParameters>] 结尾。这是一组可用于任何 PowerShell cmdlet 的参数。你可以阅读 about_CommonParameters 帮助文件获取更多信息。它们包括控制 PowerShell 在发生错误时采取的操作的变量,或从 cmdlet 输出更多内容以帮助故障排除。有关更多信息,请参见第十章《错误处理 – 哎呀!它出错了!》。

让我们总结一下 cmdlet 的语法。接下来列出了六种类型的参数:

  • Start-Service 有一个必填参数:-DisplayName

  • Get-Random 的参数 -InputObject 就是一个例子。

  • Get-Random 的参数 -Count 就是一个例子。

  • -MaximumGet-Random cmdlet 中的一个例子。整个参数被方括号包围,然后有一对第二个方括号仅包围参数名称。

  • -Shuffle 是一个强制性的开关参数的好例子。

  • 常见参数:这些是所有 PowerShell cmdlet 都可以使用的参数,允许你控制输出或错误行为。

参数组织成 Get-Random 可以返回一个随机数字、从列表中随机选取一个项目,或者按随机顺序返回整个列表。

现在我们已经理解了什么是 cmdlet 和参数,你可能会想知道它们从哪里来。下载 PowerShell 时会包含很多,但这远远不够。接下来我们将探索如何获取更多的 cmdlet。

如何找到更多的 cmdlet

Cmdlet 通常被捆绑成一个名为模块的包。我们将在第十一章《创建我们的第一个模块》中详细讲解模块结构,但现在知道模块是一个包含具有共同主题的 cmdlet 集合就足够了,主题可能是与某个特定应用程序互动或执行一组类似的功能。一旦模块被安装和导入,cmdlet 就会在 shell 或脚本中可用。

在你的机器上查找模块和 cmdlet

我们已经有了很多可用的模块。试试看;在命令行中输入 Get-Module。根据 PowerShell 的安装时间、会话的开启时长和我们正在使用的平台,应该会看到一个相对较短的列表,类似这样:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_006.jpg

图 2.6 – 已导入模块列表

这是一个当前会话中已导入模块的列表。但这并不是我们现在可以使用的所有模块。尝试像这样再次运行 cmdlet:

Get-Module -ListAvailable

你应该会看到更多;如果你使用的是 Windows 系统,你将看到更多。

cmdlet 的输出将根据模块所在的目录进行拆分,如下图所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_007.jpg

图 2.7 – 模块及其目录

如你所见,在我的机器上,该 cmdlet 找到了来自 PowerShell 7 Preview 和 Windows PowerShell 的模块,以及其他许多模块。PowerShell 使用 PSModulePath 环境变量来知道在你的环境中在哪里查找模块。我们可以通过键入以下内容来检查该变量中包含哪些位置:

 $env:PSModulePath -split ";"

如果你在 Linux 或 macOS 设备上工作,使用冒号是必要的。

每次启动 PowerShell 时,这些位置会被放入该变量中,但它们也可以由应用程序或手动添加。你可以在下面的屏幕截图中看到我的机器上的结果。注意,最后一项是 Microsoft Message Analyzer;这是我安装该应用程序时添加的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_008.jpg

图 2.8 – PSModulePath 变量

虽然我们可以手动添加路径,但对于大多数情况来说,最好确保将模块安装到默认路径。有关 PSModulePath 变量的更多信息,请参阅 about_PSModulePath 帮助文件。

现在我们知道了在哪里可以找到计算机上的模块,那么如何知道它们包含哪些 cmdlet 呢?请注意在图 2.7 中,ExportedCommands 表中有属性;这些就是我们导入模块时可用的 cmdlet。可能有一些 cmdlet 没有导出,只能在模块内部使用;我们无法直接键入这些 cmdlet 进行使用。我们可以通过运行 Get-Command –Module,后跟模块名称,来查看仅导出的 cmdlet。

让我们尝试一下。在第一章《PowerShell 7 简介——它是什么以及如何获取它》中,我们使用了 Get-Command cmdlet 来查找 cmdlet。该 cmdlet 会搜索所有可用的模块中的 cmdlet,或者我们可以告诉它只搜索已导入的模块。假设我们对操作模块的 cmdlet 感兴趣。我们可以键入以下内容,以获取包含 module 这个词的 cmdlet 列表:

Get-Command *module*

然而,如果我们键入以下内容,我们将只会得到已经导入到当前会话中的模块的 cmdlet:

 Get-Command *module* -ListImported

这显示了这两个 cmdlet 在我的机器上的表现:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_009.jpg

图 2.9 – 已导入和已安装的 cmdlet

如何使用尚未导入的模块中的 cmdlet?有两种方法,如下所示:

  • 首先,我们可以使用 Import-Module cmdlet 将模块导入会话,然后使用我们需要的 cmdlet。

  • 或者,我们也可以直接使用它,并允许 PowerShell 隐式地导入它。

尝试一下。在图 2.6 中,有一个当前会话已导入模块的列表。它不包含 PowerShellGet 模块。很可能你的当前会话也没有导入该模块,因此让我们现在导入它。键入以下内容:

 Get-InstalledModule

您会注意到,当 PowerShell 导入 PowerShellGet 模块时会有一个小小的暂停,然后如果有已安装的模块,您会看到它们的列表。那很好,但聪明的地方在于:再次输入 Get-Module 命令,来获取已导入模块的列表。您应该会看到 PowerShellGet 已经在后台被导入,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_010.jpg

图 2.10 – 仿佛魔法般……模块出现了

这引出了两个问题。第一个问题是:为什么还要导入和安装?为什么不从一开始就将所有 cmdlet 都可用呢?简短的答案是 空间。每个导入的模块都需要占用内存空间;如果我们导入了数百个模块和成千上万的 cmdlet,那么每次启动 PowerShell 时,它将变得极其笨重和缓慢,因此我们只在需要时导入我们需要的模块和 cmdlet。

另一个问题是:如果我们可以隐式获取 cmdlet,为什么还需要 Import-Module cmdlet 呢?我们需要 Import-Module 有两个原因:首先,您可能没有将模块安装到 PSModulePath 中的默认路径之一,因此隐式导入将不可用。其次,您可能希望控制模块的导入方式。例如,您可能不想导入与已导入 cmdlet 同名的模块,这时您应该使用带有 -NoClobber 参数的 Import-Module,而不是隐式导入。

活动 3

我们如何导入 cmdlet,但改变它们的名称,以便我们知道它们来自哪个模块呢?

查找新的模块和 cmdlet

到目前为止,我们已经查看了系统上已有的模块,但我们刚刚导入了 PowerShellGet,它非常有用,因为它可以帮助我们查找远程存储的模块,默认情况下,PowerShellGet 会连接到远程位置。

PowerShell Gallery

让我们首先来看一下如何找到我们感兴趣的 PowerShell Gallery 中的模块。尝试运行以下命令:

Get-Command -Module PowerShellGet

这将为我们提供模块中可用的 cmdlet 列表。我们可以看到它们分为两类——用于查找和安装资源(如模块和脚本)的 cmdlet,以及用于管理仓库的 cmdlet。我们来看看一个模块吧。尝试运行以下命令:

Find-Module -name *math*

我的结果如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_011.jpg

图 2.11 – PowerShell Gallery 中与数学相关的模块

其中一些看起来或多或少有用——我不确定我是否需要 PokerMath,但你可能需要。我们怎么知道这些模块中有哪些 cmdlet 呢?我们不能使用 Get-Command,因为这些模块不在本地计算机上,但是 PowerShellGet 包括了 Find-Command cmdlet。如果我们将某个特定模块的名称传递给 Find-Command,它会列出该模块中的 cmdlet,像这样:

Find-Command -ModuleName psmath

如果我们运行它,我们可以看到psmath包含整个范围的数学函数,从相当明显的——如Get-Math.sqrt——到一些更为深奥的人工智能(AI)和统计学函数。要了解如何使用这些函数,我们需要安装模块并查看帮助文件。我们可以通过输入以下内容来执行此操作:

Install-Module psmath

您可能会看到一个警告,说明仓库是不受信任的,就像下面的屏幕截图一样。这是预期的,因为默认情况下,没有仓库是受信任的。我们需要使用Set-PSrepository cmdlet 显式信任它们:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_012.jpg

图 2.12 – 你信任这个仓库吗?

输入Y以确认。几秒钟后,进度条消失,命令提示符将重新出现。现在模块已安装,但尚未导入。尽管我们使用PowerShellGet安装了模块,但它已安装到一个位置,这不是隐式导入它的正确路径。但我们可以使用Import-Module cmdlet 显式导入它,就像这样:

Import-Module psmath

如果我们现在输入以下内容,我们将看到社区提供的模块的一个缺点:

Get-Help Get-Math.Sqrt

许多作者更喜欢编写他们的软件而不是为其撰写文档,所以帮助内容相当稀少。然而,我们可以看到Get-Math.Sqrt接受两个参数:要么是值,要么是输入对象。-Values参数是位置参数。有趣的是,它还可以接受值的列表,而不仅仅是单个值。您可以使用输入对象来提供另一个 cmdlet 或变量的输出。尝试这个:

Get-Math.Sqrt -InputObject (Get-Random)

这将生成一个随机数,然后计算其平方根。

为什么PowerShellGet不能将模块安装到正确的位置?这与作用域有关。默认情况下,PowerShellGet会仅为当前用户安装模块,如果您使用 Windows,则会安装到您的Documents文件夹中的路径,或者在 Linux 上安装到您的主目录。我们可以通过使用-Scope参数和AllUsers参数来更改此设置,但我们需要以管理员权限运行才能执行此操作。在下面的屏幕截图中,您可以看到在这种情况下出现的错误消息。另一种方法是将位置添加到PATH环境变量中:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_013.jpg

图 2.13 – 我恐怕不能这样做,戴夫

我们也可以在线查看 PowerShell Gallery,网址为www.powershellgallery.com/。在网站上搜索math将会得到更多结果,因为它不仅会搜索模块名称,还会搜索任何关联的标签。我们可以通过使用-Filter参数和Find-Module获取类似的结果。

PowerShell Gallery 不是唯一的存储库

虽然 PowerShell Gallery 很棒,但也有其他仓库。你可能在工作中有一个内部仓库,或者你可能决定以后为自己建立一个。仓库的优点是版本控制和它所允许的自动化。缺点是维护以及潜在的被利用风险。如果你需要使用除 PowerShell Gallery 外的其他仓库,那么 PowerShellGet 包含了一些用于与它们交互的 cmdlet,如 Get-PSRepositorySet-PSRepositoryRegister-PSRepository

其他来源

PowerShell Gallery 的主要替代方案是 GitHub,网址为 github.com,以及类似的在线源代码管理工具,如 GitLab。这些平台不仅限于 PowerShell,还包含许多其他语言编写的代码。绝大多数代码以某种形式是开源的。GitHub 平台由微软拥有,但并未由微软管理,包含从完全官方的 PowerShell 仓库(包括微软自己的代码),到未经维护的、不完整的脚本片段和恶意软件。除非你绝对信任仓库所有者,否则一定在下载之前阅读代码并理解它的作用。

我们可以在 GitHub 上搜索 PowerShell 模块,通过在 GitHub 网站上输入搜索词并添加 language:PowerShell,如下图所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_014.jpg

图 2.14 – GitHub 网站上的 49 个仓库,其中一些可能有用

我们也可以在互联网上找到大量 PowerShell 脚本;尽管我们不太可能找到完整的模块,但许多有用的网站在源代码管理平台之外发布了完全可用的脚本。然而,警告依然存在,甚至更加重要。即使你信任作者,也要在使用之前阅读代码,并理解它的作用。永远不要盲目运行某些东西并指望它没问题。PowerShell 功能非常强大,您应该始终安全地运行它。关于这一点,我们将在 第十二章保护 PowerShell 中详细讨论。

获取 cmdlet、模块和脚本的最终方式是自己编写。这比你想象的要容易,我们将在本书的第二部分中介绍,从 第八章编写我们的第一个脚本 – 将简单的 cmdlet 转化为可重用的代码

现在我们应该对可以找到 PowerShell 资源的众多地方有了较好的了解,并且理解它们的相对价值。在下一部分,我们将讨论与 PowerShell 互动的最基本方式,Windows 用户可以享受一些乐趣。

与 PowerShell 进行交互式工作

在我看来,PowerShell 是一种不太寻常的编程语言,因为其庞大的 cmdlet 数量使得它特别适合互动使用;打开终端,输入一行代码,就能看到一些令人兴奋的结果。在其他解释性语言中很难做到这一点,比如 Python,因为 Python 自带的命令不多,并且将库导入到交互式会话中也很困难。因此,Python 用户很快就会转向编写脚本。我在使用 PowerShell 的 10 年里,发现很多同事实际上并没有真正从交互式 PowerShell 进展到脚本编写,这也没问题。在本章的其余部分,我们将回顾一下我们是如何使用 PowerShell 的,Windows 用户将能安装一个非常有用的工具,叫做 Windows Terminal,它能为他们提供与 Linux 和 macOS 用户默认拥有的相同的多标签终端体验。

每当我们输入一行代码时,我们就调用了一个 cmdlet。每个 cmdlet 都是一个迷你脚本或程序。这类似于 shell 脚本;Windows 中的批处理脚本或 Linux 中的 Bash 脚本,其中我们输入的每一行代码都会调用一个定义好的程序。

PowerShell 中的大部分脚本都是以相同的方式编写的——脚本可以像 cmdlet 一样运行,带有参数来修改行为和操作。这意味着我们也可以互动使用它们,从而将我们的努力与那些技术上不太擅长的同事和朋友分享。然而,在我们开始编写脚本之前,最好先设置一个方便的方式来互动使用 PowerShell。PowerShell 7 提供了改进的 shell,包括高亮显示,使我们更容易看清我们正在输入的内容,以及改进的复制粘贴功能。然而,我更喜欢另一个工具——Windows Terminal。

Windows Terminal——一个替代终端模拟器

如果你使用的是 Linux 或 macOS,你不需要做这一部分,因为你们已经有了多标签的终端,真是幸运的人。对于 Windows 用户,我们以前需要打开多个应用程序;过去,我可能同时打开了 Windows Console(命令提示符)、Windows PowerShell、PowerShell 7、Azure Cloud Shell、PuTTY、Git Bash 和 Python,作为不同的应用程序运行。然而,自 2020 年以来,有了一个更好的选择——Windows Terminal。它可以在不同的标签页中运行任何命令行程序的多个实例,这就足以让我信服,但它还支持表情符号和字形、分屏功能,以及一个据说很有趣的新字体叫做 Cascadia Code,另外它是开源的。如果你想了解更多细节,可以参考这篇博客:devblogs.microsoft.com/commandline/introducing-windows-terminal/

Windows Terminal 托管在 GitHub 上,网址是 github.com/Microsoft/Terminal,你可以从那里下载并安装 .msixbundle 文件。你也可以如果之前安装了 Winget,或者使用 Windows 11,则可以通过 Winget 安装它。然而,推荐的方式是通过 Microsoft Store 安装,这样可以让应用自动更新——由于它是开源软件,更新频繁且通常是必要的,因为它包含了 bug 修复和改进。

从 Microsoft Store 安装 Windows Terminal

从 Windows Store 安装非常简单。以下是安装步骤:

  1. 在 Windows 的搜索框中输入 store,然后启动 Microsoft Store 应用。

  2. 在 Store 应用的搜索框中输入 Windows Terminal

  3. 点击 获取,如以下截图所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_015.jpg

图 2.15 – 获取 Windows Terminal:不适合幼儿

  1. 几分钟后,Terminal 会出现在你的 开始 菜单中。

就这样。Windows Terminal 现在会自动更新。接下来,我们开始为我们的用途进行配置。因为我们已经安装了 PowerShell 7,所以当你打开它时,Terminal 会默认使用 PowerShell 7;如果没有安装,它将默认使用 Windows PowerShell。根据你在客户端安装的其他应用程序,它可能会自动识别并提供可用的选项。点击工具栏中的 向下 图标,查看已提供的应用,并选择一个非默认的应用,如以下截图所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_016.jpg

图 2.16 – Windows Terminal 中可用的基本应用

这很好,但它还远远不够涵盖你可能拥有的所有命令行应用程序。让我们看看如何做到这一点。我在我的机器上安装了 Python,因为我常常喜欢换个视角。如果你想跟着一起操作,且没有安装 Python,可以从这里下载:www.python.org/downloads/。

默认情况下,Python 安装在 C:\Users\<yourname>\AppData\Local\Programs\Python\<version number>,如下图所示。你需要记下这个路径,并在文件资源管理器中启用查看隐藏文件:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_017.jpg

图 2.17 – Python 的位置

要配置 Windows Terminal 以访问 Python,你需要设置一个新的配置文件。以下是操作步骤:

  1. 启动 Windows Terminal。

  2. 点击工具栏中的 向下 按钮,然后选择 设置

  3. 在左侧面板中选择 添加新配置文件,然后点击 新建空配置文件,如以下截图所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_018.jpg

图 2.18 – 创建新配置文件

  1. 填入名称——在我的情况下,我使用的是Python 3.10,这样我就知道它会启动哪个版本的 Python。

  2. 填入 python.exe 可执行文件的路径。

  3. 选择一个起始目录,如果你喜欢的话。我喜欢把所有的东西都放在一个地方。

  4. 我喜欢使用图标。你可以在这里找到 Python 图标:C:\Users\<用户名>\AppData\Local\Programs\Python\Python310\Lib\test\imghdrdata

  5. 点击保存

你可以在以下截图中看到这个概览:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_019.jpg

图 2.19 – 创建新配置文件,完成

现在我们完成了。当你点击工具栏中的下拉按钮时,你将看到Python 3.10作为一个选项。为什么我在这里使用 Python 作为示例?为什么不使用 PowerShell 7 预览版呢?因为 Windows Terminal 非常智能。如果你在安装 Windows Terminal 后安装了 PowerShell 7 预览版,只需重新启动 Windows Terminal,瞧!PowerShell 7 预览版就会成为一个选项。酷吧,嗯?

这里,你可以看到 Python 在 Windows Terminal 中运行的画面:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_020.jpg

图 2.20 – 在 Windows Terminal 中运行 Python

还有一件事我们必须做。我们已经讨论了能够以管理员身份启动 PowerShell 的重要性;我们也需要为 Windows Terminal 做同样的事情。最简单的方法是将 Terminal 应用程序固定到任务栏。如果你右键点击任务栏上的图标,我们可以右键点击弹出菜单中的Terminal图标,并选择以管理员身份运行,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_02_021.jpg

图 2.21 – 以管理员身份运行 Windows Terminal

如果你在 Windows Terminal 中打开一个配置文件的设置,你可以仅为该配置文件设置以管理员身份运行。Windows Terminal 还有很多其他功能可以使用。设置可以通过用户界面UI)进行访问,或者你也可以直接编辑一个方便的JavaScript 对象表示法JSON)设置文件。

总结

在本章中,我们已经相当彻底地探讨了 cmdlet。到现在为止,我们应该已经了解了命名规则、cmdlet 使用的语法、如何找出 cmdlet 所需的参数以及如何填写它们。接着,我们探索了在本地机器和 PowerShell Gallery 上发现新 cmdlet 和模块的方法。最后,我们讨论了如何与 PowerShell 进行交互式工作,并介绍了一个适用于 Windows 用户的全新应用程序——Windows Terminal。

在下一章中,我们将深入研究管道;它是如何工作的,如何将 cmdlet 连接在一起,如何理解发生了什么错误,以及如何解决它。我们还将探讨另一个适用于 PowerShell 的优秀应用程序,而且这次每个人都可以安装,而不仅仅是 Windows 用户。

练习

  1. 在 PowerShell 中,获取文件内容的正确命令是 Get-Content 还是 Read-Content

  2. 如果你在 shell 中输入 "alive alive" | oh,会发生什么?为什么?

  3. Get-ChildItem 命令有多少个参数集?哪个参数决定我们将使用哪个参数集?

  4. 如果你看到 Get-ChildItem c:\foo *.exe 命令,可以看出 c:\foo 是传递给 -Path 参数的一个参数。那么 *.exe 被传递给哪个参数?

  5. 如果不实际尝试,Get-ChildItem c:\foo -Filter *.exe, *.txt 命令会运行吗?如果不会,为什么?

  6. 如何找到与 Amazon Web Services (AWS) 相关的 cmdlet?

  7. 如何找到与 AliCloud 相关的 cmdlet?

  8. 如何在 Windows Terminal 中更改文本大小?

第三章:PowerShell 管道——如何将 cmdlet 串联起来

几乎所有的 操作系统 (OSs) 都有 管道 的概念,它允许一个进程的输出被传递到下一个进程的输入中。这个概念归功于 Douglas McIlroy,在 1973 年他在贝尔实验室工作时开发了 Unix 第 3 版。最初的实现设想将每个命令的输出视为一种类似文件的结构,下一条命令可以在此结构上操作。

本章将解释 PowerShell 如何遵循这一愿景并与之有所不同。我们将从探讨管道的概念开始,然后介绍一些操作管道内容的基本方法,再深入分析 PowerShell 中管道的工作原理,以及当管道出现问题时如何进行故障排除。

到本章结束时,我们将理解信息如何从一个 cmdlet 传递到下一个 cmdlet,如何操作这些信息以便只处理我们需要的部分,以及当出现错误信息时,如何找出问题所在。

本章将涵盖以下主题:

  • 如何将 cmdlet 组合在一起——管道

  • 选择和排序对象

  • 过滤对象

  • 枚举对象

  • 格式化对象

  • 管道是如何工作的——参数绑定

  • 故障排除管道——管道跟踪

如何将 cmdlet 组合在一起——管道

自 1970 年代的 Unix 和 C 编程语言以来,操作系统已将计算机的输入和输出抽象为 Read-Host cmdlet。stdout 流是 cmdlet 成功输出的结果。stderr 流包含程序产生的任何错误信息,并被发送到一个单独的流,以避免干扰任何成功的输出。

PowerShell 对这些流进行了扩展,拥有六个输出流,而不是两个。每个流都可以通过显式的 PowerShell cmdlet 捕获,或者在运行 cmdlet 时指定一个常用参数,如下表所示:

流 #描述Cmdlet常用参数
1成功Write-Output无——这是默认的输出
2错误Write-Error-ErrorAction-ErrorVariable
3警告Write-Warning-WarningAction-WarningVariable
4详细信息Write-Verbose-Verbose
5调试Write-Debug-Debug
6信息Write-Information-InformationAction-InformationVariable

表 3.1 – PowerShell 流

流 1 相当于标准输出(stdout),而流 2 相当于标准错误输出(stderr)。PowerShell 管道将流 1 的内容(即成功)从一个 cmdlet 传递到管道中的下一个 cmdlet。当我们看到屏幕上的红色错误信息时,它们并不是输出在流 1 中——它们输出在流 2 中。原因是我们不希望错误信息(或者详细信息或任何除输出对象之外的信息)被传递到下一个 cmdlet 中,导致另一个错误。毕竟,第二个 cmdlet 没有办法解释这些信息。

管道可能由一个或多个 PowerShell cmdlet 组成,使用管道符号 (|) 分隔。每个 PowerShell cmdlet 都是管道的一部分,即使它只是一个单独的 cmdlet。在每个管道的末尾都有一对隐式的 cmdlet,Out-Default | Out-Host,确保 cmdlet 在流 1 中的输出被格式化并写入屏幕。有些 cmdlet 没有流 1 输出,因此在运行它们后我们不会看到任何屏幕上的输出。例如,在 第二章探索 PowerShell Cmdlet 和语法 中,我们运行了以下 cmdlet:

Set-Alias -Name processes -Value Get-Process

一旦 cmdlet 执行完毕,我们会返回到提示符。如果我们查看 Set-Alias 的帮助文件,会看到它默认没有输出,因此在成功运行时我们不会看到屏幕上的任何内容。尽管如此,Set-Alias 仍然是一个管道;Out-Default 仍然运行,只是没有接收到任何输出。

Cmdlet 从左到右执行,左侧 cmdlet 的输出对象会传递到管道右侧的下一个 cmdlet。为了便于阅读(和输入),管道符号可以用作交互式工作时的换行符。尝试在管道符号后按 returnEnter

Get-process |
Get-member

在按下 return/Enter 键后,你应该看到一个继续提示符(>>),如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_001.jpg

图 3.1 – 使用管道符号作为换行符

我们一直在提到对象——描述我们具体指的是什么将会很有帮助。我们将在下一节中做这件事。

什么是对象?

当我们在 Linux 和 Windows 控制台中运行命令时,命令将字节流输出到标准输出(stdout);这被解释为一个文本文件,保存在内存中。当我们想要操作这个输出的内容时,我们必须使用与查找和操作文本相同的工具;这些工具可能是 Perl、sed、AWK 或其他一些工具。

这意味着我们变得擅长文本操作;我的桌子上大约有七本 Perl 书籍,最早的是上世纪九十年代出版的。PowerShell cmdlet 不产生类似文本的字节流;相反,它们生成对象和对象集合,这些对象是以表格结构保存在内存中的,在我们运行 Get-Process 时产生,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_002.jpg

图 3.2 – 获取进程对象集合

表格中的每一行都是一个对象。每一列是表中对象的一个属性。整个表格是对象的集合。从图 3.1中,我们知道每个对象都是System.Diagnostics.Process类型,并且具有与该对象类型相关联的属性和方法列表。通过管道,我们可以将这个集合发送到另一个 cmdlet 来提取更多信息,或者仅仅调用我们感兴趣的特定属性。如果我们想知道一个默认未显示的属性值,例如一个特定进程在其生命周期中消耗的特权处理器时间,我们可以输入以下命令:

(Get-Process -id 4514).PrivilegedProcessorTime

那个4514是从哪里来的?它是图 3.2中某个pwsh进程的Id属性。从这里,我可以看到我的pwsh进程在特权模式下消耗了 11.75 秒。我知道——我们可能不想知道像pwsh这样简单的进程的这些信息,但如果我们在调查数据库服务器的存储性能问题时,可能会对其他进程的这些值感兴趣。运行以下代码:

Set-Alias -Name processes -Value Get-Process
(get-process -id (processes).id).privilegedprocessortime

在这里,我们将获取当前在客户端上运行的所有进程的特权处理时间,使用我们为Get-Process设置的别名。

并不是所有 cmdlet 都会生成单一类型的对象。一些 cmdlet 可能会生成多个对象,我们需要小心如何处理这些对象并将它们的输出传递到管道中。例如,考虑Get-ChildItem cmdlet。它获取目录或文件夹的内容。一个目录可能包含两种基本类型的项目——即文件和子目录。这两种类型会有不同的属性——例如,我们不能将子目录嵌套在文件中。如果一个管道已经设置为操作文件对象,那么如果也传递了目录对象,它可能会失败。让我们来看看,输入以下命令:

(Get-ChildItem -Path c:\scratch\ | Get-Member).TypeName | Select-Object -Unique

在这里,我们可以看到我 Windows 机器上的C:\scratch目录包含了目录和文件,如下图所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_003.jpg

图 3.3 – 检查目录中对象的类型

我们在这里做了什么?看起来有点复杂。好吧,我们将Get-ChildItem C:\scratch cmdlet 的输出传递给Get-Member。我们只对TypeName属性感兴趣,所以我们把管道放在括号中,这样我们就可以轻松访问我们需要的那个属性。一旦得到所有TypeName实例的集合,我们就把它传递到第二个管道,传给Select-Object,并使用-unique参数告诉它只返回唯一的值。聪明吧?

在接下来的几个章节中,我们将探讨操作这些对象的基本方法。让我们从选择和排序对象开始。

选择和排序对象

我们运行的许多 cmdlet 会产生大量输出,而且其中很多内容可能并不有趣。因此,能够仅选择我们需要的部分并将其排序成有意义的顺序是非常有用的。为此,有两个 cmdlet:Select-ObjectSort-Object。我们通常会看到它们的别名——selectsort

使用 Select-Object

我们在 什么是对象? 部分中使用了 Select-Object 来选择集合中对象的唯一属性。然而,我们可以用它做更多的事情。通过运行以下命令,查看 Select-Object 的帮助文件:

Get-Help Select-Object

在这里,我们可以看到有四组参数集,它们的工作方式有两种——我们可以使用 cmdlet 操作集合的一个或多个属性,或者我们可以用它来选择集合中的一部分对象。让我们尝试第一种方法,输入以下内容:

Get-Process | Select-Object Name, Id

在这里,我们将看到一个包含两个属性的对象集合——NameId。现在,让我们在 Get-Member 中运行它,如下所示:

Get-Process | Select-Object Name, Id | Get-Member

在这里,我们可以看到我们已将那组 System.Diagnostics.Process 对象转变为 Selected.System.Diagnostics.Process 对象——这些对象只有两个属性——我们在 Select-Object cmdlet 中使用的 NameId 属性:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_004.jpg

图 3.4 – 新对象的属性

我们现在仍然有相同数量的对象,但它们只包含我们感兴趣的属性。这有两个好处;首先,PowerShell 在处理这些较小的对象时会更快,其次,PowerShell 将需要更少的内存。缺点是,我们不再能访问管道中原始对象的那些选择的属性。

我们可以使用 Select-Object 的第二种方式是从集合中选择一部分对象。实现这一点的参数在第一个参数集中;-first-last-skip。每个参数都需要一个整数作为参数。-first 5 会选择管道中的前五个对象,而 -last 2 会选择管道中的最后两个对象,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_005.jpg

图 3.5 – 选择对象的子集

我们可以使用 -skip 参数来跳过开始或结尾的值,如下所示:

1,2,3,4,5,6,7,8 | Select-Object -First 2 -skip 1

这将返回整数 23,它们是跳过第一个后得到的前两个元素。

活动 1

我们如何从这个数组中返回 23478

以这种方式运行Select-Object的问题在于,除非我们能够控制集合中对象的顺序,否则我们只是在随机抓取对象。这引出了下一个 cmdlet,Sort-Object

使用 Sort-Object 排序对象

当我们运行Get-Process时,进程会按进程名称的字母顺序返回。这是由 PowerShell 源代码决定的。然而,我们可以通过使用Sort-Object cmdlet 来更改对象的显示顺序(从而间接地重新排序管道中的对象)。

Sort-Object可以根据一个或多个属性对对象集合进行排序。我们不需要指定任何参数来运行它;如果我们没有指定排序的属性,它将根据管道中第一个对象的默认排序属性来对集合进行排序,该属性深藏在 PowerShell 的源代码中,并且很难找到。

这意味着什么?记住,Get-ChildItem会生成两种类型的输出。默认情况下,当你运行Get-ChildItem时,你会首先得到所有第一类型对象(System.IO.DirectoryInfo,即目录)的列表,然后是所有第二类型对象(System.IO.FileInfo,即文件)的列表,如以下截图中的第一个示例所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_006.jpg

图 3.6 – 运行 Select-Object 没有参数的效果

在第二个示例中,Get-ChildItem -Path C:\Scratch\ | Sort-Object,我们得到了一个按字母顺序排列并混合的所有对象列表;它忽略了对象类型。

我们可以添加一个属性名来根据该属性对我们的集合进行排序。例如,我们可以运行Get-Process并按工作集大小进行排序,像这样:

Get-Process | Sort-Object -Property ws

这很好。不过,它们已经按照默认的升序进行了排序,因此我们最感兴趣的进程——那些消耗内存最多的——排在了表格的底部。我们可以通过添加另一个参数-Descending来解决这个问题:

Get-Process | Sort-Object -Property WS -Descending

这会产生一个更有用的输出,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_007.jpg

图 3.7 – 使用 Sort-Object 按降序排序

我们甚至可以一次按多个属性进行排序。例如,我们可以尝试以下操作:

Get-Process | Sort-Object -Property SI, WS -Descending

这将按会话 IDSI)排序,然后按工作集WS)排序。

让我们来看一下帮助文件。在这里,我们可以看到Sort-Object有三个参数集,它们的工作方式大致相同;唯一的区别是-top-bottom-stable参数。-top-bottom参数比较容易理解,但-stable就不那么直观了。当我们运行Sort-Object时,如果存在相等值的对象,它会根据其内部逻辑输出对象的顺序,而不一定是它们被接收的顺序。-stable参数(以及-top-bottom)会在排序的属性相等时,保持Sort-Object接收对象的顺序。

我们现在可以看到如何将这两个 cmdlet,Sort-ObjectSelect-Object,结合起来生成有意义的信息集合。例如,我们可以键入以下内容:

Get-Process | Sort-Object CPU -Descending | Select-Object -First 5

这将获取我们当前 CPU 占用最多的五个进程,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_008.jpg

图 3.8 – 结合 Sort-Object 和 Select-Object

那么,如果我们不想要前五名呢?如果我们想要所有使用 大量 CPU 的进程呢?这就是过滤的作用。

过滤对象

我们可以使用 Where-Object cmdlet 以更复杂的方式过滤对象。Where-Object 也查看管道中对象的属性,但它还可以做出决定,输出哪些对象,丢弃哪些对象。尝试以下操作:

Get-Process | Where-Object -Property CPU -gt -Value 1

这将返回一个进程列表,其中 CPU 属性的值大于 1。在实际操作中,我们很少看到人们为参数包含 -Property-Value 名称,因为它们是位置参数。更常见的写法如下:

Get-Process | where CPU -gt 1

等一下,-gt 是什么?-gt 参数是一个 比较运算符,这是编程中的一个重要概念。

理解比较运算符

比较运算符在使用 Where-Object cmdlet 时作为开关参数表示,导致帮助文档成为一个冗长且复杂的文档,包含许多参数集,因为每次只能使用一个比较运算符。基本的比较运算符如下表所示:

比较运算符区分大小写的运算符
相等-``eq-``ceq
不等式-``ne-``cne
大于-``gt-``cgt
小于-``lt-``clt
大于或等于-``ge-``cge
小于或等于-``le-``cle
通配符相等-``like-``clike

表 3.2 – 基本比较运算符

默认情况下,运算符是不区分大小写的,因此 -eq top-eq TOP 在功能上是相同的。也有一些 NOT 运算符可以获得相反的结果,例如 -NotLike。此外,我们还有一些更高级的比较运算符,如下所示:

  • 使用 -match 根据正则表达式获取值。

  • 使用 -in 获取属性值在指定数组中的对象。我们将在 第四章 中讨论数组,PowerShell 变量和 数据结构

  • 使用 -contains 获取对象,其中指定的值可能在包含数组的属性中,而不是单一值。

让我们探索这些操作的一些工作方式。尝试运行以下命令,以获取正在运行的 PowerShell 进程列表:

Get-Process | Where-Object ProcessName -eq pwsh
Get-Process | Where-Object ProcessName -like pwsh
Get-Process | Where-Object ProcessName -like *pwsh
Get-Process | Where-Object ProcessName -like *wsh
Get-Process | Where-Object ProcessName -contains pwsh
Get-Process | Where-Object ProcessName -in "pwsh", "bash"

最后一条能正常工作,是因为我们给 Where-Object 提供了一个包含两个项的数组,"pwsh""bash",并要求它返回任何 ProcessName 属性值在该数组中的对象。实际上,数组可能不会像这样是一个字符串列表,而是通过运行另一个 cmdlet 得到的更复杂的东西。

活动 2

为什么 Get-Process | Where-Object ProcessName -contains *wsh 没有输出任何结果?

这都很有意思,但如果我们想要查找更复杂的内容,比如对两个属性进行过滤,或者查找某个范围内的值,会发生什么呢?

理解 Where-Object 高级语法

到目前为止,我们一直在使用Where-Object与所谓的-FilterScript参数。这个参数允许我们将一个简短的脚本对象传递给 cmdlet,然后该 cmdlet 在管道中的每个项目上运行。

过滤器脚本有-and-or-not。让我们看看之前的一个例子,如何在高级语法中使用。我们在本节早些时候输入了以下内容:

Get-Process | Where-Object ProcessName -eq pwsh

这让我们得到了所有正在运行的pwsh进程的列表,使用的是基本语法。

使用高级语法编写相同命令的形式如下:

Get-Process | Where-Object -FilterScript {$PSItem.ProcessName -eq 'pwsh'}

过滤器脚本是被花括号包围的部分——也就是$PSItem.ProcessName -eq 'pwsh'。让我们来解析一下。-eq 'pwsh'对我们来说很熟悉,因为我们之前使用过,但$PSItem.ProcessName又是什么呢?这是一个结构,允许我们访问当前正在处理的对象的ProcessName属性。$PSItem是课本之外的一个$PSItem;这个变量通常写作$_美元符号下划线);例如,$_.ProcessName -eq 'pwsh'。在基本语法中,我们不需要对pwsh加引号,但在高级语法中,我们需要加引号,以便脚本知道我们传递的是一个字符串值,像这样:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_009.jpg

图 3.9 – 使用 Where-Object 过滤的三种方式,其中一种是错误的

没有引号时,cmdlet 会将pwsh解释为下一个 cmdlet。如果你仔细查看错误,你会发现它并没有走到那一步,因为-eq缺少了一个值。如果在这里使用单引号或双引号并没有太大关系,但最佳实践是使用单引号,除非你需要双引号的一些特殊功能,我们将在第四章中讨论,PowerShell 变量和 数据结构

高级语法中的多个过滤器

现在我们理解了语法,可以开始使用它来组合过滤器,以生成更复杂的结果。试试这个:

Get-Process | Where-Object -FilterScript {$PSItem.ProcessName -eq 'pwsh' -and $PSItem.CPU -gt 1}

这应该给你一个pwsh进程的列表,其中CPU值大于 1。现在,如果你将CPU值改为更高的数字,你应该会看到输出发生变化:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_010.jpg

图 3.10 – 使用 Where-Object 高级语法组合过滤器

要注意,脚本块语法是严格的。除非我们小心且准确地输入,否则我们将得不到期望的结果。例如,假设我们输入以下内容:

Get-Process | Where-Object -FilterScript {$PSItem.ProcessName -eq 'pwsh' -and CPU -gt 25}

在这里,我们会得到一个错误,提示You must provide a value expression following the '-and' operator。因为我们能看到这个错误,我们可以通过将CPU替换为$PSItem.CPU来修正它。然而,假设我们只想要名为pwshbash的进程,并输入如下:

Get-Process | Where-Object -FilterScript {$PSItem.ProcessName -eq 'pwsh' -or 'bash'}

在这里,我们不会得到错误,只会得到错误的结果,如下图所示。正确的语法在下图的第二个示例中展示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_011.jpg

图 3.11 – 小心语法,尤金

我们还可以使用高级语法来访问属性的属性。让我们运行以下命令:

Get-Process | Get-Member

在这里,我们可以看到ProcessName属性是一个字符串,因此它具有字符串对象的属性。这意味着我们可以像这样运行:

Get-Process | Where-Object -FilterScript {$_.ProcessName.Length -lt 5}

在这里,我们要查找所有机器上ProcessName少于5个字符的进程。我们还使用了更常见的$_来代替$PSItem。你必须习惯这个。

过滤优化

考虑以下两个 cmdlet:

Get-Process | Sort-Object -Property CPU -Descending | Where-Object CPU -gt 1
Get-Process | Where-Object CPU -gt 1 | Sort-Object -Property CPU -Descending

它们产生相同的结果(至少在我的机器上是这样)。然而,在我的客户端,第一种方法需要 29 毫秒,而第二种方法只需要 20 毫秒。你可以自己试试,使用Measure-Command cmdlet,像这样:

Measure-Command {Get-Process | Sort-Object -Property CPU -Descending | Where-Object CPU -gt 1}
Measure-Command {Get-Process | Where-Object CPU -gt 1 | Sort-Object -Property CPU -Descending}

有时候,由于它们都是非常短的管道,你可能会得到一个意外的结果,但如果你连续运行它们 10 次,第二个 cmdlet 几乎每次都会在某些方面比第一个更快。这种变化是由于在你的客户端上有其他程序在运行,它们与 PowerShell 争夺资源。

本章前面我们讨论了如何减少 PowerShell 生成结果所需的处理和内存量。过滤优化是实现这一点的好方法。我们应该尽早在管道中进行过滤,以减少 PowerShell 需要处理的对象数量。这里有一个基本规则:左侧过滤

我们不仅仅有Where-Object cmdlet 用于过滤。许多 cmdlet 也具有过滤参数,这些参数要么是显式的,参数名为-Filter,要么是执行常见过滤任务的参数。例如,Get-ChildItem cmdlet 有-File-Directory参数,可以将输出限制为这两种对象类型中的一种。尽可能使用 cmdlet 的内建参数来过滤对象,再将它们传递到管道中进行进一步处理。

活动 3

如何找到具有-Filter参数的 cmdlet 列表?

现在,我们已经相当了解如何将管道中的对象限制为我们感兴趣的对象。接下来,我们将看看如何对这些对象执行操作。

枚举对象

我们经常需要对正在处理的对象执行某些操作。大多数时候,会有相应的 cmdlet 来执行这个操作,但有时也没有。例如,假设我们想输出文件夹中某些项的文件名和路径。没有一个便捷的属性可以仅输出文件名和路径;有像 pspath 这样的属性,可以获取我们想要的内容,但会包含一些额外信息,并没有完全符合我们需求的属性。然而,对于 Get-ChildItem 返回的对象,有一个方法可以实现这一点:tostring()。我们可以通过枚举每个项来执行这个方法,如下所示:

Get-ChildItem myfiles | Foreach-Object -MemberName tostring

这将产生我想要的完全输出,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_012.jpg

图 3.12 – 基本枚举

这是一个非常简单的示例。就像 Where-Object 一样,Foreach-Object 有基本语法和高级语法,高级语法与我们在前面部分看到的非常相似。你必须将一个脚本块提供给 ForEach-Object-Process 参数。要使用高级语法运行最后一个 cmdlet,我们需要输入以下内容:

Get-ChildItem myfiles | ForEach-Object -Process {$_.tostring()}

如下图所示,输出是相同的。请注意,当使用脚本块时,方法名 tostring 后面必须跟着一对括号:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_013.jpg

图 3.13 – 高级枚举

如果方法需要参数,那么我们需要将它们放在括号内,并以逗号分隔,如下所示:

('Powerhell').Insert(5, 'S')

这将通过在原始字符串的第 5 个位置插入 'S' 字符串来修正拼写。我们不再像以前那样经常看到交互式枚举,因为通常,cmdlet 会被编写来执行我们可能想要交互式枚举的大多数操作。然而,这在脚本编写中是一个重要的概念,我们将在 第五章,“PowerShell 控制流 – 条件语句与循环”中看到。不过,这里有一个我们可以使用它的有用技巧 —— 重复执行某个过程指定次数。试试看:

1..10 | Foreach-Object {Get-Random}

所以,在管道的第一部分,我们使用范围操作符(..)创建一个包含从 1 到 10 的 10 个整数的数组。在第二个 cmdlet 中,我们没有使用 $PSItem 管道变量 —— 我们只是指示它对管道中的每个项运行一次。正如你所看到的,我们不仅可以在脚本块中放入对象方法;我们也可以将 cmdlet 和脚本放入其中。

并行枚举

枚举的一个问题是,当对象数量很多,或者过程很复杂时,它可能需要很长时间。在 PowerShell 7 中,我们获得了并行运行 ForEach-Object 进程的能力。尝试运行以下代码,它会输出从 1 到 10 的数字:

1..10 | ForEach-Object {
$_
Start-sleep 1}

当你在每行后按 Enter 时,应该会看到一个继续提示,直到你关闭大括号。慢吧?打印 10 个数字要 10 秒钟。现在,让我们尝试使用并行处理:

1..10 | ForEach-Object -Parallel {
$_
Start-Sleep 1}

现在,你应该看到数字按每五个一组打印出来。我们可以通过-ThrottleLimit参数来改变并行处理的数量。

现在,我们已经探索了一些用于操作管道的有用 cmdlet,并且体验了第一次脚本编写(是的,这就是你刚才做的),接下来我们将看看管道是如何工作的。

管道如何工作 – 参数绑定

PowerShell cmdlet 输出与其他通用 shell 的主要区别在于,它的输出不是类似文件的内容,而是一个对象,具有类型、属性和方法。那么,一个 cmdlet 生成的对象是如何传递给另一个 cmdlet 的呢?

Cmdlet 只能通过它们的参数接受输入。没有其他方式,因此输出对象必须通过管道传递给下一个 cmdlet 的参数。考虑以下 cmdlet 管道:

Get-Process | Sort-Object -Property CPU

这里我们只看到了一个参数,-property,它被赋予了CPU的值。那么,发生了什么呢?Sort-Object被赋予了两个参数,但我们看不见其中一个。这就是所谓的管道 参数绑定

PowerShell 将第一个 cmdlet Get-Process的输出传递到第二个 cmdlet,并且必须对其进行处理,因此它会查找第二个 cmdlet 中可以接受 PowerShell 当前持有的对象的参数。这可以通过两种方式发生:ByValueByPropertyName。让我们详细看看这两种方式。

ByValue是默认方法,PowerShell 总是首先尝试这个方法,所以我们从这个开始。

理解ByValue参数绑定

我们可以通过输入以下命令查看Sort-Object的帮助文件:

Get-Help Sort-Object -Full

看一下参数。你会看到,只有一个参数可以接受来自管道的对象:-InputObject。帮助文件对它有如下描述:

-InputObject <System.Management.Automation.PSObject>
        To sort objects, send them down the pipeline to `Sort-Object`. If you use the InputObject parameter to submit a collection of items, `Sort-Object` receives one object that represents the collection. Because one object cannot be sorted, `Sort-Object` returns the entire collection unchanged.
        Required?                    false
        Position?                    named
        Default value                None
        Accept pipeline input?       True (ByValue)
        Accept wildcard characters?  false

这里,我们可以看到它只接受ByValue输入,并且只接受PSObject类型的输入。PSObject是非常宽泛的,它意味着 PowerShell 中的任何对象。所以,我们可以用它来排序一个数字数组,因为它们是System.Int32类型的对象,如下图所示。注意,正如帮助文件中所描述的那样,我们不能直接将数组传递给-InputObject参数;它必须通过管道。如果我们尝试通过参数显式传递数组,它会将整个数组作为单个对象返回,并且不会排序。我们需要让它通过管道,一次传递一个项:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_014.jpg

图 3.14 – 正确与错误使用-InputObject参数

让我们再看一个。我们可以从Get-ChildItem的帮助文件中看到,它有一个参数-path,可以接受ByValue管道输入,并且接受字符串对象。这意味着我们可以做类似这样的事情,把myfiles字符串放入管道中:

'myfiles' | Get-ChildItem

在这里,我们将得到一个有意义的输出——myfiles 目录中所有项目的列表。如果我们有一个输出路径作为字符串的 cmdlet 管道,我们可以将其传递给 Get-ChildItem 来获取内容。使用 ByValue 时需要记住的重要一点是,你传递到管道中的对象类型必须与下一个接受管道输入的 cmdlet 的参数所要求的对象类型匹配。

Get-ChildItem 很有意思,因为接受管道输入的参数不是 -InputObject 参数,而是 -path。如果你尝试将一个字符串传递给 Get-ChildItem,同时又显式指定了 -path 参数,会发生什么?你将会得到一个错误,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_015.jpg

图 3.15 – 管道破坏

前面的错误表示没有参数可以接受管道输入,尽管我们知道有这个参数。这是因为我们在开始处理管道中的对象之前,将一个值绑定到 Get-ChildItem,从而有效地将该参数从可用的参数列表中移除。如果我们看到这个错误,通常需要检查是否已经使用过该参数,而不是在沮丧中将笔记本电脑扔向墙壁。

让我们来看一下另一种将管道内容绑定到参数的方法:ByPropertyName

ByPropertyName 参数绑定

PowerShell 会首先尝试按 ByValue 绑定参数。如果 ByValue 不可用,它才会尝试使用 ByPropertyName 来强行绑定管道对象。如果你的第一个 cmdlet 生成的对象类型不适合下一个 cmdlet 的 pipeline-accepting 参数会发生什么?PowerShell 会查看第二个 cmdlet 是否有接受管道输入的参数,并且该参数的属性名与对象匹配——通常是 -Id-Name

不出所料,Stop-Process 是一个停止进程的 cmdlet。如果我们查看帮助文件,我们会看到三个参数接受管道输入:-InputObject,它接受 ByValue 对象,以及 -Id-Name,它们接受 ByPropertyName。现在,让我们输入以下内容:

Get-Random | Stop-Process

在这里,我们会遇到一个错误——与图 3.15中显示的错误相同。我们知道 Stop-Process 有三个接受管道输入的参数,因此这不是第一个原因。我们也没有显式地将任何内容绑定到参数上,因此错误一定是因为管道中的对象类型不正确。如果我们使用 Get-Member 来确定 Get-Random 生成的对象类型,然后查阅 Stop-Process 的帮助文件,我们会发现 Get-Random 生成的是 System.Int32 类型的对象,而 Stop-Process 需要的是 System.Diagnostics.Process 类型的对象。所以,如果管道中没有正确的对象类型,为什么 PowerShell 没有尝试使用 ByPropertyName 呢?其实它是尝试过的,但 Get-Random 输出的对象的属性名称并没有与 Stop-Process 中的 -Id-Name 参数匹配。让我们来玩点有趣的。输入以下内容:

New-Object -TypeName PSObject -Property @{'Id' = (Get-Random)} | Stop-Process -WhatIf

我们在这里做什么?我们使用 New-Object cmdlet 创建一个通用的 PowerShell 对象(-TypeName PSObject),并添加一个属性 Id,该属性的值由运行 Get-Random cmdlet 生成的随机数填充。如果我们将该 cmdlet 的输出传递给 Get-Member,就可以看到这个属性:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_03_016.jpg

图 3.16 – 创建自定义对象

一旦我们创建了这个新的自定义对象,我们就可以将它传递给 Stop-Process。由于对象类型不正确,它不能绑定到 -InputObject 参数,但该对象有一个与 -Id 参数匹配的属性名称,因此它会绑定到这个参数。最后,因为我们不想玩得太过火,所以我们使用了 -WhatIf 参数,以防 Get-Random 给我们提供一个有效的进程 ID。-WhatIf 是大多数 PowerShell cmdlet 中常见的参数之一,它告诉我们如果运行该 cmdlet 而不实际更改任何内容,会发生什么。

括号命令

几次之前,我们已经运行了括号中的 cmdlet,就像我们之前所做的那样。括号是一种覆盖 PowerShell 执行顺序的方式。就像数学中一样,括号是一个指令,表示优先执行。当我们在 PowerShell 中使用它们时,括号中的内容必须在该管道段的其他任何内容之前完成处理。这为我们提供了另一种直接将输入传递给参数的方式。

在前面的示例中,我们尝试运行以下内容:

Get-Random | Stop-Process

这并没有成功。尽管对象类型(System.Int32)对于 -Id 参数来说是正确的,但 PropertyName 值是错误的。使用括号时,我们可以显式地将内容传递给 -Id 参数,像这样:

Stop-Process -Id (Get-Random)

首先,PowerShell 会生成一个随机数,然后将其传递给 -Id 参数。我们将在本书中看到更多括号的有用示例。

故障排除管道 – 管道追踪

在这一章中,我们已经做了很多工作,现在是时候放松一下,和Trace-Command玩得开心了。至少,我觉得这很有趣;其他意见另当别论。然而,这个 cmdlet 确实让我们深入了解 PowerShell 的工作原理,从而能够看到它的实际操作。

运行以下代码:

Trace-Command -Name ParameterBinding -Expression {New-Object -TypeName PSObject -Property @{'Id' = (Get-Random)} | Stop-Process -WhatIf} -PSHost

在这里,我们运行 Trace-Command 并请求它记录 ParameterBinding 事件。我们将之前运行的 cmdlet 作为脚本块中的表达式传给它,然后通过 -PSHost,我们告诉它将输出显示到屏幕,而不是其默认的调试流,正如我们在本章一开始谈论流时所看到的。

现在,我们屏幕上满是黄色文字,显得很乱;我们需要仔细看看其中的内容。我们感兴趣的问题是:

  • 自定义对象绑定到哪里了?

  • 自定义对象是如何绑定的?

这是我的输出,整理后的版本,每一行下方有注释:

  1. DEBUG: 绑定管道对象到 参数:[Stop-Process]

在这一行中,我们开始绑定到 Stop-Process 的参数。

  1. DEBUG: 管道对象类型 = [``System.Management.Automation.PSCustomObject]

这告诉我们管道中的对象类型是什么。

  1. DEBUG: 参数 [InputObject] 管道输入 ValueFromPipeline NO COERCION

这告诉我们,-InputObject 只接受 ByValue 对象

  1. DEBUG: 绑定参数 [@{Id=1241688337}] 到 参数 [InputObject]

arg [1241688337] 是生成的随机数。

  1. DEBUG: 绑定集合参数 InputObject:参数类型 [PSObject],参数类型 [System.Diagnostics.Process[]],集合类型 Array,元素类型 [System.Diagnostics.Process], 无强制元素类型

这显示我们对象类型不匹配。

  1. DEBUG: 绑定参数 [@{Id=1241688337}] 到参数 [``InputObject] 被跳过

在这里,我们跳过了对 -InputObject 的绑定。

  1. DEBUG: 参数 [Id] 管道输入 ValueFromPipelineByPropertyName NO COERCION

这一行显示 -Id 参数接受 ByPropertyName 作为输入。

  1. DEBUG: 绑定参数 [1241688337] 到 参数 [Id]

DEBUG: 绑定集合参数 Id:参数类型 [Int32],参数类型 [System.Int32[]],集合类型 Array,元素类型 [System.Int32], 无强制元素类型

这显示我们对象类型匹配。

  1. DEBUG: 绑定参数 [System.Int32[]] 到参数 [``Id] 成功

在这里,我们得知我们已经成功绑定到 –``Id 参数。

所以,我们得到了问题的答案——管道中的对象绑定到了 -Id 参数,ByPropertyName

这只是对 Trace-Command 的一个快速介绍。如果你的管道失败了,且你确信对象类型匹配,或者你确保了属性名称匹配,并且没有明确绑定到唯一接受管道输入的参数,那么这个 cmdlet 是你了解发生了什么的最佳希望。

总结

在本章中,我们讨论了一些非常有趣且相当技术性的主题。我们首先描述了管道的作用,然后探讨了选择和排序对象的技巧。接着,我们讨论了过滤对象,并谈到了使用过滤功能的重要性,以便让 PowerShell 能够高效地工作。

从这里开始,我们引入了一个之后会很重要的主题:枚举,并探讨了 PowerShell 7 的一项新特性——并行枚举。在本章的最后部分,我们深入了解了管道如何实现其魔力,并研究了两种参数绑定方法:ByValueByPropertyName。最后,我们玩了一下一个可以让我们深入了解管道工作原理的 cmdlet:Trace-Command

大多数时候,管道只需正常工作。然而,对于那些不工作的情况,本章为我们提供了必要的知识,帮助我们理解管道的工作原理,并且希望能帮助我们修复问题。有时候,cmdlet 作者没有为其 cmdlet 提供接受管道输入的方式。本章向我们展示了如何发现这一点,并给我们提供了一种解决方法。

本章结束了本书关于 PowerShell 工作机制的介绍部分。在下一章,我们将开始编写一些代码,重点讲解变量和数据结构。请继续关注。

练习

以下是一些练习,帮助你巩固本章的知识:

  1. 我们如何使用 PowerShell 仅显示今天是星期几?

  2. 我们需要获取 CPU 使用情况和所有正在运行的进程的路径位置,并且我们不想要过多的无关信息。我们该如何操作?

  3. 现在我们有了列表,如何按字母顺序反向排列路径名?

  4. 这里有很多内容。我们如何确保它只列出 CPU 使用率大于 5 的进程?

  5. 获取我们主驱动器中只读文件的最有效方法是什么?

  6. 我们需要获取我们主目录下所有文件的大小。我们只需要文件名和字节大小。

  7. 我们有一个包含进程名称列表的文件,名为 processes.txt。我们需要使用它来发现本地计算机上进程的信息,因此我们需要找到一个可以从文件中获取内容的 cmdlet。

  8. 如果不实际运行 cmdlet,在 Windows 主机上如果我们在没有 -WhatIf 的情况下运行它会发生什么?

    'bobscomputer' | Stop-Computer -WhatIf
    

如果它不正确,那么正确的 cmdlet 应该是什么?

如果我们在 Linux 主机上运行它,会发生什么?提示:思考一下这个问题。不要尝试它,特别是在没有 -WhatIf 的情况下。

进一步阅读

随着我年龄的增长,我发现计算机历史变得越来越迷人;在我二十多岁时被认为是前沿的概念和设备,如今已经成为布满灰尘的旧遗物。然而,这些旧遗物在某种程度上帮助解释了我们现在所处的位置。Unix 口述历史项目中有一部分内容讲解了管道的概念:dspinellis.github.io/oral-history-of-unix/frs122/unixhist/finalhis.htm

第四章:PowerShell 变量和数据结构

是时候真正理解我们谈论变量时的含义了。变量是计算机科学和数学中的常见概念,因此,理解它们是什么以及它们如何在 PowerShell 中使用非常重要。

我们将从探索变量的字面意义和隐喻意义开始。我们将研究如何在 PowerShell 中使用它们,并将 PowerShell 的工作方式与其他语言进行对比。我们将探索基本数据类型的概念,它们是数据的基本构建块,然后再讨论 PowerShell 使用的常见数据结构。最后,我们将通过解包这一重要且有用的技术来简化 cmdlet,来增加一些乐趣。

在本章中,我们将涵盖以下主要主题。

  • 理解 PowerShell 变量

  • 探索对象类型

  • 发现值类型

  • 输入解释

  • 导航引用类型

  • 解包 – 用哈希表来获取乐趣和利润

理解 PowerShell 变量

变量就像一个盒子。我们可以把东西放进去。我们可以把这些东西拿出来,再放进去别的东西。这个盒子可能包含一件东西,或者可能包含很多东西。它可以包含同一种类型的多个东西,例如,30 双袜子,或者它可以像我的厨房抽屉一样,包含各种不同的东西,包括线。我们可以以不同的方式组织它包含的东西,或者像我的厨房抽屉一样不组织它们。它可能什么也不包含。

变量实际上是内存区域的标签。它仅仅是一个名称和内存中的地址。当你告诉 PowerShell 使用变量的内容时,你是在告诉它去那个内存区域并使用那里的内容。使用标签给我们带来两个优势;首先,它节省了大量输入,尤其是当变量包含多个对象时。其次,它允许我们赋予变量含义,这样我们和其他读代码的人就能明白变量的目的,并且有一个线索了解它可能包含的内容。这比它现在看起来的要重要和有用得多。

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_01.jpg

图 4.1 – 这不是一个变量。向马格里特致歉

PowerShell 的设计目的是易于使用,因此变量可以动态创建,这与一些语言(如 Java)不同,在 Java 中我们必须先声明变量,然后才能给它赋值。我的意思是什么?考虑这行代码;不用打出来,只需思考一下:

$MyVariable = 'some stuff'

我在那里做什么?我正在动态创建一个包含some stuff的变量。

如果我要以更像 Java 风格的方式做这件事,我会这样写:

New-Variable -Name MyVariable
Set-Variable -Name MyVariable -Value 'some stuff'

这将创建一个变量,给它一个名字,然后我们将一个值放入其中。在实际操作中,这种情况非常罕见。大多数人在大多数情况下,都是动态创建变量的。

如果你仔细阅读这些代码行,你会看到第一个示例中包含了$MyVariable,而第二个示例中则是MyVariable,没有$符号。让我们来讨论一下为什么。

变量不是它们的内容

我们很少需要操作一个变量。回到盒子比喻,除非我们五岁,否则通常更感兴趣的是盒子里的内容,而不是盒子本身。MyVariable是分配给变量的名称,是我们用来引用它的标签。$MyVariable则指的是变量的内容。这就是我们感兴趣的东西。让我们来演示一下。

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_02.jpg

图 4.2 – MyVariable 不是 $MyVariable

图 4.2的第一行,我们动态地创建了一个名为MyVariable的变量,并将some stuff放入其中:

$MyVariable = "some stuff"

在第二行,我们通过输入$MyVariable来请求 PowerShell 获取MyVariable的内容,结果返回了some stuff

在第三行,我们只输入了变量的名称MyVariable,但是 PowerShell 并没有理解我们想要什么。

在第四行,我们显式地通过Get-Variable命令请求MyVariable,再次得到了some stuff字符串,但我们还得到了其他一些不属于字符串的内容;Get-Variable命令返回了一个PSVariable对象,而不是变量的内容,即some stuff字符串。我们可以使用Get-Member来确认它是什么类型的变量。输入以下内容:

Get-Variable MyVariable | Get-Member

在第五行,PowerShell 很合理地告诉我们并没有一个名为some stuff的变量——我们这里传递的是MyVariable的内容,而不是变量的名称。

在第六行,看起来我们像是将一个变量传递给Write-Output,但实际上并不是。我们传递的是一个值为MyVariable的字符串,而不是MyVariable的内容。

第七行正确地将MyVariable的内容传递给Write-Output,通过引用$MyVariable

实际上,如果我们总是以$符号开头命名变量,几乎总是会是正确的。这就引出了名字的问题:什么是好的变量名,什么是不好的变量名?

变量命名

在命名变量时,有两个因素需要考虑。首先是我们可以使用的名称,其次是我们应该使用的名称。

我们可以(和不能)使用的变量名

变量名可以包含字母、数字、问号和下划线符号_的任意组合。它们不区分大小写。如果我们将变量名放入花括号{}中,还可以使用其他字符,但我们并不这么做。真的——这样会让生活变得更复杂,而且几乎没有必要。下面是一些好坏变量名的例子:

合法的 变量名非法的 变量名
Z54z54!
ComputerName$``ComputerName
Computer_NameComputer-Name
{``Computer Name}Computer Name
ComputerName?{``Computer Name{}}

表 4.1 – 我们可以使用和不能使用的名字

现在我们知道了可以使用什么样的变量名,那么我们应该使用什么样的名字呢?

我们应该(和不应该)使用的名字

使用变量的一个目的就是赋予其意义。我们通过使变量名具有意义来实现这一点;一个变量名应该给出一些提示,说明变量的内容或用途是什么。虽然MyVariable在我们输入一两行代码时是一个完全合法且合适的名字,但当我们编写脚本时,它并没有提供任何关于它包含的内容或我们希望如何使用它的线索;它只告诉我们它是一个变量,并且它是我的

我的日常工作常常涉及调试别人写的代码。我曾经做过因为脚本中有 20 个或 30 个变量,名字叫aIxyiix3agtd等而做噩梦的事情。我根本不知道它们是什么意思,而且我敢打赌,原作者也不记得了。在命名变量时,我们应该明白,我们的代码将比编写它时被读取的次数要多得多,通常是我们自己读取,有时甚至是在几年后。为了自己着想,给变量起个有意义的名字吧。

我们还应该使用一致的命名规则,例如ComputerName1ComputerName2ComputerName3,而不是ComputerName1computer_name2ComputerNameThree。PowerShell 最佳实践指南建议广泛使用 Pascal 命名法,其中每个单词的首字母都大写,因为这种方式易于阅读,并且与屏幕阅读器兼容。其他语言,如 Python,建议变量名应全部小写,并且单词之间使用下划线分隔:computer_name。无论我们选择哪种方式,保持一致性会使我们的工作更加轻松。

我们也不使用问号。这会显得凌乱,而且可能会导致一些复杂的问题。

最后,我们应该避免尝试使用自动变量或偏好变量的名称。等一下,这是什么?

三种常见的 PowerShell 变量类型

到目前为止,我们一直在讨论一种特定类型的 PowerShell 变量——用户创建的变量。还有两种其他类型:自动变量偏好变量。用户创建的变量只在生成它们的会话或脚本运行时存在;一旦我们关闭 PowerShell 窗口,这些变量就会被销毁。这被称为作用域,我们将在第八章《编写我们的第一个脚本——将简单的 Cmdlet 转化为可重用代码》中详细介绍。自动变量和偏好变量则会在每个会话或脚本中存在。

自动变量

这些是 PowerShell 内部使用的变量。我们已经使用过一个:$PSItem,或$_,它指的是管道中的当前对象。如果我们愿意的话,可以给它赋值,但 PowerShell 会在当前运行的管道完成时清除它,如图 4.3所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_03.jpg

图 4.3 – 不要使用自动变量的名称

在将$PSItem设置为"some stuff"之后,管道完成,变量被清除,丢失了我们试图存储的信息。幸运的是,大多数自动变量是受保护的,如图 4.4所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_04.jpg

图 4.4 – 你不能覆盖一些自动变量

你可以在帮助主题about_Automatic_Variables中查看自动变量的列表,或者可以在网上查看它们:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables

偏好变量

偏好变量存储关于 PowerShell 如何工作的相关信息,使我们能够自定义 PowerShell 的行为。例如,我们可以使用一个偏好变量来确保默认情况下所有我们运行的(支持的)cmdlet 都应用-WhatIf参数。让我们看看它是如何在图 4.5中工作的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_05.jpg

图 4.5 – 使用偏好变量

在第一行中,我们检查了WhatIfPreference变量的值;它是False,默认值。这是合理的,因为我们刚刚开始了 PowerShell 会话。在第二行中,我们通过设置$whatIfPreference = $``true来赋值True

然后我们再次检查其值。果然,现在它是True。让我们看看现在的情况如何。我们运行Get-Process来获取一个合适进程的Id参数;在这个例子中,我将获取pwsh进程的 ID,407。现在,当我们运行Stop-Process -Id 407时,通常会期待pwsh会话结束;但它没有,因为默认情况下,现在所有进程都以-WhatIf参数设置为True运行。

通过这种方式更改偏好变量仅在当前会话或脚本运行时有效。如果你停止并重新启动 PowerShell,它们会恢复为默认值。如果你需要持久化一个偏好,你可以更改你的 PowerShell 配置文件,这是一个 PowerShell 脚本,可以通过查询PROFILE变量并键入$PROFILE来找到。你将学习如何编辑脚本,在第八章,“编写我们的第一个脚本——将简单的 Cmdlet 转换为可重用代码”中。

关于每个偏好变量的完整解释可以在帮助主题About_Preference_Variables中找到,或者你可以在线阅读:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables

最后,我们可以通过键入Get-Variable(不带参数)来查看当前所有变量中的值。

现在我们了解了有关变量(框)的基本情况,我们可以开始看看可以放入其中的内容。让我们来看一下对象类型。

探索对象类型

在上一节中,我们讨论了变量的类型。现在,我们要讨论对象类型——即放入盒子里的东西。变量中对象的类型告诉计算机如何处理它——它有什么属性,以及我们可以对它做什么。输入以下内容:

$MyVariable = "some stuff"
$MyVariable | Get-Member

我们应该能看到类似于图 4.6中的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_06.jpg

图 4.6 – 它是一个字符串

我们已经把一个字符串放进去,我们知道这一点,因为它被告诉了我们——TypeNameSystem.String。此时,MyVariable包含一个字符串。我们可以通过赋予其他值来改变其类型。试着输入以下内容,不加引号:

$MyVariable = 4.2

然后,使用Get-Member检查内容。现在我们得到的是一个System.Double对象类型,表示一个浮动点数。

我们可以做更好的事情。输入以下内容,并使用Get-Member检查变量的内容:

$MyVariable = (Get-Process)
$MyVariable | Get-Member

现在,我们已经将Get-Process的输出保存在我们的变量中,并且内容的类型是System.Diagnostics.Process

变量可以保存任何类型的对象。记住,PowerShell 中的一切都是对象。我们在第三章《PowerShell 管道——如何将 Cmdlet 连接起来》中介绍了一些 PowerShell 对象的细节,但从更通用的编程角度回顾它们是个好主意。

什么是对象?——redux

想象一辆自行车。它是什么颜色的?有什么样的车把?轮径是多少?我的自行车有猿式车把,15 英寸的轮子,表面有划痕的红色闪光漆。你的自行车可能是一个更实用的物品,配有升降把手、20 英寸的轮子,颜色是可用的黑色。我的儿子汤姆的自行车有追逐把手、22 英寸的轮子,颜色是白色。

那里有三个对象,属于(不存在的)TypeName Imaginary.Bike类型。我们可以在表 4.2中列出这些对象。所有这些对象都有相同的一组属性:名称、车把、轮径(英寸)和颜色属性。在每个实例中,它们的属性值不同。其他属性可能包括篮子、车灯等等。有些属性可能是可选的,但有些不是;自行车总会有两个固定直径的轮子和一个颜色。

名称我的自行车你的自行车汤姆的自行车
车把猿式把手升降把手追逐把手
轮径(英寸)152022
颜色红色黑色白色

表 4.2 – 我们的自行车

我们可以在 PowerShell 中创建这些自行车,列出每辆车的属性,并且我们可以通过使用Get-Member看到它们是Imaginary.Bike类型的对象:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_07.jpg

图 4.7 – 三辆假想自行车

我们的自行车也有一组共同的操作方式。我们可以踩踏板来加速它们。我们可以使用刹车来减速它们。这些在第一章中的 Get-Member 是 PowerShell 7 的简介——它是什么及如何获取它。我们期望同一类型的对象具有相同的方法。

这个对象的概念适用于大多数现代编程语言,并且大多数编程语言以类似的方式处理它们。我们刚刚进行的想象练习在 Python 中和在 PowerShell 中同样适用。以下是 Python 中同样的三辆想象中的自行车:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_08.jpg

图 4.8 – 另一种语言中的三辆想象中的自行车

在 PowerShell 中的区别在于一切都是对象。考虑到这一点,让我们探索一些我们希望放入变量中的常见对象类型。让我们从值类型开始。

发现值类型

在 .NET 中的 System.Object 类。总体而言,PowerShell 中的值类型类似于(但并非完全相同于)原始类型。保存值类型对象的内存位置保存实际数据。还有引用类型,我们稍后会看到,它们保存对实际数据的引用;数据保存在其他地方,可能在多个位置。值类型数据是固定大小的,以位为单位。值类型具有单个值。为了说明,让我们看一些值类型。我们将从最简单的值类型,布尔类型,开始。

内存位置——计算机科学 101 提示!

数据存储在内存中的两个不同位置:。栈用于静态分配(不变的事物),而堆则是动态的。堆存储全局信息,其结构就像它的名字一样;这只是一个数据堆,具有树状结构。堆的大小不固定,允许随机访问。对数据的访问可能非常慢,并且随着时间推移,堆会变得分散。

栈是一种有序的固定大小的内存空间。它具有线性结构,最后放入栈的数据位于首位,被移除时也是首先移除的。与堆相比,信息访问要快得多。然而,由于空间有限,我们偶尔会遇到栈溢出,即尝试将更多数据放入栈中而它无法容纳的情况——这相当于计算机科学中的交叉流(《捉鬼敢死队》的参考)。每个运行中应用的线程都有自己的栈,但它们共享应用堆。

当我们创建一个变量时,它被放入栈中。变量引用的数据可能在栈中(对于值类型变量)或者在堆中(对于引用类型变量),但变量本身总是在栈中。

布尔值

布尔类型的变量只能保存两个值之一:true 或 false。就是这么简单。它被称为System.Boolean[bool][bool]是一个简写,避免我们输入更长的类名;它是System.Boolean类型的别名。布尔值非常容易赋值,我们可以使用自动变量$True$False。试试下面的操作:

$MyBoolean = $True
$MyBoolean | Get-Member

我们可以看到类型是System.Boolean。这种类型的变量在脚本中非常有用,正如我们在第五章中将看到的,PowerShell 控制流 – 条件语句和循环。在那一章中,我们还会看到一个相似但略有不同的值类型——[switch]类型——它也只能是 true 或 false,但具有不同的成员集合。

整数

整数是没有小数点的整数。常见的三种整数类型为[int32][int64][byte][int32]是默认类型,它是一个有符号的 32 位数值,表示它可以存储介于-2,147,483,648 和+2,147,483,647 之间的整数。如果我们创建一个变量来存储这些值范围内的整数,那么 PowerShell 会为其分配[int32]类型:

$MyNumber = 42
$MyNumber | Get-Member

如果我们尝试赋值超出该范围的数字,PowerShell 会将其类型设为[int64],它是 64 位长,可以存储介于-9,223,372,036,854,775,808 和+9,223,372,036,854,775,807 之间的数字。

我们有时会在书籍和网页上看到[int][long],而不是[int32][int64]——这两个术语可以互换使用。

[byte]是一种特殊的整数类型;它没有符号,并且是一个介于 0 和 255 之间的整数,长度为 8 位——即 1 字节。

活动一

为什么不使用单一的整数类型[int64]呢?为什么要搞得这么复杂?

实数

在数学中,实数是存在的数字——它们可以是整数(42)、小数(3.1)、分数(1/3)或无理数(π、√2)。例如,-1 的平方根这样的虚数并不是实数,尽管它们在很多方面非常有用。在 PowerShell 中,整数使用我们刚刚讨论的整数类型表示,而常见的无理数则通过[math]类型表示。我们大多数时候使用实数类型来表示浮点数。

有三种常见的变量类型用于处理实数:[single](或[float])、[double](或[long])和[decimal]。[single]类型是 32 位,精度为 7 位数字(即小数点右侧的数字)。[double]类型是 64 位,精度为 15 位或 16 位数字。默认情况下,当你使用小数时,PowerShell 会创建[double]类型的变量:

$MyRealNumber = 42.0
$MyRealNumber | Get-Member

[Decimal]类型是 128 位长,精度可以达到 28 位数字。这在科学和金融计算中使用。

字符

[char] 类型描述了一个单一字符,是我们当前使用的 UTF-16 字符集中的一个成员。它是一个 16 位的值,对应于当前字符映射中的一个符号。试试以下操作:

[char]$MyChar = 24
$MyChar

在我的机器上,我看到一个上箭头 。你可能看到不同的东西。但你不会看到的是 24。我们做了一些新的尝试;我们在变量前面加了一个类型加速器。为了理解为什么,我们需要讨论类型。

类型说明

现在我们理解了一些基本类型,接下来需要讨论的是类型声明以及语言,特别是 PowerShell,如何操作变量中对象的类型。计算机是如何知道一个对象的类型的呢?编程语言可以分为两类:一种是只支持 静态类型,其中变量能容纳的对象类型在变量创建时就已声明,并且不会改变;另一种是支持 动态类型,其中变量的内容决定了它的类型。在动态类型语言中,变量的类型是可以改变的。试试以下操作:

$MyVariable = 'some stuff'
$MyVariable | Get-Member
$MyVariable = 42
$MyVariable | Get-Member

我们可以看到,$MyVariable 的对象类型会根据其中的内容变化,这使得 PowerShell 成为一种动态类型语言。事实上,Bruce Payette 在《PowerShell in Action》中将 PowerShell 描述为 类型随意,因为它会尽力让我们放入变量中的任何东西都能按照我们想要的方式运作。只要变量中的对象具有正确的属性,PowerShell 就不关心它的类型是什么。我们来试试。

动态类型与静态类型

如果我们输入 $x = '4',我们得到 [string]。如果我们输入 $x = 4,没有引号,我们得到 [int32]

现在,让我们看看如果输入以下内容会发生什么:

$x = '4' + 4

试试看,并将 $x 传输给 Get-Member 来确认类型。PowerShell 会尽力解释你想要的内容。它选择将第二个 4 作为字符串处理,并将两个字符串拼接在一起,结果是 44。惊人吧?试试反过来:

$x = 4 + '4'

现在它将第二个 '4' 视为整数,并返回 8。在大多数语言中这行不通:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_09.jpg

图 4.9 – Python 不是随便的

在 Python 中,我们可以将字符串连接在一起,这叫做 连接,我们也可以将整数相加(称为 加法),Python 会动态地根据变量内容的不同将其类型定义为字符串或整数。但是,我们不能将字符串和整数混合在一起;Python 会抛出错误。

这通常是一个好事,但有时也可能是双刃剑。因为 PowerShell 会尽力做我们想要的事情,它有时会做出我们不希望的行为。假设我们始终希望内容是字符串类型,但某个数据输入错误或不当操作将整数放了进去。这会影响我们之后对该变量的操作,因为它已经将对象类型更改为整数。在不太宽容的语言中,我们会得到一个明确指出问题所在的错误,但 PowerShell 可能会给我们一些意外的结果,若幸运的话可能需要很长时间来调试,若不幸则可能导致灾难性后果。

变量类型转换

幸运的是,我们可以通过在创建变量时使用加速器[int32]来让 PowerShell 更像静态类型语言:

[int32]$MyNewVariable = '42'
$MyNewVariable | Get-Member

即使我们给它的是字符串,它也是一个[int32]类型。请注意,如果我们给它一个不容易解释为整数的内容,它将无法正常工作:

[int32]$MyNewVariable = 'Forty Two'

这会抛出一个错误。重要的是,即使我们只输入以下内容,它也会抛出错误:

$MyNewVariable = 'Forty Two'

因为当我们在几行前创建MyNewVariable时,我们将其定义为[Int32]类型,所以现在它只能保存这种类型的内容。这也可能导致一些令人困惑的结果。

当我们尝试将一个浮动点数放入MyNewVariable时,可能会期望此命令抛出一个错误:

$MyNewVariable = 4.2

但是不行。PowerShell 只是选择最接近的整数,并使用它:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_10.jpg

图 4.10 – PowerShell 按照指示操作

不过,我们可以将变量转换为新的类型,如下所示:

[single]$MyNewVariable = 4.2

现在,我们已经得到了正确的值。类型转换是一项非常有用的技术,稍后我们将会大量使用,特别是在第七章,“PowerShell 和 Web – HTTP、REST 和 JSON”中。它允许我们将一系列字符串转换成可以与基于 Web 的应用程序交互的 XML 和 JSON 对象。

有几种常见的方法可以使用类型转换来改变变量内容的类型。首先,我们可以通过将内容作为已定义的类型复制来创建一个新变量。让我们创建一个我们知道包含整数的变量:

[int32]$MyVariable = 42

我们可以将其转换为新变量,并作为字符串使用:

$MyString = [string]$MyVariable
$MyString | Get-Member

我们也可以反过来操作:

[string]$MyOtherString = $MyVariable

活动二

$myString$MyOtherString 有什么区别?

提示:我们接下来可以将哪些类型的对象放入每个变量中?

其次,我们可以在不创建新变量的情况下进行转换;我们可以在代码中使用[string]$MyVariable,如果可能,PowerShell 会将其内容视为正确类型的对象。

这就是我们需要了解的简单值类型,以及 PowerShell 如何使用动态和静态类型。接下来,我们需要查看更复杂的复合变量类型——引用类型。

导航引用类型

现在我们对类型有了一些了解,也知道了值类型是如何工作的,我们可以讨论另一种主要的对象类型——引用类型。在前一节发现值类型中,我们将值类型对象与其他语言中的基本数据类型进行了比较。引用类型对象相当于数据结构。引用类型对象只在堆栈中保存指向堆中更多数据的引用;这一点很重要,因为引用类型对象没有固定大小。一般来说,引用类型最多可以包含 2 GB 的数据。为了证明这一点,我通过从古腾堡计划下载文本文件,将莎士比亚的戏剧《哈姆雷特》转化为字符串:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_11.jpg

图 4.11 – 字符串化哈姆雷特

如我们所见,整个字符串大约有 197,000 个字符。

我在这里做了两件有趣的事;我使用了字符串类型的 length 属性来查看我的字符串有多长(以字符为单位),但我也使用了 GetType() 方法来检查对象类型,而不是使用管道将其传递给 Get-Member。在我们进一步讨论属性、方法以及可以用字符串做的其他有趣事情之前,我想先从一个简单的引用类型对象——数组开始。

数组

数组是一种固定长度的数据结构,包含一组对象。数组中的对象不一定是同一类型,也不一定是排序或有序的。一个数组可以包含零个或多个对象。我们可以通过隐式使用逗号字符 , 或显式使用 [array] 来告诉 PowerShell 创建一个数组。试试下面的操作:

$MyArray = 1,2,3
$MyArray.GetType()

你试过了吗?你真的应该试一下。随着我们继续这个部分,我会回到我们在这里创建的数组。如果你到了部分末尾才发现没创建数组,然后又得回去从头开始,那会很糟糕。

要创建一个单元素数组,你可以这样做:

$number = ,1
[Array]$MyTinyArray = 1

输出可以在以下截图中看到:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_12.jpg

图 4.12 – 两个单元素数组

我们可以看到,我们每次都会创建一个 Object[] 类型的 BaseType,它是 System.Array

我们还可以使用范围运算符(..)来创建数组。尝试以下操作:

$NewArray = 1..10

我们应该得到一个包含从 1 到 10 所有数字(包括 10)的数组。

最后,我们可以使用数组运算符 @()

$AnotherArray = @(1..5; "some stuff")

注意,当我们分隔不同类型的对象并使用表达式时,必须使用分号,而不是逗号。当我们开始编写脚本并使用展开技巧时,这一点特别有用,这将在本章最后一节中讲解。

数组基础

数组中的每个对象称为元素。我们可以通过调用整个数组($NewArray)来列出元素,和调用其他变量一样:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_13.jpg

图 4.13 – 列出数组的元素

我们可以选择通过传递索引来调用数组中的单个元素。这将获取第一个元素:

$NewArray[0]

这将获取倒数第二个元素:

$NewArray[-2]

我们可以调用多个元素:

$NewArray[0,2,5,7]

我们还可以调用一系列元素:

$NewArray[6..9]

我们可以通过直接赋值来改变数组元素的值:

$NewArray[5] = 5

我们可以通过使用+=运算符向数组中添加元素:

$NewArray += 11

但是从数组中删除一个元素是有点棘手的;相反,创建一个只包含你想要的元素的新数组。

默认情况下,数组可以包含多种类型的对象:

$ScruffyArray = 1, 'socks', (Get-Process)

我们可以限制数组中对象的类型,使其只能包含某一类型的对象:

[Int32[]]$IntArray = 1..5

现在尝试以下操作:

$IntArray += 'socks'

哎呀,我们遇到了错误:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_14.jpg

图 4.14 – 你不能把它留在里面

如果我们看一下,可以看到IntArray对象的类型是[int32[]],其基础类型是System.Array,而元素的类型是[Int32]。我们在第二章《探索 PowerShell Cmdlet 和语法》中看到了这个语法,当时我们在查看Get-Help cmdlet 时使用过它。一个空的方括号表示一个可以包含多个值的参数;换句话说,一个数组:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_15.jpg

图 4.15 – System.String 接受一个数组

最后,我们可以将数组合并在一起:

$BigArray  = $NewArray + $IntArray

基本内容已经覆盖。让我们来看一下我们可以使用的数组属性和方法。

数组的属性和方法

使用Get-Member来查看数组的属性和方法是很困难的。PowerShell 不会将数组传递到管道中;它按顺序传递数组中的每个元素。通过输入以下内容来尝试:

$NewArray | Get-Member

我们得到的只是[Int32]类型的属性和方法。

要查看数组类型的属性和方法,你需要访问帮助主题about_Arrays,或者在线查看:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_arrays

让我们简单看一下其中一些更重要的内容。

Count、Length 和 Rank

这些是数组的常见属性。CountLength的别名。它们都告诉我们数组中有多少个元素。$NewArray.Count$NewArray.Length是相同的。这又回到了老管理员的肌肉记忆问题。你会在 PowerShell 文献中看到这两种说法。

Rank 对于处理数据集时很有趣。我们一直在使用的数组是 $ScruffyArray,它包含 Get-Process 的输出。在 PowerShell 中,你处理的大多数数组可能是单维数组。然而,在数据科学中,我们经常需要使用多维数组;它们比较复杂,我还没看到一个好的 PowerShell 模块来操作它们。如果你真的感兴趣,可以看看 about_Arrays 帮助主题。只要知道,如果你需要操作多维数组,这完全是可能的。

Clear、ForEach 和 Where

这些是数组的常见方法。当数组的元素支持Clear方法时,你可以在数组上使用它来清除所有元素。如果元素不支持该方法,你会得到一个错误。

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_16.jpg

图 4.16 – 有些方法比其他方法更有用

ForEach方法允许我们遍历数组中的每个元素,并对其执行操作。输入以下代码可以得到数组中数字的平方:

@(0..3).ForEach({$_ * $_})

个人而言,我认为这种代码高尔夫(代码简化)故意复杂化,违背了 PowerShell 的最佳实践。如果只处理几行代码,这没问题,但我不会把它放在脚本中。我们将在第五章中讨论如何以更易读的方式做这件事,PowerShell 控制流 – 条件语句 和循环

Where 方法类似,虽然在单行表达式中更有用。要获取 $NewArray 中大于 5 的元素,我们可以输入以下代码:

$NewArray.where({$_ -gt 5})

它在功能上与这个相同:

$NewArray | Where-Object {$_ -gt 5}

可能更不易阅读。我们在第三章中已经讨论过 Where-ObjectPowerShell 管道 – 如何串联 cmdlet。记住,PowerShell 会依次将数组中的每个成员输出到管道中。那么,为什么这些方法还存在呢?如果我们正在处理非常大的数组,那么直接在数组上使用方法,而不是通过管道,会更快。以下 cmdlet 在我的笔记本电脑上大约需要 300 毫秒来完成:

Measure-Command {@(0..100000).ForEach({$_ * $_})}

以下 cmdlet 大约需要 400 毫秒:

Measure-Command {@(0..100000) | ForEach-Object{$_ * $_}}

所以,这取决于我们想要节省时间的地方。如果最快的代码是我们的目标,那么就对数组使用该方法。如果我们希望代码在回顾时能快速理解,那么使用管道。

数组性能

然而,值得节省时间的一个地方是我们对数组的操作。虽然看起来我们可以通过添加元素来改变数组,但实际情况并非如此。数组的大小是固定的。每次添加一个元素时,我们都会创建一个新数组。假设我们看到类似下面的代码:

$SlowArray = @()
1..10000 | Foreach-Object { $Slowarray += $_ }

我们实际上是在创建和丢弃 10,000 个数组。许多文档会告诉我们,替代方案是使用 .NET 类型而不是数组——例如 [System.Collections.ArrayList],像这样:

$ArrayList = [System.Collections.ArrayList]@()
1..10000 | ForEach-Object { $Null = $ArrayList.Add($_) }

这里我们有相同的编程风格,我们先创建数组变量,然后向其添加元素,但我们必须调用非 PowerShell 类型,并使用 Add() 方法。它有效,但相当复杂,尤其是当我们刚刚开始学习时。

另一种选择是通过巧妙的方式创建数组,并像这样构造它:

$AutoArray = (1..10000 | ForEach-Object {$_})

这会生成与其他两个方法相同的数组,速度与 ArrayList 对象类型相同,但使用全 PowerShell 代码的简单性。我在 Measure-Command 中运行了这三个 cmdlet,你可以看到它们的区别:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_17.jpg

图 4.17 – 三种创建相同数组的方法

我第一次看到这个观察是在 Tobias Weltner 的精彩博客中,PowerShell.one。我强烈推荐阅读他的文章。

复制数组

现在我们对数组有了一个相当清晰的了解,可以看看值类型和引用类型之间的一个关键区别:复制变量的方式。试试看:

$a = 42
$b = $a
$a = 'socks'

在不再输入任何内容的情况下——$b 中的值是什么?如果你说是 42,那你说对了。当然是的。$a 的值保存在栈中,所以当我们创建 $b 时,我们将 42 复制到栈顶,并称之为 $b(如果你收到错误信息 'socks' isn't of type [Int32],请关闭 PowerShell 会话并重新启动一个新的会话)。

复制数组并不像那样工作。试试以下操作:

$Array1 = 1,2,3
$Array2 = $Array1
$Array1[2] = 5
$Array2

啊!发生了什么?Array2 包含了更新后的值!这是因为当我们将 Array1 复制到 Array2 时,我们只是复制了数据在堆中的引用,即进程存储区域。当我们修改元素时,并没有创建新数组;我们只是修改了旧位置的数据。引用保持不变,因此 Array1 的所有副本也会引用新数据。

我们如何解决这个问题呢?很简单。至少,算是简单的。试试看,按照以下步骤操作:

$Array1 = 1,2,3
$Array2 = $Array1 | ForEach-Object {$_}

然后,瞧,我们现在可以对 Array1 进行修改,而不影响 Array2

$Array1[2] = 5
$Array2

我们让 PowerShell 将 Array1 中的每个元素放入管道中,并将其传输到 Array2,从而创建了一个独立的数据数组。这有点慢且笨拙,但它有效,而且简单。

目前关于数组的内容就讲到这里。接下来,我们会在本书的后续内容中继续深入探讨它们。现在,让我们来看第二种引用类型:字符串。

字符串

字符串是一个按顺序排列的[char]类型对象集合——它就是文本。正如我们在本节关于引用类型的开始所看到的,字符串是一种引用类型,并且是不可变的——只读的。如果你想改变字符串,你需要创建一个新的字符串并销毁旧的那个。

字符串有两个属性,CharsLengthChars 返回给定索引处的[char]对象:

$String = "PowerShell"
$String.Chars(0)

Chars 是一个带参数的属性——我们必须为其提供一个值,这个值是一个 [int32];否则会抛出错误。不过,我们可以传递一个包含单个 [int32] 的变量。Length 属性返回字符串对象的字符长度:

$String.Length

请查看下面的截图,了解这一切是如何显示的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_18.jpg

图 4.18 – 字符串的属性

字符串有很多方法。大多数值类型的方法涉及改变对象类型,而字符串有许多方法可以改变格式。看看这个:

$String | Get-Member -MemberType Method | Measure-Object

我们将看到字符串上有 52 个可用方法。我们不会在这里覆盖所有方法,但我们可以尝试一些常见的方法。记住,每次使用这些方法时,我们都在操作字符串的输出,而不是字符串的内容。

要将字符串转换为全大写:

$String.ToUpper()

要将字符串转换为全小写:

$String.ToLower()

要将字符串输出为单个字符的数组:

$String.ToCharArray()

要替换字符串中的字符:

$String.Replace('o','X')

完成所有操作后,键入并查看变量的实际内容没有改变:

$String

Replace()Join()Split() 一起,也是 PowerShell 字符串操作符:

$List = 'one,two,three,four'
$List.Split(',')

这也可以写成以下形式:

$List -Split ','

这在下面的截图中显示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_19.jpg

图 4.19 – 两种分割方式

我们经常发现我们有一个日期,但它是以文本格式存在的,最终变成了一个字符串。ToDateTime 方法允许我们将该字符串捕获为 [DateTime] 类型,但我们需要在方法中提供我们想要的文化信息。我们可以像这样操作:

$Culture = Get-Culture
[String]$DateString = '1/5/2024'
$ImportantDate = $DateString.ToDateTime($Culture)

我们可以在下面的截图中看到,$ImportantDate 是一个 [DateTime] 对象:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_20.jpg

图 4.20 – 将字符串转换为 DateTime 对象

然而,如果有机会,使用 [DateTime] 值类型加速器并将变量转换为正确类型的对象会更简单:

[DateTime]$Anniversary = '1/5/2024'

单引号和双引号

在 PowerShell 中,单引号和双引号的行为不同。它们都可以用来定义字符串,但它们的用途不同。试试这个:

$MyName = 'Nick'
Write-Output 'My Name is $MyName'
Write-Output "My Name is $MyName"

我们可以在下面的截图中看到,输出是非常不同的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_21.jpg

图 4.21 – 单引号和双引号

双引号告诉 PowerShell 在处理之前扩展它找到的变量。我们使用双引号时,Shell 会通过将变量显示为绿色来提醒我们它将要执行的操作。

当打印变量的内容并紧跟另一个字符时,将变量名括在大括号中:

"${MyName}: Engineer"

要打印变量本身,而不是变量的内容,使用反引号字符 `

Write-Output "The value of `$MyName is $MyName"

在本书中,我们将会经常处理字符串,但现在我们应该看一下另一个重要的引用类型——哈希表。

哈希表

哈希表是 PowerShell 实现的一种数据结构,叫做字典。它们由键值对组成。我们使用哈希表来查找给定键的值,或检查哪个键包含给定值;它们基本上是查找表。让我们创建一个哈希表并进行操作:

$Hash = @{}

我们使用@{}来创建一个哈希表。我们不应该将它与@()混淆,后者会创建一个数组。现在我们已经有了哈希表,我们应该往里面添加一些内容。我们可以在创建哈希表时这么做,像这样:

$MyBike = @{HandleBar = "ApeHanger"; Color = "Red"; Wheel = 15}

注意,我们使用分号来分隔键值对,而不是像在数组中那样使用逗号:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_22.jpg

图 4.22 – 创建和填充哈希表

我们可以使用add方法向哈希表中添加键值对,如下所示:

$MyBike.Add('Bell', $True)

add方法需要两个参数,用逗号分隔;第一个是键,必须用引号括起来,第二个是值。如果值是字符串,它也必须用引号括起来:

$MyBike.Add('Condition','Poor')

注意,不像数组,PowerShell 将哈希表视为单个对象:

$MyBike | Measure-Object

将其与以下内容进行比较:

$NewList = @(1,2,3,4)
$NewList | Measure-Object

然而,我们可以使用Count属性来返回键值对的数量:

$MyBike.Count

现在我们有了哈希表,我们可以用它做什么呢?显而易见的用途是查找值:

$MyBike.condition

它返回值poor。我们也可以像使用数组索引一样使用键:

$MyBike['Wheel']

我们也可以使用这种方法来添加键值对:

$MyBike['Gears'] = 'Fixed'

使用方括号的一个大优点是,它允许我们传递一个键的数组并返回对应的值:

$MyBike['HandleBar', 'Condition', 'Gears']

或者,我们可以使用一个变量来保存这个数组:

$BikeDetails = 'HandleBar', 'Condition', 'Gears'
$MyBike[$BikeDetails]

我们可以使用GetEnumerator()方法逐个列出哈希表中的所有键值对:

$MyBike.GetEnumerator()

等等。我们难道不能直接输入$MyBike吗?不行。如果我们尝试这样做,整个哈希表会作为一个单独的对象返回。它看起来像是键值对,但不会像一组独立的键值对一样工作。试试下面的操作:

$MyBike | ForEach-Object {[Array]$BikeProperties += $_}
$BikeProperties.Count
$MyBike.GetEnumerator() | ForEach-Object {[Array]$NewBikeProperties += $_}
$NewBikeProperties.Count

我们可以看到,在第一行中,只有一个对象通过了管道。

我们也可以使用remove方法删除键值对:

$MyBike.Remove('Gears')

我们可以测试某个键是否存在:

$MyBike.ContainsKey('HandleBar')

然后我们可以测试值是否存在:

$MyBike.ContainsValue('poor')

有序哈希表

默认情况下,哈希表是无序的,这其实并不重要,因为我们只是寻找它们包含的键值对中的值。然而,有时候我们希望哈希表以特定的方式排列。我们可以使用[ordered]关键字来实现。注意,这个关键字不放在变量前面,而是放在右侧:

$OrderedHash = [ordered]@{a=10;b=20;c=30}

你可以在下图中看到它们的区别:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_23.jpg

图 4.23 – 有序哈希表

注意,现在它变成了一个不同的类型——OrderedDictionary。我们仍然可以通过传递键来获取值,但由于它是有序的,我们现在也可以直接传递键值对的索引:

$OrderedHash.c
$OrderedHash[1]

最后,您可能注意到我们又回到了自行车的话题。那是因为我们可以对哈希表做一些很酷的事情——我们可以将它们转换为对象:

$MyImaginaryBike = [PSCustomObject]$MyBike

从以下屏幕截图中可以看到,我们现在有一个名为 $MyImaginaryBikePSCustomObject,它具有一组与原始哈希表键值对匹配的属性:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_24.jpg

图 4.24 – 将哈希表转换为对象

酷吧?我们将在本书的其余部分中经常使用哈希表,因为它们是非常有用的类型。

还有其他引用类型,涉及其他类型的数据结构,如队列和栈,但我们通常不常使用它们,至少在日常的 PowerShell 中不会使用。在本章结束之前,我们将介绍哈希表的最终用法:splatting。

Splatting – 哈希表的一个酷用法

一些 PowerShell cmdlet 接受大量参数,逐个在同一行中输入这些参数可能会让人感到困惑。这时,哈希表派上了用场。试试以下方法:

$Colors = @{
ForegroundColor = 'red'
BackgroundColor = 'white'
}
Write-Host 'all the pretty colors' @Colors

注意我们没有使用 $Colors;我们使用的是 @Colors。同时,注意它的顺序没有关系:

Write-Host @Colors 'OK, just red and white, then'

这样也能工作,因为我们在哈希表中明确指定了参数:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/pwsh7-ws/img/B17600_04_25.jpg

图 4.25 – 基本的 splatting 示例

我们可以使用数组,但这只对位置参数有效;数组中的第一个值将是第一个位置参数,第二个值是第二个,依此类推。因为大多数 cmdlet 不会有超过一两个位置参数,所以这并不像哈希表那样有用。

我们在接下来的章节中将会经常使用 splatting,但对于这一章来说,内容就到这里。现在是总结我们所学的内容的时候了。

总结

这一章内容比较长,但我们学到了很多东西。我们首先了解了变量及其在 PowerShell 中的使用方式。接着,我们从计算机科学的角度重新审视了对象,学习了它们的属性和方法。这为我们提供了探索对象类型的基础,我们查看了一些值类型,它们相当于计算机科学中的基本类型。接下来,我们看到值类型如何被分组到数据结构或引用类型中。最后,为了增加一些趣味性,我们了解了 splatting 如何帮助我们节省时间和精力。

在下一章中,我们将研究 PowerShell 中的流程控制,包括 ifelse 等条件语句,以及使用 ForEachWhile 的循环。我们还会安装更多的软件,因为那才是有趣的部分,对吧?

练习

  1. My Variable 这个变量有什么问题?

  2. 不尝试代码的话,这个 cmdlet 会返回什么 TypeName

    New-Variable -Name MyVariable -Value "somestuff"
    Get-Variable MyVariable | Get-Member
    
  3. 我们如何改变 PowerShell 显示错误的视图?

  4. 我们可以使用什么自动变量来清空数组或哈希表的内容?

  5. 我们如何比较两个整数?

  6. 这里的 MyVariable 对象类型是什么?

    $MyVariable = ,1
    
  7. 我们如何将字符串中的每个字符放入数组中?

  8. 这里会出什么问题,为什么?

    Write-Output 'My Name is $MyName'
    
  9. 我们如何创建一个类型为 OrderedDictionary 的对象?

进一步阅读

本章有很多内容要阅读,因为我们只是略微触及了这个主题的表面。我们确实应该阅读与本章内容相关的帮助主题,以及一些官方的 PowerShell 语言文档:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值