位元詩人 Unix 本身就是 DSL

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

許多開發者認為語言設計、DSL(領域專用語言)、語言工具的建構過程過於抽象。然而,資訊界早已存在一個歷史悠久的 DSL 環境 —— Unix 命令列

在深入學習 lexingparsingIRcodegen 等抽象的語言工程之前,不妨先花些時間探索 Unix 命令列,親身體驗「DSL」如何在日常操作中運作。

語法

在 Unix 世界裡,語法就是指令

  • Shell 內建指令POSIX 指令:可視為這個 DSL 的核心語法,通常不必刻意區分。
  • AWK:功能更強的「進階語法」,適合處理文字與資料流。
  • Make 等工具:帶有小型 DSL 的指令,形成特定情境下的子系統。
  • Perl、Python、Ruby:屬於大型子系統,提供更通用的程式語言能力。

學習程式設計並不是要先讀完整本 language reference 才能開始寫程式。
Unix 亦然 —— 邊學邊用,才能真正進步

REPL

在 Unix 世界裡,命令列本身就是 REPL

  • 這個 REPL 相較於多數程式語言的 REPL 危險許多,因為它會直接改變系統的一部分。
  • Shell 語法可以直接輸入在命令列上,但通常不太實用。
  • 在尚未熟悉 Shell 前,不要隨意修改設定檔,更不適合用設定檔來練習。
  • 若想安全地探索這個 REPL,可以先下載一些外部資料,例如 Yahoo 個股或 ETF 的股價表,避免影響系統本身。

資料型態

在 Unix 這個 DSL 中,資料型態並不是核心重點 —— 幾乎所有東西都以字串形式存在:
指令、檔案名稱、目錄名稱,全都是字串。

由於 Shell 缺乏複合型態與資料結構,它並不適合用來撰寫演算法。
但這並非缺陷,而是設計上的本意:Shell 的角色在於操作系統,而非成為通用程式語言。

運算子

Unix 也有自己的一套「運算子」:

  • Pipe (|):用來串接多個指令,形成資料流的管線。
  • 重導 (redirection):決定資料流的方向,例如輸入、輸出或錯誤訊息。
  • 判斷式 (predicate):如 -z-f-d 等,用來檢查字串是否為空、檔案是否存在、路徑是否為目錄。
  • 整數運算:雖然效能不佳,但在需要簡單計數時仍可派上用場。
  • 浮點數運算:Shell 並未提供,因為這並非它的設計目的。

這些運算子讓 Shell 能夠靈活地編排系統操作,而不是專注於數值計算。

敘述

在 Unix 世界裡,指令本身就是敘述

  • 敘述可以只由一個指令構成。
  • 也可以透過 pipe 串接多個指令,形成一個更長、更複雜的敘述。
  • 每個敘述都會真實地改變系統的一部分,因為它直接操作底層環境。
  • 雖然 Shell 並不是 functional language,但若小心設計,仍能區分哪些敘述具有 side effect,哪些則是純粹操作。

語法糖

在 Unix 中,語法糖就是 alias
它適用於那些短到不值得包進腳本裡的單行敘述,讓常用指令更方便。

以下是一個常見的範例:

alias mv='mv -i'
alias cp='cp -i'
alias rm='rm -i'

alias ls='ls --color=auto -F'
alias grep='grep --color=auto'

alias ll='ls -lh'
alias la='ls -A'

透過 alias,我們能快速建立習慣化的指令組合,既省時又降低操作失誤的風險。

函式

Shell 也支援 函式,用來封裝並重複使用一段較長的敘述。以下是一個範例:

ABS_PATH ()
(
    cd -- "$(dirname -- "$1")" && printf '%s/%s\n' "$(pwd)" "$(basename -- "$1")"
)

這個函式會回傳指定檔案的絕對路徑,讓你在腳本或命令列中更方便地處理路徑問題。

模組

既然指令是 Unix 的語法,那麼 系統路徑 (PATH) 就可以視為「模組路徑」。
透過修改 PATH,我們能「引入」社群提供的指令,或是自己撰寫的 DSL,讓它們成為環境的一部分:

PATH="$PATH:$HOME/bin"
PATH="$PATH:$HOME/.plenv/bin"
PATH="$PATH:$HOME/opt/racket-9.2/bin"
PATH="$PATH:$HOME/src/ccrun"
PATH="$PATH:$HOME/src/ccwarn"
PATH="$PATH:$HOME/src/ocaml-clean-compile"
PATH="$PATH:$HOME/src/clean-artifact"
PATH="$PATH:$HOME/src/hanparse/bin"
PATH="$PATH:$HOME/src/chunk-spec/bin"
PATH="$PATH:$HOME/src/inplace"
PATH="$PATH:$HOME/src/serpctl/bin"
export PATH

eval "$(plenv init -)"

這樣一來,新的指令就能像模組一樣被「載入」到命令列環境中,與內建語法並肩運作。

DSL

在 Unix 中,命令列腳本本身就是一種 DSL
腳本能創造新的指令,融入既有環境,與內建指令一同運作。
限於篇幅,這裡不深入介紹腳本語法的細節。

撰寫腳本是探索 Unix 命令列的核心技能。
掌握腳本後,就不再受限於內建「語法」,而能依照自己的習慣設計流程,讓環境更貼近需求。

舉例來說,ocaml-clean-compile 是一系列用來改善 OCaml 編譯流程的腳本。
原本 OCaml 在編譯時會產生大量暫存檔,必須手動刪除,非常麻煩。
這些腳本則會自動在暫存目錄中編譯程式碼,編譯完成後將執行檔傳回,並順手清理暫存目錄,讓流程更乾淨高效。

框架

Oh My Zsh 就是一個典型的 Shell 框架,但它僅能在 Zsh 環境下使用。

這類框架的好處在於:除了原有的「語法」,還能額外提供許多框架專屬的語法,確實能提升生產力。
然而,從 DSL 的角度來看,並不建議長期依賴「框架」。原因有二:

  • 可攜性不足:依賴框架的腳本一旦脫離該框架就無法運作。
  • 學習受限:過度依賴框架,會錯失將日常摩擦轉化為腳本的機會,難以累積屬於自己的 DSL。

因此,框架適合用來輔助,但真正的價值仍在於透過腳本打造屬於自己的命令列生態。

結語

Unix 命令列的本質是 Environment as a Language
只要花一些時間學習如何撰寫腳本,就能大幅提升生產力,突破內建工具的限制,打造屬於自己的工作流程。

嘗試探索 Unix 命令列,體驗這個 internal DSL 所帶來的效率與彈性。
當你日後設計或使用其他 DSL 時,會更能理解並感受到 DSL 所帶來的生產力提升。

Unix vs DSL 對照表

Unix 元素 DSL 概念 說明
指令 (Shell / POSIX) 語法 基本語法單元,直接操作系統。
Pipe、重導 運算子 控制資料流與指令組合,形成複合語法。
判斷式 (-z, -f) Predicate 條件判斷,決定敘述的執行路徑。
敘述 (單指令 / pipeline) Statement 可執行的語法結構,直接改變系統狀態。
函式 (Shell function) Function 可重用的語法封裝,提升抽象層次。
alias 語法糖 簡化常用語法,讓操作更直觀。
PATH 模組路徑 引入外部工具或自製 DSL,擴充語法空間。
腳本 (Shell script) DSL 定義新語法與流程,累積成專屬的 DSL。
框架 (Oh My Zsh 等) Framework 提供額外語法與工具,但可攜性有限。
關於作者

位元詩人 (ByteBard) 是資訊領域碩士,喜歡用開源技術來解決各式各樣的問題。這類技術跨平台、重用性高、技術生命長。

除了開源技術以外,位元詩人喜歡日本料理和黑咖啡,會一些日文,有時會自助旅行。

近期在學習韓文,並將語言學習的心得轉化為開源專案,回饋社群。

這裡是位元詩人的 GitHub 個人頁