洗練されたPerl: ワンライナー101

コマンド行ユーティリティーとしてのPerl

Perlをプログラミング言語として使用している人は、Perlがコマンド行操作に使用する、クイック・アンド・ダーティ・スクリプト記述エンジンと同じように有用であるということを良く忘れます。Perlではコマンド行を使用して、その他のほとんどの言語ではコードに複数ページを必要とするタスクをわずか1行で実行できます。Teodorと一緒に、役に立つ例をいくつか見ていきましょう。

Teodor ZlatanovGold Software Systems

Teodor Zlatanovは1999年にボストン大学を卒業し、コンピューター・エンジニアリングで学位を取得しています。1992年以来プログラマーとして働いており、Perl、Java、C、C++などの言語を使用してきています。関心を持っている領域としてはオープン・ソース作業、Perl、テキスト構文解析、3層のクライアント/サーバー・データベース・アーキテクチャー、Unixのシステム管理などです。助言や間違いの指摘を歓迎しています。連絡先はtzz@bu.eduです。



2001年 4月 01日

この方法を完全に行うためには、使用システムにPerl 5.6.0をインストールしておく必要があります。システムには、最近(2000年以降)のLinuxまたはUnixのインストールがいいのですが、その他のオペレーティング・システムでも機能します。ここの説明例ではすべて、tcshシェルを使用します(bashその他のシェルでも機能します)。ここに挙げる例は、Perlや、Linuxその他のオペレーティング・システムの初期バージョンでも機能するはずですが、うまくいかない場合には、練習として読者の皆さんが自分で解決してみてください。

まず指摘しておきたい第1の要点は、経験豊富なプログラマーはクイック・アンド・ダーティ・ソリューションを避けるべきではないということです。別のコラムで、ドキュメンテーションと完璧さを強調しましたが、このコラムではプログラミングの影の部分に焦点を当てており、その場合ドキュメンテーションは必ずしも必要としない代わりに、強い集中力を必要とします。これは経験で誰でも良く知っていることです。

第1の要点同様に重要な第2の要点は、クイック・アンド・ダーティ・ソリューションを適切に実行することは困難であるということです。完璧なスクリプトを文書化、テスト、デバッグする方法を把握していれば、ワンライナーで成功する可能性は非常に高いと言えます。しかし把握していない場合は、セコイアをニシンで切り倒そうとするようなものです(手にしているスキルがニシンということになります)。

最初に、シェルの特徴である、Unixがコマンド行引数をPerlに渡す方法とPerlによるその引数の解釈を学ぶ必要があります。

コマンド行の本質

Unixでは、実行可能タスクの概念は、通常メモリ上にロードされるプログラムというプロセスとして現れます。通常カーネルによって起動される初期プロセスを除いて、プロセスは他のプロセスや、場合によってはカーネル・プロセスによって起動されます。ユーザー・サイドから言えば、プロセスの開始にはシェルまたは起動プログラムが必要です。そのため、ユーザーがシェルのコマンド行にxeyesとタイプするか、または起動プログラム・メニュー(たとえば、GNOMEタスクバー)から、X Eyesアプリケーションを選択すると、シェルまたは起動プログラムがそのプログラムを実行するための新しいプロセスを作成します。

プロセスはコマンド行引数を入手します。たとえばperlとperl -wは同じプログラムに対する2つの異なる呼び出しとなります。内部的には、Perlは(Cと同じように)、@ARGVという配列を解釈して引数をスクリプトへ渡します。しかしながらCとは違って、Perlは引数のうちのいくつかをそれ自身のために使用します。例えば、実行されるスクリプトはPerlのインタプリタ自体への引数である"-w"を、そのスクリプト自身が必要としていない限り参照しません。シェルは引数をスペースで区切ります。

Perlの-e引数は、コマンド行の-eに続くものはすべて取り出して、スクリプトとして実行するようにPerlに指示します。-M引数は、-Mに続くものはすべて取り出して、モジュール(たとえば、標準スクリプトのuse ModuleName)としてインポートするように指示します。Perlがコマンド行から提供しなければならないスイッチに関する詳細情報については、perldoc perlrunページを参照してください。

