アルパカ三銃士

〜アルパカに酔いしれる獣たちへ捧げる〜

Go のバイナリを Perl スクリプトとしても扱う

環境は macOS 10.14.4 で go1.12.5 です。

まずは次のコードを読んでみましょう

package main

import (
    "fmt"
    "io/ioutil"
)

const script = `
#!perl
print "Hello, Perl World!!";
__END__
`

func init() {
    ioutil.Discard.Write([]byte(script))
}

func main() {
    fmt.Println("This is Go world!!")
}

これをビルドして実行してみます

go build -o main main.go

こうすると main という実行用のバイナリができますね。

通常通り実行してみましょう。

$ ./main
This is Go world!!

今度は Perl で実行してみましょう

$ perl -x ./main
Hello, Perl World!!

ワオ!!

解説

Go のコードに記載していた文字列が使われる場合、その文字列はバイナリに含まれるようです。(将来的にコンパイラがもっと賢くなれば、こういったことはなくなるかもしれません)

ビルドした Go のバイナリを覗いてみましょう

$ strings ./main | less
# /perl で検索
#!perl
print "Hello, Perl World!!";
__END__
exitsyscall: ... # 何かしら記述が続く

といったような感じになってます。 上記のバイナリに含まれる文字列を Perl のコードとして見てみるとこんな感じになります。

#!perl
print "Hello, Perl World!!";
__END__
exitsyscall: ... # 何かしら記述が続く

ここまで分かれば今度は Perl 側の話に移ります。

Perl のコードで __END__ 以下に何を記述されても全て意味を持たないものとして扱われるので無視されます。そしてもう一つ perl -x の x オプションは #!perl の行までのテキストを全て無視するようになります。

-x[directory]     ignore text before #!perl line (optionally cd to directory)

これらの条件が全て揃うことによって実行可能なバイナリを Perl スクリプトとしても扱うことが可能になるわけです。

Ruby でも同じ -x オプションがあるとのことなので是非試してみたいですね!!

協力してくださった @tompng さんありがとうございました!