技术杂谈:具有可携性的脚本 shebang

PUBLISHED ON OCT 14, 2018

    传统上,命令进程序使用 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 仅保留在参数复杂或要和多个命令行工具互动时才使用即可。