おそらくここでは例を挙げるのが最も適切でしょう。このコラムの趣旨から、ワンライナーを使用しましょう。スクリプトの -MData::Dumper -e'print Dumper -@ARGV' 部分は、@ARGV配列の内容を単に出力するだけです。

リスト1. コマンド行引数
# at the command line, type each line after the '>' and you'll get the output that 
# follows it
# print the @ARGV contents with no program arguments
> perl -MData::Dumper -e'print Dumper \@ARGV'
$VAR1 = [];
# print the @ARGV contents with arguments "a" and "b"
> perl -MData::Dumper -e'print Dumper \@ARGV' a b 
$VAR1 = [
          'a',
          'b'
        ];
# print the @ARGV contents with warnings on, and arguments "a" and "b"
> perl -w -MData::Dumper -e'print Dumper \@ARGV' a b 
$VAR1 = [
          'a',
          'b'
        ];
# print the @ARGV contents with arguments "a", "b", and "-w"
# note how the -w is not stolen by Perl if it follows arguments
# that Perl knows it doesn't want
> perl -MData::Dumper -e'print Dumper \@ARGV' a b -w
$VAR1 = [
          'a',
          'b',
          '-w'
        ];
Here is the final line that includes some <angle brackets>

シェルで引数の数や長さに制限を設けていなければ、必要な数だけ引数をPerlに渡すことができます。Perlの魔法のファイル・ハンドル<>を開くと、ファイル名としてPerlに渡されたすべての引数が開き、1行ごとに各ファイルの内容が読み込まれます。$_変数はデフォルトで各行を保持しています。

シェルは、引用符内にあるものをすべて単一の引数に直します。リスト1において、 -e'print Dumper \@ARGV' と記述したものをPerlが単一のワンライナー・スクリプトと見なしたのはそのためです。一重引用符を使用する方が望ましい理由は、ワンライナーの中で二重引用符を使用できるからです。Perlにおいて二重引用符は、二重引用符で囲まれたものをすべて解釈する際に役立ちます。別の例でさらに詳しく説明しましょう。

リスト2. 一重引用符と二重引用符
# print the Perl process ID, followed by a newline
> perl -e'print "$$\n"'
2063
# error: the first two double quotes go together, the rest is passed
# to the script directly
> perl -e"print "$$\n""
Bareword found where operator expected at -e line 1, near "1895n"
        (Missing operator before n?)
syntax error at -e line 1, next token ???
Execution of -e aborted due to compilation errors.

bashの場合は二重引用符内部を\でエスケープできるため、tcshよりもbashの方がどちらかというと好ましいと言えます。しかし シェル は、Perlに渡す前に二重引用符内の$$をやはり解釈します。肝心なことは、-eワンライン・スクリプト引数指定に二重引用符を使用しないことです。詳細についてはperldoc perlrunページを参照してください。ただし、基本的には使用システムで機能するものを自分で見つけてそれに従うべきです。

これまで、実行中の-eおよび-Mスイッチ(モジュールをインポートして、ステートメントを実行する)を見てきました。以下にその他役立つスイッチを挙げておきました。ただし、混乱を避けるために複雑なスイッチは省略してあります。完全なリストおよび使用方法については、perldoc perlrunページを参照してください。

清浄度
-w警告をオンにする
-Mstrict厳密なプラグマをオンにする
データ
-0(ゼロ)入力レコード区切り文字を指定する
-a@Fという名称の配列にデータを分割する
-F 分割に使用する-aスイッチのパターンを指定する(perldoc -f splitを参照)
-iファイルをインプレース編集する(詳細についてはperldoc perlrunを参照)
-nすべての@ARGV引数を、<>を使用して1つずつファイルとして実行する
-p-nスイッチと同じだが、$_の内容も出力する
実行コントロール
-eスクリプトとして実行するストリングを指定する(複数のものを加える)
-Mモジュールをインポートする
-I標準プレース前のモジュールを検索するためのディレクトリーを指定する

