技術雜談:具有可攜性的命令稿 shebang

PUBLISHED ON OCT 14, 2018
FacebookTwitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    傳統上,命令列程式使用 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 僅保留在參數複雜或要和多個命令列工具互動時才使用即可。