2442 字
12 分钟
sHELL 即地狱
请注意

所有 PowerShell 指的是 pwsh7。

Shell#

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

Terminal window
# 变量赋值:等号前后不能有空格,违反直觉
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 哲学,终于引入了对象,淘汰了文本流。我们现在有了:

Terminal window
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:

Cool
find . -name "*.log" -mtime +7 -exec rm {} \;
grep -r "ERROR" /var/log|cut -d: -f1|sort|uniq -c|sort -nr

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

Bad
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 可太好用了:

Bash
curl -s "https://api.github.com/repos/octocat/Hello-World" | \
grep '"name"' | \
head -1
PowerShell
$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
Nushell
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 # 跨平台一致
看看 Shell 原神
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 很难到最需要它们的地方去,我们依然在处理可能在千禧年就处理过的问题。

相关资源:

sHELL 即地狱
https://nptr.cc/posts/2025-07/s-h-e-l-l/
作者
Nullpinter
发布于
2025-07-14
许可协议
All Right Reserved.