ファイル操作

特定の方法でファイル名を変更するファイルがいくつかディレクトリーにあると仮定します。たとえば、aaaという語を含むファイルはすべてbbbという語に置き換えて、ファイル名を変更しなければならないとします。ここではUnixのmvコマンドは使用しませんが、その理由はPerlのrename()関数でファイル名の変更を十分適切に行えるからです(rename()関数が適切に機能しない場合の詳細については、perldoc -f renameを参照)。

aaaからbbbにファイル名を置き換えたワンライン・スクリプトについては リスト3 を参照してください。

find . コマンドは、カレント・ディレクトリー内およびそのサブ・ディレクトリーにあるすべてのファイルとディレクトリーのリストを出力します。ファイルだけが必要な場合は、find the "-type f"パラメーターを与えてください。findの出力すなわちファイル・リストを取り出して、それをワンライナーに渡してください。

ワンライン・スクリプトでは、以下のように書き換え可能であることを示す-neパラメーターを使用します。

リスト4. aaaからbbbへファイル名の変更とその分解
while (<>) 
{
 chomp;                                 # trim the newline from the filename
 next unless -e;                        # the filename ($_) must exist
 $oldname = $_;                         # $oldname is now $_
 s/aaa/bbb/;                            # change all "aaa" to "bbb" in $_
 next if -e;                            # the new filename mustn't exist
 rename $oldname, $_;                   # rename the old to the new name
}

ご覧のとおり、これはかなり複雑な7行のスクリプトです。-nスイッチが手順を簡略にしています。しかしそれでも、$_変数、s///および-e演算子を把握していなければなりません(詳細については、perldoc perlopページを参照)。Unixのfindコマンドの代わりにFile::Find標準Perlモジュールをfile findの実行に使用できるはずですが、その場合はスクリプトはおそらく大きくなりすぎてワンライナーでは無理でしょう。

ワンライナーは、使いやすさと分かりにくさの微妙なバランスのもとに成り立つものであり、小さいが手に負えなくなる恐れのあるプログラムを保持し続けるのではなく、必要であれば実際のスクリプトとして書き換える準備をしておかなければなりません。

ファイル処理の別の例を挙げてみましょう。既知の命名構造を使用してMP3ファイルのディレクトリーを調べて、アルバム名を抜き出します。ファイル名はArtist-Album-Track#-Song.mp3であると仮定します。

リスト5. Artist-Album-Track#-Song.mp3のアルバム名の検索
> find . -name "*.mp3" | perl -pe 's/.\/\w+-(\w+)-.*/$1/' | sort | uniq

このスクリプトは非常に単純です。findの振る舞いに応じて、必ず各ファイル名の前に./を1つ出力します。次に$_をアルバム名だけで置き換えて、-pスイッチが自動的にアルバム名を出力します。最後にsortとuniqの順番により、同じアルバム名が繰り返される場合は必ず1度だけ出力されるようになります。find、sort、uniqという呼び出しはすべてPerlで実行できるはずですが、オペレーティング・システムですでにそれらを記述している場合、なぜわざわざ面倒な手間をかけるのでしょうか? 練習としては面白いでしょうが、実際にはワンライナーは20?30行もの不要なコードになってしまいます。

Perlスクリプトを分解してみましょう(簡略化した方法で、-pスイッチの複雑な部分を一部省略しています)。

リスト6. Artist-Album-Track#-Song.mp3のアルバム名の検索とその分解
while (<>) 
{
 s/.\/\w+-(\w+)-.*/$1/;                 # extract the album name into $_
} continue
{
 print;                                 # print the album name
}

