看到大家都很嫌棄Windows自帶的命令行工具(命令提示符和PowerShell),那麼它們到底和Linux的命令行差在哪裡?


差在哪?差在軟體生態啊。

拿操作系統為例的話,你用的不是操作系統,你用的是操作系統之上的各種app。一個操作系統之上有各種優秀的app,那麼哪怕這個操作系統設計得很爛,優化得很差,也是會有很多人用的,至於它優化差的問題可以靠瘋狂堆硬體解決,我說的自然就是____系統。

說回到命令行也是一樣,用戶使用的,其實不是命令行的shell本身,也不是終端模擬器程序,而是,大量的基於命令行的app,以及這些app之間的搭配。

大量的基於命令行的那些Linux app形成了肌肉記憶,旁人只道我在命令行運指如飛,可不知道要達到這種狀態是經過了多長時間的歷史積澱。而Linux命令行的應用生態,豐富得讓你可以實現操作系統中需要做到的幾乎所有事情。

Windows下,沒有這成百上千的基於命令行的app,沒有這些讓人已經習慣成自然的app,沒法使用就像吃飯喝水一般自然的這些命令行app,自然,Windows命令行就沒辦法好用:因為基於Windows命令行的軟體生態太貧瘠了。

這,其實與PowerShell還是bash並沒有什麼直接關係。

--

補充:有的人認為ps在設計理念上全方位超越bash。這句話粗看起來似乎有道理,然而,一個shell它首先是一個命令行,其次才是個批處理編程語言。

使用更先進編程理念的shell從來就沒能在普及層面戰勝目前的shell,因為他們忽略了,手工輸入的方便快捷才是shell的第一需求。


Windows 在很長一段時間內,自帶的終端模擬器都一言難盡:難以配置,默認情況下很醜陋,複製粘貼的邏輯很彆扭等等,直到 Windows Terminal 出來才緩解了這個狀況,但 Windows Terminal 到目前還不是默認的終端模擬器,單純這個原因就讓 Windows 的終端體驗下降很多。

另一個問題是補全問題。雖然現在 PowerShell 支持的補全功能並不弱,但糟糕的是,沒有幾個第三方工具適配了 ps 補全功能,對於沒有適配的工具,補全工具基本只能用來補全路徑,而 bash 上補全適配狀態要好的多。而且,Linux 上命令補全通常是漸進式的,每次按下 TAB 都會補全到最長的公共前綴上,並展示可能的命令,而到了 Windows 上 TAB 補全就變成了在輸入處遍歷可能的補全列表,按過頭了還需要再循環一圈,想回退到按 TAB 前的狀態還只能手動退格……也許是我習慣問題,但我覺得 Linux 上默認的的補全邏輯遠遠比 Windows 上更加易用。

至於 Shell 方面,cmd 更是一言難盡,我也不多說了。PowerShell 設計上很不錯,但我並不認為它適合作為 Shell 來使用,而是更適合用來寫腳本,冗長的命令與選項加上 Windows 的補全邏輯作為 shell 用起來簡直是災難,哪怕 wsl 里運行 pwsh 都比 Windows 上直接運行的 PowerShell 要易用的多的多。另外 PowerShell 啟動速度也要慢於 cmd 慢於 bash,這個問題近期倒是改善了不少,但是用起來還是和 bash 揮之即來招之即去的流暢感有所差距。

再就是 Windows 上命令行生態和 Linux 的差距,這是 Windows 重 GUI 輕 CUI 的風格造成的。Windows 上相當一部分 CUI 應用還是移植自 *nix,由此引來的風格差異也是降低 PowerShell 易用性的重要因素之一。

順帶一提,PowerShell 用來寫腳本不錯,不過我更喜歡 Ammonite 一些:

Ammonite?

ammonite.io

作為一門語言來說,Ammonite 的功能遠強於 PowerShell,因為它支持的是 Scala 的一個超集。與 PowerShell 能利用 .Net 生態類似,Ammonite 也能夠利用整個 JVM 生態,它甚至支持從遠程 Maven 倉庫自由導入 package,譬如想使用阿里雲的 OSS SDK,只需要這樣:

import $ivy.`com.aliyun.oss:aliyun-sdk-oss:3.9.1`

然後就能導入 SDK 中的類型了:

import com.aliyun.oss.OSSClient
import com.aliyun.oss.model._

更多的功能請參見官網文檔。


這個問題需要從至少 3 個不同的方面來看——首先你得把 Shell 和 Terminal 這兩個概念分開,然後還要考慮的是圍繞命令行界面(CLI)所構建的生態環境。很多人都把他們混在了一起,得出的結論就大不相同甚至截然相反。

Shell 就是真正解釋並執行命令的那個程序,比如 cmd/PowerShell/bash,它們各自使用自己的語言,比如 cmd 使用的語言叫 Batch(批處理)。Terminal 是用來顯示結果、處理輸入輸出的那個程序,Windows 上自帶的叫 conhost,Linux 不同的桌面環境自帶的 Terminal 不同。由於 bash 是大多數 Linux 發行版默認的 shell,下面就都是拿他跟 cmd/PowerShell 作比較了。


