Getopt::Kingpinを使って、さくっとコマンドラインスクリプトを作成する

  • 2
    いいね
  • 0
    コメント

この記事は、Perl 5 Advent Calendar 2016 - Qiitaの3日目の記事です。

昨日はnqounetさんのPerl5で文字列操作でした。
Perlの文字列操作はかなり強力なのを再認識できますね。

今日は、引数解析の拙作モジュールGetopt::Kingpinに関する記事です。

Goal

Getopt::Kingpin を使ったコマンドラインスクリプトのサンプルを提示する。

背景

前の記事(perlスクリプトの引数処理モジュールGetopt::Kingpinの紹介)で、モジュールを紹介しました。
今回は、実際の使用例/実装例を示したい。
ドキュメント/サンプルソースを充実させたいので、そのついでとしてという意味も。

お題 ≒ 今回作るもの

wcコマンドを作成してみます。
https://infohost.nmt.edu/tcc/cgi/manweb.cgi?p=wc によると、ヘルプは以下のような感じ。
これをざっくりコピーする。

NAME
    wc - print the number of newlines, words, and bytes in files

SYNOPSIS
    wc [OPTION]... [FILE]...

DESCRIPTION
    Print newline, word, and byte counts for each FILE, and a total line if more than one FILE is specified.
    With no FILE, or when FILE is -, read standard input.

    -c, --bytes
        print the byte counts
    -m, --chars
        print the character counts
    -l, --lines
        print the newline counts
    -L, --max-line-length
        print the length of the longest line
    -w, --words
        print the word counts
    --help
        display this help and exit
    --version
        output version information and exit

作ってみる

基本

Getopt::Kingpinは、なるべくオリジナルであるgolang版に近い動き/記述ができるように作ってあります。
SYNOPSISに従いGetopt::Kingpin->new()して、flag()およびarg()を追加していくのが基本となります。
それぞれ以下のようなイメージです。

  • new('script name', 'script description')
    • parserを生成します
  • flag('flag name', 'flag description')
    • --words-w等の-から始まるフラグ(≒オプション)を設定する
  • arg('arg name', 'arg description')
    • 引数を表す(flagを除くもの(典型的には-から始まらないもの))

ここまで設定できたらparse()を実行し引数を処理します。

もう少し詳細

例えば$kingpin->flag('lines', 'print the newline counts')->short('l')->boolであれば以下のイメージです。

  • flagの名前/指定の仕方は--lines
  • 詳細はprint the newline counts
  • flagのshort_nameは-l
  • 型はbool (0 or 非0)

引数の解析

引数の解析部まで、以下に示します。

use Getopt::Kingpin;

my $kingpin = Getopt::Kingpin->new($0, 'print the number of newlines, words, and bytes in files');
my $flag = {
    lines           => $kingpin->flag('lines', 'print the newline counts')->short('l')->bool,
    words           => $kingpin->flag('words', 'print the word counts')->short('w')->bool,
    chars           => $kingpin->flag('chars', 'print the character counts')->short('m')->bool,
    bytes           => $kingpin->flag('bytes', 'print the byte counts')->short('c')->bool,
    max_line_length => $kingpin->flag('max-line-length', 'print the length of the longest line')->short('L')->bool,
};
my $files = $kingpin->arg('file', 'target file[s]')->required->existing_file_list;
$kingpin->version('0.02');
$kingpin->parse;

ここまで書いた時点でヘルプの実装はできていて、以下のように表示できます。

$ perl wc.pl --help
usage: wc.pl [<flags>] <file>

print the number of newlines, words, and bytes in files

Flags:
      --help             Show context-sensitive help.
  -l, --lines            print the newline counts
  -w, --words            print the word counts
  -m, --chars            print the character counts
  -c, --bytes            print the byte counts
  -L, --max-line-length  print the length of the longest line
      --version          Show application version.

Args:
  <file>  target file[s]

参考) golang版

うーん、そっくり(当たり前)。
実装もヘルプも、ほとんど同じになるように作っています。

package main

import (
    "gopkg.in/alecthomas/kingpin.v2"
    "os"
)

var (
    app             = kingpin.New("wc", "print the number of newlines, words, and bytes in files")
    lines           = app.Flag("lines", "print the newline counts").Short('l').Bool()
    words           = app.Flag("words", "print the word counts").Short('w').Bool()
    chars           = app.Flag("chars", "print the character counts").Short('m').Bool()
    bytes           = app.Flag("bytes", "print the byte counts").Short('c').Bool()
    max_line_length = app.Flag("max-line-length", "print the length of the longest line").Short('L').Bool()
    files           = app.Arg("file", "target file[s]").Required().ExistingFiles()
)

func main() {
    app.Version("0.02")
    app.Parse(os.Args[1:])
}
usage: wc [<flags>] <file>...

print the number of newlines, words, and bytes in files

Flags:
      --help             Show context-sensitive help (also try --help-long and
                         --help-man).
  -l, --lines            print the newline counts
  -w, --words            print the word counts
  -m, --chars            print the character counts
  -c, --bytes            print the byte counts
  -L, --max-line-length  print the length of the longest line
      --version          Show application version.

Args:
  <file>  target file[s]

スクリプト完成版

以下に完成版を置いてますので、適宜参照してください。

https://gist.github.com/sago35/76e26a4e3603dbc1438bbe2471e571df

注意点

以下のように*_listという名前の型を使うと、引数fileを複数(0個以上)指定することができます。
また、$filesは、Getopt::Kingin::Argオブジェクトのarray refとなります。

my $files = $kingpin->arg('file', 'target file[s]')->required->existing_file_list;

なので、使うときにはデリファレンスが必要で、以下のようにします。

my @files = @{$files->value};

このようにすると、@filesPath::Tinyオブジェクトの配列となります。
後は煮るなり焼くなりどうぞ。

#この部分の作りは、もっといいやり方に直したいとは思っているが、いい方法が浮かばない

まとめ

自分が作ったものを使っているので当たり前ですが、自分にとっては使いやすいです。
golang版kingpinに馴染んでいる人ならある程度違和感なく使えるかと思います。
ということで、是非使ってみてください。