Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

この記事は、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に馴染んでいる人ならある程度違和感なく使えるかと思います。
ということで、是非使ってみてください。

sago35
現職は、車載組込エンジニア。 仕事では主にC言語、ツール類はperlおよびgolangで作成。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away