もう一度、Perlがどのような形態でfind、sort、uniq間の媒介ツールであったかに注意してください。Perlですべてを記述しようとしないでください。すべての記述は可能であり、時にはその必要もありますが、ワンライナーは再利用するものです。また、正規表現がどの程度単純であるかも見てください。当然、MP3ファイル名が正しく付けられていなければ、いくつかおかしなアルバム名が出てくることもありますが、その正規表現を完全なものにするよう努力する価値はあるのでしょうか? そんな労力を要する作業を行う場合は、おそらくファイル名を解析する代わりにCPANのMP3 ID3タグ・モジュールを使用すべきです。ワンライナーがツールというよりもむしろ厄介なものになる場合を把握しておいてください。このことが、ワンライナーに取りかかる前にPerlを十分に把握しなければならないと先に述べた意味です。プログラミング・アプローチにおいてすべてのツールを使用すると、優れたPerlプログラマーであると同時に全体的に見て優秀なプログラマーともなります。


データ操作

上記の概念はデータ操作でも同様に当てはまります。また-iスイッチを覚えておかなければなりません。その理由は、ほとんどのツールではまったく実行できないファイルのインプレース編集が可能だからです。ここではどうやってファイルの内容を編集して、すべてのaaaをbbbに置き換えるかを見ていきます。

リスト7. aaaからbbbへの置換のためのファイル内容の編集
> cat test
aaa
bbb
ccc
ddd
aaa
> perl -pi -e's/aaa/bbb/' test
> cat test
bbb
bbb
ccc
ddd
bbb

もちろん、aaaの代わりにどんな正規表現でも使用できます。

すべての行について$_の出力には-pスイッチを使用することに注意してください。これが必要な理由は、Perlスクリプトの 出力 はファイル内部で行われるからです。つまりいくつか面白いトリックを行えるということです。たとえば、次のとおりです。

リスト8. ファイルに行番号を挿入
> perl -pi -e'$_ = sprintf "%04d %s", $., $_' test

このスクリプトは、ファイルのすべての行に4桁の行番号を挿入します。構文を見て頭が痛くなったら、すぐ近くの人に動物園の2頭のラクダに関するジョークを知っているかと尋ねてみてください。何か重いもので頭を殴られたようになれ、しばらくの間は頭痛のことは忘れて、それからまた仕事に戻れます。

今度はさらに複雑なものです。ここではUri Guttmanの優れたFile::ReadBackwardsモジュールを使用し、興味深いイベントを求めてログ・ファイルを逆順に調べます(CPANからFile::ReadBackwardsモジュールをインストールしておく必要があります)。sshdというストリングを探して、sshdデーモンからのすべての通知を見ます。

リスト9. ファイルの逆順にsshdメッセージを検索
> perl -MFile::ReadBackwards -e'foreach my $name (@ARGV) \
   { $f = File::ReadBackwards->new($name) || next;       \
     while( $_ = $f->readline ) {print $_ if m/sshd/}}'  \
  /var/log/messages

各行の最後にある\文字は、まだ続きがあること、つまり行がまだ終わってはいないことをシェルに知らせるものです。この3行のスクリプトは、実際のスクリプトに書き換えなければならなくなる前のワンライナーの大きさとほぼ同じです。ファイルのすべての行を省略して逆順に出力することによって、少ないコードで同じ効果を実現できますが、実際にファイルを逆順に読み込んで新しい行で停止するFile:ReadBackwardsの場合と比べると、効率ははるかに落ちます。この効率は、コマンド行からでは容易に実現できないものです。

しかしなぜここで停止するのでしょうか? sshdログ・メッセージで言及されているすべてのIPアドレスを抜き出してみましょう。

リスト10. ファイルの逆順にsshdメッセージにあるIPを検索
> perl -MFile::ReadBackwards -e'foreach my $name (@ARGV) \
   { $f = File::ReadBackwards->new($name) || next;       \
     while( $_ = $f->readline ) \
     {print "$1\n" if m/sshd/ && m/connection from\D*([\d.]+)/ }}' \
  /var/log/messages

これでは見苦しくなりました。まさに今ここで、これを実際のスクリプトに移動するべきです。

