傳統上,命令列程式使用 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 Michelle\n";
PERL_END
)
awk "$awk_script"
perl -e "$perl_script"
在這個例子中,我們先將兩個腳本分別存在變數中,再輪流呼叫即可。由於變數內存的是程式碼而非檔案名稱,所以呼叫方式要略為修改。
在這兩種方案中,如果沒有傳入多參數的需求的話,使用 env
會比較簡單,而 shell wrapper 僅保留在參數複雜或要和多個命令列工具互動時才使用即可。