Shell 語言所提供的功能角度講,Batch 無疑是這三個當中最弱雞的,例如沒有函數(只能用 CALL 和標籤模擬)、沒有 switch-case 等等。PowerShell 功能最強,面向對象、並且與 .net 有良好的互操作性,甚至可以嵌入 C# 代碼:

$id = get-random
$code = @"
using System;
namespace HelloWorld
{
public class Program$id
{
public static void Main(){
Console.WriteLine("Hello world!");
}
}
}
"@

Add-Type -TypeDefinition $code -Language CSharp
iex "[HelloWorld.Program$id]::Main()"

當然 Bash 也能模擬一下面向對象甚至函數式編程,但這不是語言提供的一等功能。PowerShell 也有一個相當明顯的缺點,就是跟 bash 相比太啰唆。

Shell 程序所提供的功能角度講,cmd 仍然是最弱雞的,例如歷史記錄無法持久化保存、沒有 profile 支持。PowerShell 目前這兩點都是支持的。

因此如果有人說 cmd(Batch) 比 bash 差,這完全沒有問題。但是要是拿 PowerShell 跟 bash 相比,我認為還是 PS 技高一籌。


下面說一下 Terminal:Windows 自帶的那個 conhost 被 Linux 上一眾 Terminals 吊打我認為毫無問題,隨便就能想得到很多槽點(有一些槽點在 Windows 10 上解決了,但還是很想吐):

  • 字體受限
  • 顏色受限
  • 窗口寬度受限
  • 行選擇
  • 默認是當前語言的編碼
  • 不支持 ANSI terminal code
  • 功能上的缺失,比如標籤頁、搜索…
  • ……

由於 conhost 在兼容性上無法妥協,因此有了新的 Windows Terminal。還有很多其他的第三方 Terminal,比如 ConEmu,不過這些都不是「系統自帶」的。因此如果有人說 conhost 難用的一批,請不要反駁並加入他們。


最後是圍繞命令行界面所構建的生態。從系統管理與維護的角度講,其實 Windows 與 Linux 並沒有太大差距,比如 Windows ServerCore 就是不帶桌面環境的 Windows Server。還有很多程序專門提供了 PowerShell Module 來對軟體進行管理,例如 CosmosDB Emulator。

但在其他方面(第三方程序的豐富程度、命令行包管理器等),Linux 無疑要比 Windows 強很多,甚至 bash 不喜歡的話還可以換別的。我認為主要原因在於 Linux 的桌面環境不是必需品,用戶用什麼桌面環境、甚至有沒有桌面環境都不一定,而 Windows API 就實實在在在那,還有相當好的兼容性,圖形界面又對普通用戶更友好。這樣的 API 不用白不用,用了不白用,白用誰不用 _(:з」∠)_


拿一個具體例子來說,就是很多人提到的軟體包管理工具 scoop。它的命令行介面就可以凸顯出 Windows 的命令行和 Linux 的命令行有多少差距。

例子一、

scoop install 只能用來安裝新的包,如果這個包已經存在(即使殘缺不全,甚至只有一個空目錄),就提示出錯,只能先用 scoop uninstall 將其卸載然後重新安裝。

比如我安裝 curl,在下載過程中 ctrl + c 退出,再安裝,就會提示這個錯誤:

看起來好像沒什麼大問題。但隨便想個場景,自己用 scoop 安裝一個包時,發現下載速度很慢,於是 ctrl + c 退出,掛上代理,重新安裝。

而 Linux 下的各種包管理軟體,都不會犯這樣的低級錯誤,沒安裝上就是沒安裝上,下次繼續安裝就行。

例子二、

用 scoop install 安裝一個包時,主程序會檢測自身代碼是否三個小時以上沒更新了(寫死在代碼里的,不可配置),如果是,那麼先強行更新自身代碼,然後強行更新所有源列表,最後再給你安裝。

這種做法有多荒唐我就不分析了。即使是 archlinux 那種滾動式發行版(長時間不更新源列表的話,安裝大概率會出錯,比如要下載的文件已經不存在了,或者本地的依賴已經無法滿足了),也不會在安裝之前強行把 pacman 和源列表更新到最新。

例子三、

scoop update 的功能是更新主程序和源列表(不說將二者綁定在一起是否合理了),更新過程中,會把 git log 全打出來,每個包的更新記錄,不管你有沒有安裝它。但直到運行完,你也不知道自己已安裝的包是否有更新。要想知道,你得再運行 scoop status,它會顯示你哪些包有更新。如果你想全部升級,那麼還得再運行一個 scoop update *。

大家可以仔細體會下這眼花繚亂的操作。對我來說,不封裝是很難用的。

例子四、

scoop 會把源列表都同步到本地(一個包對應一個 json 文件),按理說搜索應該很快。但實際上很慢,因為它要依賴網路。因此我自己寫了個腳本,用本地的文件來搜索,速度至少提升一數量級(從幾秒到幾百毫秒)。

例子五、

上邊已經說到 scoop 已經把源列表同步到了本地,但用它查詢指定包信息時依然很慢,而且信息不全(實際上只需要把對應 json 文件打出來就行)。所以我又寫了個腳本用來查詢包信息。