上記の正規表現が、connection fromおよび数字以外の文字列の後で、どのように数字と点だけを捉えるかに注意してください。これは完璧なものではありませんが、現実の世界ではIPv4アドレスで問題なく機能します。ワンライナーで必要なものを理解して、正確にそれを実行するべきです。使い捨てスクリプトをあれこれ工作しすぎないようにしてください。後悔することになります。一方で、スクリプトを捨てない場合を把握して、それに従ってコードを記述してください。


実際の例

私の妻は休暇の写真を一山分もWindowsで名称変更していました。ファイル名としてはOur Christmas Tree.jpgといった、まあ問題のない名称が付けられていました。イメージ集合のHTMLページを作成するPerlスクリプトであるindexpage.plを実行しようとしたら、スクリプトは正常に機能しませんでした。問題は、ファイル名、引用符、スペースに作成者が無頓着だったからです。

indexpage.plを自分で直す代わりに(自分で直せば結構な練習になりましたが、午前2時ではそのような時間はなく)、私はワンライナーを使用しました。
JPGファイル名を変更するワンライン・スクリプトについては リスト11 を見てください。

これは厄介でした。というのも、スクリプト内部で一重引用符を使用できなかったからです。結局一重引用符にASCII値39を使用し、$quote変数内に一重引用符を置いて、置換で間接的に使用しました。

これは一連のmvコマンドを出力し、それによって自分が正しいことをしているのか確認することが出来ます。最後に、コマンドをファイルに保存して、そのファイルのすべてのコマンドを実行するシェルsourceコマンドを使用しました。 リスト12 に実行中のJPGファイル名変更が表示されています。

名称変更後、indexpage.plスクリプトは正常に実行できました。


結論

ここまで見てきて、ワンライナーを適切に実行するのは容易でないことがお分かりいただけたと思います。ワンライナーに飛び付く前に、まずスクリプト記述スキルに習熟してください。さもないと、ワンライナーを適切に実行するには非常な困難が伴うでしょう。正規表現、フロー制御、デフォルト変数操作を確実に把握してください。

パワーと分かりやすさとのバランスを取ってください。ワンライナーは、プロトタイプのように使い捨てにするべきです。使い捨てにしないと、迷子になった可愛いプードルが帰ってきたら獰猛な狂犬になっていたというようなことになりかねません。

ワンライナーを汎用に用いることはわずかな例外以外認められません。それらは使い捨てのもので、ピラミッド(のように後々まで残るもの)ではありません。

決してワンライナーを即座に実行してはなりません。実際にコマンドを実行する前に、ワンライナーが実行するものを必ず出力してください。これでひどい白髪になるのを防げます。

ワンライナーのスキルは控えめに使用してください。このような野獣の扱いには、用心深すぎるということはありません。

最後に、思いっきり楽しんでください。ワンライナーは自分の代わりにPerlに面倒な作業をさせる最善の方法です。さまざまなアイデアや論評については、UsenetにあるPerl専門のニュース・グループやメーリング・リストを見てください。

参考文献

  • CPAN とは、Comprehensive Perl Archive Network(総合Perlアーカイブ・ネットワーク)のことです。その目的は、必要なPerlの資料をすべて保管することです。2001年1月の時点で、749 MBの情報があり、世界中に100以上ものミラーサイトがあります。
  • Perlの情報や関連リソースについては、 Perl.com にアクセスしてください。このサイトにはPerlコミュニティーに関係のあるものがすべて揃っています。
  • Programming Perl, 3rd Edition , Larry Wall, Tom Christiansen, and Jon Orwant, (O'Reilly & Associates 2000) は、5.005から5.6.0への変更が反映された、最新のPerlに関するベスト・ガイドです。
  • Unix Power Tools, 2nd Edition, Jerry Peek, Tim O'Reilly & Mike Loukides (O'Reilly & Associates 1997) は、Unixのシェルおよびその関連ツールの初心者向けの素晴らしいガイドです。少し古いですが、それでも優れた内容の本です。
  • Programming Perlなどその他の多くの優れた書籍の出版社である O'Reilly & Associates のホームページにもアクセスしてください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=226806
ArticleTitle=洗練されたPerl: ワンライナー101
publish-date=04012001