跳到正文
原文
Hacker News· corbindavenport·· 6 小时前精选AI 评分56

作者实践 PowerShell 7 后称其是好用的跨平台自动化工具

I can't believe PowerShell is good

AI 导读

作者在多个项目中使用 PowerShell 后认为,它虽源自 Microsoft,却比 Bash 更可预测、可复用。PowerShell 7 基于 .NET Core,可跨 Windows、Mac 和 Linux 运行,并内置参数与帮助、JSON 转换、网页请求及压缩包处理等能力。

推荐理由

文章结合参数处理、JSON 转换和项目实例,对比 PowerShell 与 Bash 的跨平台自动化体验。

正文 · AI 翻译

Microsoft 制作了不少糟糕的软件,Windows 的许多方面都在此列,从主电子邮件应用只是一个网页外壳,到无处不在的 Copilot 按钮。因此,每当我发现某款 Microsoft 产品能在某个方面改善我的生活时,我总会感到意外,尤其是当它源自 Windows 的时候。

我从未特别关注 PowerShell——这款命令行 shell 和脚本工具于 2006 年首次推出,并随着 Windows 7 的发布成为捆绑应用。在我将 Windows 作为主要桌面操作系统使用的那段时间里,我大多使用 Command Prompt 和批处理脚本,或者用WSL 中的 Bash,更复杂的任务则用 Python。现代版本的 PowerShell 支持跨平台,也可以在 Mac 和 Linux 上安装。

今年早些时候,我因为一个与 Windows 相关的项目需要学习 PowerShell 脚本,后来又在 Mac 上试用了几次。它很快就成了我最喜欢的自动化和脚本工具之一——那些原本我会尝试用 Node.js、Shortcuts、macOS Automator,或者更不愿用 Bash 脚本来完成的任务,现在都交给了它。

现在我终于明白了。即使不把 PowerShell 当作 shell 使用,即使使用的是 Mac 或 Linux,它依然非常出色。

撞得头破血流

在 Linux、macOS 和其他基于 Unix 的环境中,运行一系列命令最常见的方式就是使用Bash 脚本。它们也被称为 shell 脚本,尽管它们并非从技术上讲是同一回事,因为 Z Shell(zsh)等其他 shell 也会使用这类脚本。

下面是一个 Bash 脚本示例,它会要求你输入媒体 URL,然后使用 yt-dlp 应用程序将其保存为桌面文件夹中的 MP3 音频文件:

#!/bin/bash

echo -e "Enter a URL: "
read url
cd ~/Desktop
yt-dlp -x --audio-format mp3 "$url"
echo "Done!"

如果只是运行一系列命令,Bash 脚本完全够用。你可以使用变量和循环,多次使用的代码可以放进函数中,它甚至还有限地支持数组等数据类型。它们有时也能在种类繁多的硬件和操作系统上运行。

对于稍微复杂一点的事情,Bash 脚本简直是最糟糕的。几乎没有什么"内置"函数——你应该使用POSIX 命令来完成常见任务,比如文件操作或解析文本。超出此范围的任何事情都依赖于外部工具,例如使用cURL或Wget来获取网页内容,多款不同的应用程序来访问剪贴板,以及jq来读取 JSON 数据。你可能还需要用于解析选项和参数的样板代码。

让人吃惊的是,要让一个 Bash 脚本在他人的计算机上可靠运行也非常困难。有些人可能会在另一种 shell 中尝试运行你的脚本,而该 shell 对脚本的解释方式并不相同——这就是为什么安装 Homebrew 的命令会特意以调用 bash shell 开头,许多其他工具也使用同样的技巧。有人在获取网页内容时可能只安装了 cURL,也可能只装了 Wget,或者两者都有,或者两者都没有!你不能假定每个系统都会配备相同的一组基本 POSIX 命令,甚至连 sudo 命令也不能想当然,正如几个月前我才了解到的那样。

Bash 脚本的局限常常把我推向真正的编程语言,即便任务并不严格需要它们。Python 或许是最合适的替代,但它缺少花括号让我有些遗憾。我写过很多 JavaScript 代码,所以 Node.js 是个选项,但为了做系统操作我得在各处放 await 运算符,需要更多依赖时还得 祈祷自己没有装上恶意软件。

你好,PowerShell

和微软(以及谷歌)的许多其他产品一样,PowerShell 实际上是名字相似但 两个不同的东西。你应该使用的是 PowerShell 7,也叫 PowerShell Core,它是基于 .NET Core 的现代跨平台版本。另一个是 Windows PowerShell(v5.1 及更低版本),它基于 .NET Framework,已经不再积极开发。Windows 11 仍然 只预装 Windows PowerShell,而不是 PowerShell 7。

PowerShell 7 在 Windows、Mac 和 Linux 上都是 免费下载 的。在我的 Mac 电脑上,我 用 Homebrew 进行了安装,然后就可以在终端里用 pwsh 运行它。

A Terminal window running an echo command in ZSH and PowerShell

PowerShell 脚本的基本思路和 Bash 脚本没有太大区别。它是一系列命令的列表,这些命令由 shell 或底层系统提供,并可以在需要时使用额外的函数和关键字(比如 if、foreach 或 try/catch)。

PowerShell 有许多内置命令,称为“cmdlets”(读作“command-lets”),你也可以 安装更多 或自己编写。它们中的大多数名称是 动词-名词 这样的搭配,比如用 Set-Location 代替 POSIX 的 cd,用 Clear-Host 代替 cls,用 Remove-Item 代替 rm。

再来看看那个 MP3 下载器,不过这次用 PowerShell 脚本来写:

Param(
    [Parameter(Mandatory = $true)][uri]$url
)

Set-Location -Path ~/Desktop

yt-dlp -x --audio-format mp3 "$url"
Write-Host "Done!"

这段脚本里最酷的部分是 Param 块,它利用 PowerShell 内置的参数支持 将“url”定义为一个必填的 URI 输入。PowerShell 会在脚本启动时询问该 URL,与 Bash 示例类似,同时也允许通过 -url 参数作为替代的输入方式。

你甚至可以对脚本运行 PowerShell 内置的 Get-Help 命令,它会像 Get-Help 对内置命令那样,列出参数及其数据类型。

Terminal screenshot of different ways of running the script

在 Bash 中实现同样的功能需要大量样板代码,离不开 getparams 和各种 if 语句。PowerShell 则帮你把这些都处理好了。

如果你好奇的话,那种 Unix 风格的 ~/Desktop 路径在所有平台的 PowerShell 中都能使用。即便 Windows 在系统层面使用反斜杠路径,PowerShell 也能按需进行转换——不过原生系统命令可能不行,除非你先用 Convert-Path 这个 cmdlet。和 Bash 一样,~ 字符是用户主目录的别名,你也可以使用 $HOME 变量,PowerShell 在所有平台上都支持 它。

PowerShell 原生支持整数、字符串、数组和哈希表。在解析 Web API 返回的内容时这尤其有用,因为 PowerShell 可以 转换为 以及 从 JSON 转换。它同样是完全面向对象的,你可以 创建自定义对象,或在需要时调用 .NET 函数。

和 Bash 一样,你可以用任何文本编辑器编写 PowerShell 脚本。如果你恰好使用 Visual Studio Code,它为 PowerShell 提供了 语法补全、定义跳转和 linting 支持 等功能。

试运行

我使用 PowerShell 完成的第一个实际项目是 Just the Browser,这是一个旨在从桌面 Web 浏览器中移除 AI 功能、跟踪以及其他不需要的功能的项目。Mac 和 Linux 的安装程序是 Bash 脚本,因此将 Windows 版本也制作成 PowerShell 脚本是合理的。

安装程序的任务是检查计算机上安装了哪些浏览器,然后为每种浏览器显示一个“更新设置”菜单项。选中该项后,脚本会下载并安装一个包含浏览器设置的 Windows 注册表文件。如果设置已经安装,脚本会在菜单中额外显示一个“移除设置”选项,以撤销这一过程。

A browser selection menu in a PowerShell window

其工作原理是创建一个包含 .NET List<T> Class 的列表对象,其中每个列表项都是一个包含菜单标签和更新/移除函数的对象。为了检查是否已经修改过任何注册表项,我使用了 Test-Path 和 Get-ItemProperty cmdlet,如下所示:

if (Test-Path "HKLM:\SOFTWARE\Policies\Google\Chrome") {
  $IfAiSettingExists = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Google\Chrome").AIModeSettings
}

这段代码中有几个很酷的地方。你可能已经注意到,注册表路径看起来更像是一个名为“HKLM”的驱动器上的文件路径,而不是通常的 Windows 注册表层次结构,后者如下所示:

HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome

PowerShell 会为 Windows 注册表创建一个虚拟驱动器,让你能够像操作任意文件系统中的文件一样获取、添加、更改、清空和删除注册表项。同一个 Test-Path cmdlet 也可以用来检查 Downloads 文件夹中是否有某个文件。你不必分别学习如何使用文件和注册表项,因为在 PowerShell 中,它们是同一个东西。

PowerShell 中还有其他虚拟驱动器,例如用于Windows 证书存储的虚拟驱动器,你甚至可以自行创建。如果你厌倦了在命令行环境或脚本中输入长路径,就创建一个指向该路径的虚拟驱动器!你甚至可以将它命名为 FART:\,随你喜欢。

该脚本还必须下载用于配置 Web 浏览器的 .reg 文件。以下是 Google Chrome 配置流程的简化版本:

$GoogleChromeInstallRegistry = "https://raw.githubusercontent.com/corbindavenport/just-the-browser/main/chrome/install.reg"

Invoke-WebRequest $GoogleChromeInstallRegistry -OutFile "$env:LocalAppData\chrome.reg"