這耗時又差了一個數量級。

總結

如果 scoop 是一個剛出生的軟體,有這些問題還好說。但它已經誕生七年多了,也有了很多用戶(其中不少用戶把它當神器宣傳),卻有著如此糟糕的命令行介面。這說明什麼?說明它的多數用戶根本不在乎命令行的體驗,有個工具,能用,就行了。不然這些問題不會得不到解決。

所以 Windows 的命令行體驗,要想提升,還是很困難的。原因很簡單,就是大部分用戶都不在乎。

有人會大談 Powershell 的功能和設計,但 scoop 就是用這樣「功能強大」和「設計優雅」的 Powershell 寫出來的。靠 Powershell 能拯救 Windows 的命令行體驗嗎?

題外話

說到 Powershell,我還有些話要說。有段時間我看它的功能很心動,就想把它作為日常的 shell,適配的 alias 都寫了一些,然後放棄了。下邊是我遇到的問題和我嘗試解決的方法:

一、啟動速度太慢。可以啟動後就不退出了,一個不夠就啟動多個,切換著使用。雖然不符合我的使用習慣,但也不是無法克服。

二、命令補全體驗不好。安裝 PowerTab 插件,界面可以,功能也過得去,雖然還是欠缺打磨。

三、有些內置命令不好用,比如 curl 對應的內置命令,還帶彈窗的,速度也很慢。可以用外部命令代替,比如 curl.exe。

四、輸出內容重定向到文件後,都成了 utf-16 編碼的了,沒法配置,非常不方便。到此為止我已經不大想解決了。(現在單獨下載的 Core 版本已經是 utf-8 的了,但系統內置的還是 utf-16。)

五、我基本沒發現遷移到 Powershell 有什麼好處,只看到了代價(除了上邊提到的,還涉及習慣新語法,很多用法都變麻煩了,那不是用 alias 就能簡化的),於是放棄,回到 WSL 里的 zsh。

要說 Powershell 的體驗怎麼樣?單獨的問題似乎都有解決方法,即使不完美。但綜合起來,就是不斷做減法,減到一個很差的結果,而且 Powershell 的優勢並不能將其彌補。

隨便貼下我封裝 scoop 的腳本,因為我是在 WSL 下用的,寫的 zsh:


你要拿cmd去和bash比,那是欺負人。

但是你說powershell和bash,本質上來說PowerShell不論是設計理念還是各方面都是全方位領先的。差別主要就是生態和偏見了。

當然,powershell的版本和需要手動安裝也是個麻煩事兒。

我這邊只隨便說一點,PowerShell有完整的dotnet支持,很多問題你要依賴某個小工具的,PowerShell可以直接調dotnet類庫來解決。這並不是寫個腳本或者程序,而是PowerShell本質上就是dotnet的命令行前端。

其實,有些人真的是非常完美的詮釋了什麼叫做偏見:

例如大家都熟悉 ls是列目錄,非要來一個 dir。

事實上我估計dir比他的年齡都大,而且,你要真說是列目錄的話,dir是directory的縮寫,ls則是list的縮寫,你說哪個更貼近列目錄?而dir和ls都是pwsh的alias這種常識我都懶得說了。

更搞笑的是:

都不明白bash的精髓嗎?bash的精髓不是語法,而是能將一堆Linux命令輕鬆的包裹在一起。語法再高級,再完備有啥用,這個世界缺高級動態語言嗎?Python夠不夠?缺的是各種小工具。

真是不好意思,這個精髓被pwsh幾乎完全的繼承並且發揚光大了,那就是pwsh的管道,說發揚光大是因為pwsh的管道是強類型的。

pwsh內置的絕大多數指令都可以直接通過管道來解決。比如說我們需要遞歸列出所有a開頭的文件,我們會寫:

ls a* -r

不用懷疑pwsh是完全支持這個腳本的。

好了,我們現在確認了,我們要把這些文件刪掉,怎麼做?

因為pwsh管道傳輸的是對象,而且所有的內置指令幾乎都支持管道,所以:

ls a* -r | rm

完事……你完全不用考慮ls輸出的格式是什麼,有沒有全路徑什麼的,因為,管道傳輸的是對象!

你再看看這個精髓,pwsh這個學生,學的不錯吧……

更別說,pwsh還有非常強大的where和select……

ls -r |? Name -like a* |? Length -lt 10000 |select -First 10| rm

你現在告訴我,這是bash的精髓,還是pwsh的?

我真心覺得這是欺負人……

對了,pwsh還可以呼叫幫凶dotnet:

ls |? LastWriteTime -gt ([System.DateTime]::Now.AddDays( -100 ))

數百萬個基礎類型的小工具夠不夠?

====================================================

其實大部分人說了半天就是這麼一句話:

新東西是不可能學的,這輩子都不可能學的,dotnet又不會,只能用bash這樣子,才能維持的了生活這樣。用bash就像回家一樣,指令個個都是人才,縮寫又很漂亮,我超喜歡bash的……

嗯,祝好……


推薦閱讀:
相关文章