惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
阮一峰的网络日志
阮一峰的网络日志
G
Google Developers Blog
J
Java Code Geeks
Blog — PlanetScale
Blog — PlanetScale
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
L
LangChain Blog
Hugging Face - Blog
Hugging Face - Blog
T
The Blog of Author Tim Ferriss
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Stack Overflow Blog
Stack Overflow Blog
P
Proofpoint News Feed
腾讯CDC
博客园_首页
博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
WordPress大学
WordPress大学
D
DataBreaches.Net
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
博客园 - 叶小钗

Cyberia

复活吧微软边缘 - Cyberia 从零开始的EAC抓轨教程 - Cyberia Apophenia, 新时代补全工具 - Cyberia 浅谈博客设计 - Cyberia 爱软TV之GitHub - Cyberia 排印演进 - Cyberia EAC相关工具集合 - Cyberia 《C(金钱掌控)》观后感 - Cyberia C#在Windows UI开发中的衰落 - Cyberia 存储退潮 字体padding计算器 开源已死 如何写出优雅完整的音频元数据 自动化EAC音频抓取 起夜级家庭网络正在变得越来越重要 优化Powershell的启动时间 无JavaScript其实没有很重要 你不需要一个电纸书阅读器 文件分享的难题 为什么我不敢看都市 某科学的 SSH 算号指南 时代的辉夜姬 为什么 Pixi 是神 关于神椿的过去,当下以及未来,我的一点看法 Tahoe 的图标令人难评 The EVE of PUSHING LIMITS 在 Word 中通过制表符实现居中对齐等操作 编码之殇:前 Unicode 时代的 WAV 处理 WR1800K AX NAND 路由器刷入 Immortalwrt 一个 PWM 的世界
sHELL 即地狱
Nullpinter · 2025-07-14 · via Cyberia

请注意

所有 PowerShell 指的是 pwsh7。

Shell#

Shell 是个老东西了。我想,没有人会喜欢写 Bash 脚本的:

# 变量赋值:等号前后不能有空格,违反直觉

name="Harusaruhi"

# name = "Harusaruhi" # 这样写会报错!

# 方括号内必须有空格,否则报错

if [ $name = "Harusaruhi" ]; then

echo "匹配"

fi

# if [$name = "Harusaruhi"]; then # 错误:缺少空格

# if [ $name="Harusaruhi" ]; then # 错误:等号前后不能有空格

# 字符串比较和数值比较用不同符号

num1=10

num2=20

if [ $num1 -lt $num2 ]; then # 数值比较用 -lt

echo "数值比较"

fi

if [ "$num1" \< "$num2" ]; then # 字符串比较用 \< (需要转义)

echo "字符串比较"

fi

# 数学运算的多种诡异语法

result1=$((num1 + num2)) # 方式1:双括号

result2=`expr $num1 + $num2` # 方式2:expr命令

79 collapsed lines

result3=$(expr $num1 + $num2) # 方式3:$()包裹expr

echo "结果: $result1, $result2, $result3"

# 字符串处理的反人类语法

text="Hello World"