$ChromeInstall = Start-Process "reg.exe" -ArgumentList "import `"$env:LocalAppData\chrome.reg`"" -WindowStyle Hidden -Wait -PassThru

这会使用 Invoke-WebRequest cmdlet 从提供的 URL 下载注册表文件,并将其临时存储在用户账户的 AppData 文件夹中。然后,它在隐藏窗口中启动 reg.exe 命令,指示该命令导入刚下载的文件,并等待命令执行完毕。PowerShell 可以直接修改这些键,但我希望将它们放在一个可以手动导入的单独文件中,因为并非所有人都想使用自动安装程序。

在 Mac 或 Linux 系统上,Invoke-WebRequest cmdlet 的工作方式与 Wget 或 cURL 类似,让你可以直接在脚本中下载 Web 内容。这是 PowerShell 在所有平台上内置的另一项功能,我在 Bash 脚本中也经常需要它。

我希望安装程序能够在较旧版本的 Windows PowerShell 中运行,这样它就可以在较旧的 PC 上运行,而无需先升级 PowerShell 版本。这只需要少量调整,例如下面这条命令会在使用 Invoke-WebRequest 之前启用 TLS v1.2,因为它过去默认未启用:

[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12

在另一个项目中,我尝试将我的旧 Tumblr 博客合并到我的主要个人网站中,该网站使用 Eleventy 静态网站生成器。当时没有现成工具可以完成这项工作,所以我编写了一个 “Tumblr 到 Eleventy”PowerShell 脚本。

该脚本有两个主要输入:Tumblr 的归档导出(即一个包含 HTML 和媒体附件的 ZIP 文件),以及 Tumblr API。它使用 Invoke-WebRequest 和 ConvertFrom-Json 来检查 API 并遍历博客上的所有帖子,使用 Expand-Archive 来解压 ZIP 文件,并使用类似 Copy-Item 和 New-Item 的 cmdlet 来创建一个适合 Eleventy 站点的新文件结构。

最重要的部分是保留原始 Tumblr 帖子的所有元数据,包括标题、固定链接、发布日期和标签。脚本通过创建一个包含所有数据的自定义对象来实现这一点,然后使用 ConvertTo-Json cmdlet 将其转换为 Eleventy 友好的 JSON 格式:

$OutputJson = Join-Path -Path $OutputPath -ChildPath "index.json"

$PostMetadata = [PSCustomObject]@{
    title = $Post.title ? $Post.title : $Post.summary
    permalink = ($Post.post_url -replace $Post.blog.url, "/") + "/index.html"
    date = [DateTimeOffset]::FromUnixTimeSeconds($Post.timestamp).UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ")
    tags = $tag ? (@($tag) + $Post.tags) : $Post.tags
    tumblr_id = $Post.id
    tumblr_url = $Post.post_url
    tumblr_short_url = $Post.short_url
    tumblr_blog_name = $Post.blog_name
}

$PostMetadata | ConvertTo-Json -Depth 1 | Set-Content -Path $OutputJson -Encoding UTF8

$Post 变量包含来自 Tumblr API 的响应,所以这只是在将数据映射到 Eleventy 所期望的键。例如,API 有时将我的帖子标题放在 summary 而不是 title 中,所以脚本会先检查 .title,然后使用 .summary 作为后备。Tumblr 以 Unix 时间 的形式提供每个帖子的发布日期和时间,PowerShell 可以将其转换为 Eleventy 所需的 UTC 格式。

The script converting my blog posts in a Terminal window

如果不是 PowerShell,我可能本来会用 Node.js,因为 Node 内置了 网络请求 和 文件处理。不过,像解压归档这样的简单任务就需要外部依赖(要么调用系统的 unzip,要么使用 JS 原生库),而且到处都是 await 语句。我最终并没有迁移我的博客,但希望其他人会觉得这个脚本有用。

工具箱中的另一个工具

我仍在熟悉 PowerShell 脚本编写,但每完成一个项目,我都对它更加印象深刻。它是跨平台的,比 Bash 脚本更可预测和可重用,而且相对容易理解。我无法相信这与 Copilot 出自同一家公司。

当然,如果你以 酷炫的方式 进行脚本编写和自动化,让 Claude 或其他 AI 聊天机器人来掌控局面,那这些都不重要了。机器人可以为你思考和测试。但是,我喜欢学习东西,并尽我所能防止大脑萎缩。在 LM Studio 中的本地 Gemma 4 模型确实在网络搜索失败时帮助我进行了故障排除,但到目前为止,我绝大部分的脚本都是精心手工编写的。

如果你有兴趣学习如何使用 PowerShell,网上有大量的指南和视频教程可供选择。我一直在观看 JackedProgrammer 的《PowerShell 7 入门》,也许某个时候我还会买一本参考书。它可以放在我 1996 年平装版的 O'Reilly 《HTML:权威指南》 旁边。

来源:Hacker News · spacebar.news