具有可攜性的命令稿 shebang

傳統上,命令列程式使用 C (或 C++) 這類編譯語言來撰寫,其他替代的編譯語言像是 D、Go、Rust 等也可以考慮。不過,我們也可以用命令稿來撰寫命令列程式,在類 Unix 系統上,命令稿的選擇很多,除了 Bash、tsch 等 shell scripts 以外,也可以用 Perl、Python、Ruby 等語言製作命令稿。類 Unix 系統使用檔案第一行的 #! (shebang) 來決定命令稿實際運作的語言。這篇短文就是要談談如何 (儘可能地) 寫出具有可攜性的命令稿 shebang。

註:本文的可攜性是指讓此命令稿在類 Unix 系統間流通。

早期有些書會用這樣的方法寫 shebang:

#!/usr/local/bin/perl

print "Hello World\n";

基本上,這種寫法就是把直譯器的路徑寫死在程式中,完全不具可攜性。而且這個寫法假定使用者自己編譯 Perl,但這種情形反而少。

大部分的教材都是使用 env 指令來達成可攜性,如下例:

#!/usr/bin/env perl

print "Hello World\n";

這樣的好處在於 env 會自動偵測系統的 perl 所在的位置,即使使用者使用 plenv 等方案將 Perl 裝在特殊的位置,這個命令稿也可正確執行。

env 並不是完美無缺,否則筆者也不需寫這篇短文。env 的缺點在於直譯器後只能傳入單一參數,而多參數的命令稿在 AWK 或 Perl 並不少見。真正可以傳入多參數又具有可攜性的方案其實是寫 shell wrapper,我們用一個短例來說明:

#!/bin/sh

cat <<'END' | perl - "$@"
for my $arg (@ARGV) {
    print $arg, "\n";
}
END

在這個例子中,系統認定該命令稿是使用 sh 的 shell script,但我們在這個 shell script 中內嵌一個 Perl 腳本,所以實際執行功能的是 perl 而非 sh。由於 sh script 可以傳入參數,所以這個 script 可以如同一般的命令列工具般接受參數,於 script 內部再將參數轉由 perl 來執行即可。

這樣寫會比 env 更具可攜性,但缺點是寫起來比較醜,沒有語法高亮,因 IDE 往往會將這種腳本視為 sh script。

我們甚至可以嵌入超過一種語言的命令稿,如以下例子:

#!/bin/sh

awk_script=$(cat <<'AWK_END'
BEGIN { printf "Hello World\n"; }
AWK_END
)

perl_script=$(cat <<'PERL_END'
printf "I'am Michael\n";
PERL_END
)

awk "$awk_script"
perl -e "$perl_script"

在這個例子中,我們先將兩個腳本分別存在變數中,再輪流呼叫即可。由於變數內存的是程式碼而非檔案名稱,所以呼叫方式要略為修改。

在這兩種方案中,如果沒有傳入多參數的需求的話,使用 env 會比較簡單,而 shell wrapper 僅保留在參數複雜或要和多個命令列工具互動時才使用即可。

上篇使用 Babel、Flow、ESLint 改善網頁或 JavaScript 程式專案
下篇Rust 或 Go (Golang) 何者較適合做為後端語言