echo ${text#Hello } # 删除前缀

echo ${text%World} # 删除后缀

echo ${text/World/Universe} # 替换

# 数组的诡异语法

arr=("apple" "banana" "orange")

echo ${arr[@]} # 访问所有元素 - @符号的含义不明

echo ${#arr[@]} # 数组长度 - 完全无法直观理解

# 参数?

echo "脚本名: $0"

echo "参数个数: $#"

echo "所有参数: $@"

echo "上个命令退出状态: $?"

# 循环语法的不一致性

# for循环有多种奇怪的形式

for i in 1 2 3; do

echo "数字: $i"

done

# 另一种for循环语法

for ((i=1; i<=3; i++)); do

echo "计数: $i"

done

# while循环

counter=1

while [ $counter -le 3 ]; do

echo "While: $counter"

counter=$((counter + 1))

done

# 函数定义和调用

function_name() {

# 函数参数通过$1, $2...访问,没有参数名

echo "函数参数1: $1"

echo "函数参数2: $2"

# 返回值只能是数字,且通过$?获取

return 114514

}

# 调用函数

function_name "hello" "world"

echo "函数返回值: $?"

# 奇怪语法

if [ -f "file.txt" ]; then

echo "文件存在"

fi

if [ -d "directory" ]; then

echo "目录存在"

fi

# 引号

echo 'Single quotes: $name will not be expanded'

echo "Double quotes: $name will be expanded"

echo `Command substitution with backticks`

echo $(Command substitution with dollar parentheses)

# 逻辑运算符

if [ $num1 -gt 5 ] && [ $num2 -lt 30 ]; then

echo "逻辑与"

fi

if [ $num1 -gt 5 -a $num2 -lt 30 ]; then

echo "另一种逻辑与"

fi

echo "当前进程ID: $$"

echo "后台进程ID: $!"

echo "当前选项: $-"

echo "随机数: $RANDOM"

其抽象的操作符和滥用的特殊符号,可能在当时非常重要,因为存储空间和屏幕都非常有限,但是在现代只会导致代码可读性极差并且维护困难。函数设计也非常的 Shell,通过数字 index 来访问参数更是让整个逻辑的理解难上加难,没有一点信息量。

然后我们有了 PowerShellNushell

走进新时代:面向对象与数据驱动#

微软,Microsoft,我们罪该万死的 M$,终于没有遵循可笑的 Unix 哲学,终于引入了对象,淘汰了文本流。我们现在有了:

function Process-UserData {

param(

[string]$Username, # 类型!

[string]$Email, # 变量名!

[string]$OutputFile

)

# 面向对象的操作

$user = Get-User -Name $Username # 可读的 Builtin Command!

$user.Email = $Email

$user | Export-Csv -Path $OutputFile

}

但是,遗老们就开始发力了。我不明白为什么有些人会故意的把 *sh 写的晦涩难懂,故意使用缩写而不是完整的 argument 和 option:

find . -name "*.log" -mtime +7 -exec rm {} \;

grep -r "ERROR" /var/log|cut -d: -f1|sort|uniq -c|sort -nr

仅仅展开缩写就能够极大的提高可读性和可维护性:

find . --name "*.log" --mtime +7 --exec rm {} \;

grep --recursive "ERROR" /var/log | cut --delimiter=: --fields=1 | sort | uniq --count | sort --numeric-reverse

真正的 Unix 用户只需要 grep、sed、awk 就能解决99%的问题,为什么要用一个臃肿的对象系统?
Unix 的管道哲学是用简单工具组合复杂功能,PowerShell 却想用一个复杂工具解决所有问题
这种设计哲学与 Unix 的极简主义背道而驰

这些都是常见的言论,我只能说 COM 设计的比 Pipeline + FileSink 好不是没有原因的。

不止 PowerShell,Nushell 也添加了基本的类型系统(尽管并不丰富):Integers、Strings、Tables、Any……一个完备可用的类型系统能够极大的提高开发和除错的效率,使得整个流程更加可控、现代化。我们必须认识到,大部分的数据都不是文本和数据流,而是有结构的:使用awk处理一个有向无环图试试?

类型系统和对象的引入使得编写的脚本更加健壮,更加结构化;IDE 也能够通过类型信息自动生成文档和提供提示。

结构化数据处理#

简单工具组合复杂功能相比,现代 Shell 可太好用了:

curl -s "https://api.github.com/repos/octocat/Hello-World" | \

grep '"name"' | \

head -1

$repoInfo = Invoke-RestMethod "https://api.github.com/repos/octocat/Hello-World"

$repoInfo.name

# 其他

11 collapsed lines

# 直接处理CSV

$csvData = Import-Csv "employees.csv"

$csvData | Where-Object {$_.Department -eq "IT"} | Select-Object Name, Salary

# 直接处理XML

$xmlData = [xml](Get-Content "config.xml")

$xmlData.configuration.appSettings.add | Where-Object {$_.key -eq "DatabaseUrl"}

# 直接处理Excel文件

$excelData = Import-Excel "report.xlsx"

$excelData | Group-Object Department | Select-Object Name, Count

http get "https://api.github.com/repos/nushell/nushell" | get name

# 其他

11 collapsed lines

# 处理CSV数据

open employees.csv | where department == "IT" | select name salary

# 处理YAML配置

open config.yaml | get database.connections | where active == true

# 处理TOML文件

open Cargo.toml | get dependencies | columns

# 处理XML数据

open data.xml | get root.items.item | where status == "active"

臃肿的对象系统能够带来类型安全、性能优化以及更好的错误处理。直接加载结构化数据的能力代表了 Shell 脚本的一个重要演进方向。它不仅提高了开发效率,还大大降低了出错概率,使得复杂的数据处理任务变得简单直观。这是软件工程向更高层次抽象发展的必然结果。正如我们不再用汇编语言编写应用程序一样,在数据处理领域,我们也应该拥抱更高效、更安全的工具和方法。

Builtins:告别依赖地狱#

传统的 Unix Shell 信奉”小工具组合”,结果就是每个脚本都要依赖一堆外部工具。写个处理 JSON 的脚本?你需要 curljqheadtailgrepsedawk……天知道用户的系统上有没有装这些东西,更别说版本兼容性了。

curl -s "$API_URL" | jq '.items[]' | head -10

find /tmp -name "*.log" -exec gzip {} \;

date -d "2024-01-01" +%s # GNU date

date -j -f "%Y-%m-%d" "2024-01-01" +%s # BSD date,完全不同的语法!

这种设计带来的问题显而易见:环境依赖版本差异安全风险性能损失。每个外部命令都是一个新进程,每个新进程都是潜在的攻击面。

现代 Shell 的内置命令解决了这些问题:

$data = Invoke-RestMethod $API_URL

$data.items | Select-Object -First 10

Get-ChildItem /tmp -Filter "*.log" | Compress-Archive

Get-Date "2024-01-01" | Get-Date -UFormat %s # 跨平台一致

http get $API_URL | get items | first 10

ls /tmp | where name =~ "\.log$" | each { gzip $in.name }

"2024-01-01" | into datetime | format date "%s"

  • 安全性提升:内置命令无法被外部劫持,which curl 可能返回恶意替代品,但 Get-Command Invoke-RestMethod 总是返回可信的内置命令。
  • 性能优化:传统方式处理1000个文件需要创建3000个进程,现代内置命令在同一进程内完成所有操作。
  • 一致性保证:不再需要处理 GNU coreutils 和 BSD 工具之间的差异,内置命令在所有平台上行为一致。

现代 Shell 的内置命令不是臃肿,而是必要的现代化。它们提供了类型安全、错误处理、性能优化和一致性保证,这些都是传统外部工具无法提供的。正如我们不再用汇编语言写应用程序一样,在 Shell 脚本领域,我们也应该拥抱更高效、更安全的内置工具集。

Deno Shell#

deno_task_shell 是一个跨平台的 Shell 解析器,最初为 deno task 设计,但现在被许多应用嵌入使用。dax 库基于 deno_task_shell 的解析器,提供了在 Linux、Mac 和 Windows 上一致的 Shell 语法支持。

这种嵌入式 Shell 的设计理念确实很符合现代应用的需求:不是要做一个完整的 Shell,而是提供一个可预测、跨平台的 Shell 子集,让应用开发者不用再为不同操作系统的 Shell 差异而烦恼。

可移植性和可预测性比完整功能更重要。我们不需要一个功能完备的 Shell,我们需要的是一个在任何地方都能正常工作的 Shell。

新的问题#

说了这么多好处,难道就没有缺点吗?有的,主要是大。Nushell的典型 musl 分发大小在 60 MiB 左右,这使得其不能够在路由器、嵌入式上正常使用。PowerShell 更是直接分发了 .NET RT,以提供完整的CLR支持(是的你可以在 pwsh 中调用 C# 和玩反射)。相比之下,传统的 /bin/sh 可能只有几百 KB,bash 也不过几 MB。

这在软件设计中是一个永恒的矛盾。现代 Shell 为了提供类型安全、丰富的内置命令和跨平台支持,基本上不可能拥有小的体积,但是这些嵌入式应用场景,反而在一定程度上正是 Shell 的用武之地。

依旧是地狱#

说了这么多,现代的 sHELL 在哪里呢?在源里。没有一个是某个操作系统的默认 shell,这使得其脚本分发变得非常困难。现代的 Shell 很难到最需要它们的地方去,我们依然在处理可能在千禧年就处理过的问题。

相关资源: