'^$'
`'^$'
`'^.+'
`'^.+'
`':'
-f 1,6`':'
-f 1,6 -s`'A-Za-z'
'N-ZA-Mn-za-m'
`
perl -[n|p][l]e 'script' [file...]
perl [-n|-p][-l] script_file [file...]
perl -n
は『レコード』と呼ばれる行を一つずつパターンスペース '$_'
に入力する。perl -p
はさらにそのパターンスペース '$_'
を出力する。Perl スクリプトを '-e script'
や 'script_file'
で指定することにより様々な処理をすることができる。
まず、入力行は sed, Awk とは異なり、レコードセパレータである改行コード '\n'
が取り除かれずにパターンスペース '$_'
に入る。但し、'-l'
オプションを指定すると、改行コードが取り除かれ、出力のレコードセパレータに改行コード '\n'
が設定される。
また、perl -na
は、Awk のように、フィールドセパレータである空白を区切りとして '$F[0]'
, '$F[1]'
, '$F[2]'
, 〜 にその行の『フィールド』群が入る。
Awk のように 'BEGIN'
, 'END'
のような前処理と、後処理を記す特殊ブロックが使えるが、他はすべてサブルーチンか主処理となる。そして、sed, Awk のようなマッチの範囲「式, 式」に処理されるブロックはサポートされないが、条件式で '..'
演算子(2つの式が sed スタイルのときは '...'
演算子)を用いることでそれと似た制御ができる。
例えば以下は、Awk では awk
となる、HTML の '/^<pre>/,/<\/pre>$/'
'pre'
タグを含むそれに囲まれた行を表示する Perl スクリプトである。
perl -ne 'print if (/^<pre>/../<\/pre>$/)'
ここで、'print'
は 'print $_'
と等価である。
例えば以下は、sed では sed -e
となる、ソースコードを HTML にペーストできるように「<」から「<」への変換等を行なう Perl スクリプトである。
's/&/\&/g;s/</\</g;s/>/\>/g'
perl -pe 's/&/&/g; s/</</g; s/>/>/g'
このように Perl スクリプトは、オプションにより sed, Awk 風に書けるようになっている。
perl -n
は『レコード』と呼ばれる行を一つずつパターンスペース '$_'
に入力する、以下とほぼ等価なスクリプトとなる。
perl -e 'while (<>) {…}'
これはさらに以下とほぼ等価なスクリプトとなる。
perl -e ' unshift(@ARGV, "-") unless @ARGV; while (defined($ARGV = shift @ARGV)) { open(FH, $ARGV); while ($_ = <FH>) { … } close(FH) }'
perl -p
はさらにそのパターンスペース '$_'
を出力する、以下とほぼ等価なスクリプトとなる。
perl -e ' while (<>) { … } continue { print }'
これはさらに以下とほぼ等価なスクリプトとなる。
perl -e ' unshift(@ARGV, "-") unless @ARGV; while (defined($ARGV = shift @ARGV)) { open(FH, $ARGV); while ($_ = <FH>) { … } continue { print $_ } close(FH) }
まず、入力行は sed, Awk とは異なり、レコードセパレータである改行コード '\n'
が取り除かれずにパターンスペース '$_'
に入る。但し、'-l'
オプションを指定すると、改行コードが取り除かれ、出力のレコードセパレータに改行コード '\n'
が設定される、以下とほぼ等価なスクリプトとなる。
perl -e 'BEGIN{ $\ = "\n" } while (<>) { chomp; … }'
また、perl -na
は、Awk のように、フィールドセパレータである空白を区切りとして '$F[0]'
, '$F[1]'
, '$F[2]'
, 〜 にその行の『フィールド』群が入る、以下とほぼ等価なスクリプトとなる。
perl -e'while (<>) { @F = split('
', $_); … }'
このように Perl は様々な場面で省略可能な引数などのサポートが手厚く、簡素に書ける反面、一見不明瞭なコードになりがちである。
例えば先の、HTML の 'pre'
タグを含むそれに囲まれた行を表示する Perl スクリプトは、省略せずに書けば以下のようになる。
perl -e ' while (<>) { print $_ if ($_ =~ /^<pre>/ .. $_ =~ /<\/pre>$/) }'
例えば先の、ソースコードを HTML にペーストできるように「<」から「<」への変換等を行なう Perl スクリプトは、省略せずに書けば以下のようになる。
perl -e ' while (<>) { $_ =~ s/&/&/g; $_ =~ s/</</g; $_ =~ s/>/>/g } continue { print $_ }'
スクリプトは宣言か文のリストからなり、宣言はピクチャ書式とサブルーチン、文は代入式、制御構文、関数呼び出し、ブロック '{'
宣言か文のリスト '}'
である。宣言か文の区切りは ';'
で区切る。シェルや C/C++ の文と似ているが '}'
直前の ';'
が省略できることが異なる。
Perl の変数には、'$'
から始まるスカラ変数、'@'
から始まる配列変数、'%'
から始まるハッシュ変数があり、その値は数値、文字列、リファレンスの型を扱える。配列、ハッシュの値は '$'
から始まる変数名と '[添字]'
や '{キー}'
でアクセスする。
特に、変数には数値と文字列が区別なく扱える一方で、文字列の '0'
を意図せず数値として評価されてしまうなど、注意が必要となる。
配列変数は:
#!/usr/bin/perl @list = (1, 2, 3, 4, 5); print $list[1], "\n"; # 要素: 2 print $list[3], "\n"; # 要素: 4 print scalar @list, "\n"; # 要素数: 5
ハッシュ変数は:
#!/usr/bin/perl %hash = ('foo'
=>'Foo'
,'bar'
=>'Bar'
); print $hash{'foo'
}, "\n"; # 値:'Foo'
print $hash{'bar'
}, "\n"; # 値:'Bar'
print scalar keys %hash, "\n"; # 要素数: 2
変数へのリファレンスは '\'
で表し、デリファレンスはアクセスしたい型に応じて '$'
, '@'
, '%'
を用いる。
スカラ変数へのリファレンス:
#!/usr/bin/perl
$scalar = 'A Happy New Year!'
;
$ref = \$scalar;
print $$ref, "\n"; # 文字列: A Happy New Year!
配列変数へのリファレンスは:
#!/usr/bin/perl @list = (1, 2, 3, 4, 5); $ref = \@list; print $$ref[1], "\n"; # 要素: 2 print $$ref[3], "\n"; # 要素: 4 print scalar @$ref, "\n"; # 要素数: 5
ハッシュ変数へのリファレンスは:
#!/usr/bin/perl %hash = ('foo'
=>'Foo'
,'bar'
=>'Bar'
); $ref = \%hash; print $$ref{'foo'
}, "\n"; # 値:'Foo'
print $$ref{'bar'
}, "\n"; # 値:'Bar'
print scalar keys %$ref, "\n"; # 要素数: 2
スカラへのリファレンスは '\'
で表し、無名配列へのリファレンスは […]
、無名ハッシュへのリファレンスは {…}
で記述できる。
スカラへのリファレンス:
#!/usr/bin/perl
$ref = \'A Happy New Year!'
;
print $$ref, "\n"; # 文字列: A Happy New Year!
無名配列へのリファレンスは:
#!/usr/bin/perl $ref = [ 1, 2, 3, 4, 5 ]; print $$ref[1], "\n"; # 要素: 2 print $$ref[3], "\n"; # 要素: 4 print scalar @$ref, "\n"; # 要素数: 5
無名ハッシュへのリファレンスは:
#!/usr/bin/perl $ref = {'foo'
=>'Foo'
,'bar'
=>'Bar'
}; print $$ref{'foo'
}, "\n"; # 値:'Foo'
print $$ref{'bar'
}, "\n"; # 値:'Bar'
print scalar keys %$ref, "\n"; # 要素数: 2
他に、上記を応用した「多次元配列」や「構造体」がある。
以下の制御構文が使用できる。シェルの 'elif'
, C の 'else if'
ではなく 'elsif'
であること、C の 'continue'
ではなく 'next'
、シェル/C の 'break'
ではなく 'last'
であることに注意。
if () {} [elsif {}] [else {}]
unless () {} [elsif {}] [else {}]
while () {} [continue {}]
until () {} [continue {}]
{ do { {} [continue {}] } while () }
{ do { {} [continue {}] } until () }
for (;;) {}
for (@) {}
continue {}
last [label]
next [label]
redo [label]
return [$]
exit [$]
foreach [var] (@) {}
goto label
'switch'
は基本的にはサポートされないが、代わりに 'switch'
風なブロックや 'given'
, 'when'
構文が存在する。
以上は C/C++ とは異なり必ずブロックを必要とし、単文は書けない。しかし、以下のような制御構文であれば、ブロックではなく単文で記述できる。
print if ()
print unless ()
print while ()
print until ()
print for ()
print foreach ()
Perl の演算子は C/C++ のそれとおよそ同じだが、加えて、文字列や正規表現のための演算子など多数存在する。特に注意すべき演算子を以下にあげる。
Perl | C/C++ | 備考 |
expr1 . expr2 | string(expr1) + string(expr2) | 文字列の連結 |
lvalue .= expr | string(lvalue) += string(expr) | 文字列の連結の代入 |
expr1 ** expr2 | pow(expr1, expr2) | 指数関数 |
lvalue **= expr | lvalue = pow(lvalue, expr) | 指数関数の代入 |
expr1 =~ expr2 | regex_search(expr1, , regex(expr2, ...)) | C/C++ の ビット否定ではなく、正規表現のマッチ |
expr1 !~ expr2 | !regex_search(expr1, , regex(expr2, ...)) | C/C++ の ビット否定ではなく、正規表現のマッチの否定 |
expr1 eq expr2 | string(expr1) == string(expr2) | 文字列の比較として、等しい |
expr1 ne expr2 | string(expr1) != string(expr2) | 文字列の比較として、等しくない |
expr1 lt expr2 | string(expr1) < string(expr2) | 文字列の比較として、小さい |
expr1 gt expr2 | string(expr1) > string(expr2) | 文字列の比較として、大きい |
expr1 le expr2 | string(expr1) <= string(expr2) | 文字列の比較として、等しいか小さい |
expr1 ge expr2 | string(expr1) >= string(expr2) | 文字列の比較として、等しいか大きい |
Perl における主な特殊変数は以下の通りである。
@ARGV
- コマンドライン引数の配列 $ARGV[0]
, $ARGV[1]
... $ARGV[@ARGV-1]
$ARGV
- 現在読み込み中のファイル名ARGV
- 現在読み込み中のファイルハンドル%ENV
- 環境変数の連想配列 $ENV{名前}
$RS
, $/
- 入力のレコードセパレータ。既定値は "\R"$NR
, $.
- 入力のレコード数$ORS
, $\
- 出力のレコードセパレータ。既定値は "undef"$OFS
, $,
- 出力のフィールドセパレータ。既定値は "undef"$SUBSEP
, $;
- 疑似的な多次元配列の添字の区切り。既定値は "\x1c,\034,28,^\,IS4(FS)"$ARG
, $_
- 既定のパターンスペース@ARG
, @_
- サブルーチンに渡された引数の配列$MATCH
, $&
- 正規表現で最後にマッチした文字列$PREMATCH
, $`
- 正規表現で最後にマッチした直前の文字列$POSTMATCH
, $'
- 正規表現で最後にマッチした直後の文字列$LAST_PAREN_MATCH
, $+
- 正規表現で最後にマッチした最後のグループの文字列%LAST_PAREN_MATCH
, %+
- 正規表現で最後にマッチした名前付きグループの文字列のハッシュ@LAST_MATCH_START
, @-
- 正規表現で最後にマッチしたグループの文字列の先頭のオフセットの配列@LAST_MATCH_END
, @+
- 正規表現で最後にマッチしたグループの文字列の終端のオフセットの配列%-
- 正規表現で最後にマッチした名前付きグループの文字列の配列のハッシュ$1, $2, ... ${$#+}
- 正規表現で最後にマッチしたグループの文字列@F
- '-a'
オプションでレコード毎に格納されるフィールドの配列Perl におけるエスケープ文字は以下の通りである。
\a
- "^G,BEL,アラート(alert)"\b
- "^H,BS,バックスペース(backspace)"、但し、[]
内のみ。\t
- "^I,HT,水平タブ(horizontal tab)"\n
- "^J,LF,改行(new-line)"\f
- "^L,FF,改頁(form feed)"\r
- "^M,CR,行頭復帰(carriage return)"\v
- "^K,VT,垂直タブ(vertical tab)"、但し、[]
内のみ。\e
- "^[,ESC,エスケープ(escape)"\OOO
- 8進数の文字コード\o{OOO}
- 8進数の文字コード\xHH
- 16進数の文字コード\x{HHHH}
- 16進数のワイド文字コード\cC
- 制御文字。C
には任意の文字。\N{NAME}
- Unicode 名の文字\N{U+HHHH}
- Unicode 文字\l
- 次の文字を小文字化。[]
内では不可。\u
- 次の文字を大文字化。[]
内では不可。\L
- \E
まで小文字化。[]
内では不可。\U
- \E
まで大文字化。[]
内では不可。\Q
- \E
まで非英数字文字をバックスラッシュでエスケープ化。[]
内では不可。\E
- 上述の終端。[]
内では不可。\w
- 英数字とアンダースコア「_」\W
- 上記以外\s
- 空白\S
- 上記以外\d
- 数字\D
- 上記以外\p{}
- Unicode プロパティの文字\P{}
- 上記以外\X
- Unicode「拡張書記素クラスタ」。[]
内では不可。\C
- Unicode 内で単一のオクテット。[]
内では不可。\K
- 直前を保持、$&
に含めない。[]
内では不可。\N
- 改行以外の文字。[]
内では不可。\v
- 垂直空白の文字クラス\V
- 上記以外\h
- 水平空白の文字クラス\H
- 上記以外\R
- 総称的な改行。[]
内では不可。\b
- ワード境界。但し、[]
内では上述。\B
- 上記以外。[]
内では不可。\A
- 文字列の先頭。[]
内では不可。\Z
- 文字列の末尾。[]
内では不可。\z
- 同上。[]
内では不可。\G
- 文字列の先頭か前回のマッチ直後。[]
内では不可。\1
- 後方参照。1
には正の整数。[]
内では不可。\g1
- 後方参照。1
には正の整数。[]
内では不可。\g{1}
- 後方参照。1
には正の整数。[]
内では不可。\g{-1}
- 相対後方参照。1
には負の整数。[]
内では不可。\g{name}
- 名前後方参照。[]
内では不可。\k'name'
- 名前後方参照。[]
内では不可。\k<name>
- 同上\k{name}
- 同上このように Perl では非常に多くのエスケープ文字がサポートされる。
abs [$]
- 値 $
の絶対値。$
を省略した場合 $_
。atan2 $y, $x
- 値 $y/$x
についての逆正接関数cos [$]
- ラジアン値式 $
についての余弦関数。$
を省略した場合 $_
。sin [$]
- ラジアン値式 $
についての正弦関数。$
を省略した場合 $_
。exp [$]
- 値式 $
についての指数関数。$
を省略した場合 $_
。log [$]
- 値式 $
についての対数関数。$
を省略した場合 $_
。sqrt [$]
- 値式 $
の平方根。$
を省略した場合 $_
。int [$]
- 値式 $
の小数点以下切捨て。$
を省略した場合 $_
。rand [$]
- 値式 $
のとき [0, $)
の乱数。$
を省略した場合 1
。srand [$]
- 正整数 $
で rand
のシード設定。$
を省略した場合は自動設定。acos [$]
- 値式 $
についての逆余弦関数。$
を省略した場合 $_
。但し、Math::Trig
要。asin [$]
- 値式 $
についての逆正弦関数。$
を省略した場合 $_
。但し、Math::Trig
要。tan [$]
- ラジアン値式 $
についての正接関数。$
を省略した場合 $_
。但し、Math::Trig
要。sqrt [$]
- 値式 $
の複素数平方根。$
を省略した場合 $_
。但し、Math::Complex
要。pop [@]
- 配列の最後の値を返し、それを除いて、配列サイズを一つ減らす。引数を省略した場合、'@ARGV'
(サブルーチン内は '@_'
) と見なすpush @, @l
- 配列にリスト(または値) @l
を末尾に追加する。shift [@]
- 配列の最初の値を返し、それを除いて、配列サイズを一つ減らす。引数を省略した場合、'@ARGV'
(サブルーチン内は '@_'
) と見なすunshift @, @l
- 配列にリスト(または値) @l
を先頭に追加する。splice @, $offset[, $length, @l]
- 配列の要素の削除および、リスト(または値) @l
の追加の操作grep $, @
- リストから式 $
が真である要素のリストを返す。式において、要素は $_
にセットされる。grep {} @
- リストからブロック {}
が真である要素のリストを返す。ブロックにおいて、要素は $_
にセットされる。map $, @
- リストの要素に式 $
を適用した要素のリストを返す。式において、要素は $_
にセットされる。map {} @
- リストの要素にブロック {}
を適用した要素のリストを返す。ブロックにおいて、要素は $_
にセットされる。sort [{}] @
- リストをブロック {}
が返す比較で並べ変えたリストを返す。ブロックはサブルーチン名でもよい。join $, @
- 文字列式 $
をセパレータとしてリストを単一の文字列へ連結split $pattern[, $, $n]
- 文字列式 $
を正規表現 $pattern
で複数の文字列へ分割。$
を省略した場合 $_
。qw/WORD LIST/
- ワード列のリストを返す。reverse [$_]
- リストの逆順のリストを返す。unpack $template[, $]
- pack
の $template
形式に従ってバイナリ文字列式 $
からリストを生成する。$
を省略した場合 $_
。exits $
- 式 $
で表されるハッシュのキー、もしくは、配列の要素が存在するか否かを返す。delete $
- 式 $
で表されるハッシュのキーと値、もしくは、リストの要素を削除し、それを返す。each @
- 配列 @
の添字と値を次々と返し、最後は偽を返す。each %
- ハッシュ %
のキーと値を次々と返し、最後は偽を返す。each $
- 配列かハッシュへのリファレンス式 $
の添字と値もしくはキーと値を次々と返し、最後は偽を返す。keys @
- 配列 @
の添字を次々と返し、最後は偽を返す。keys %
- ハッシュ %
のキーを次々と返し、最後は偽を返す。keys $
- 配列かハッシュへのリファレンス式 $
の添字もしくはキーと値を次々と返し、最後は偽を返す。values @
- 配列 @
の値を次々と返し、最後は偽を返す。values %
- ハッシュ %
の値を次々と返し、最後は偽を返す。values $
- 配列かハッシュへのリファレンス式 $
の値を次々と返し、最後は偽を返す。chr [$]
- コード値 $
で表される文字を返す。$
を省略した場合 $_
。ord [$]
- 文字列式 $
の一文字目ので表されるコード値を返す。$
を省略した場合 $_
。chomp [$]
- 文字列変数 $
の文字列の末尾の改行を削除する。$
を省略した場合 $_
。chop [$]
- 文字列変数 $
の文字列の末尾の一文字を削除する。$
を省略した場合 $_
。length [$]
- 文字列式 $
の長さを返す。$
を省略した場合 $_
。lc [$]
- 文字列式 $
を小文字化した文字列を返す。$
を省略した場合 $_
。lcfirst [$]
- 文字列式 $
を頭文字だけ小文字化した文字列を返す。$
を省略した場合 $_
。uc [$]
- 文字列式 $
を大文字化した文字列を返す。$
を省略した場合 $_
。ucfirst [$]
- 文字列式 $
を頭文字だけ大文字化した文字列を返す。$
を省略した場合 $_
。reverse [$]
- 文字列式 $
を逆順にした文字列を返す。$
を省略した場合 $_
。引数がリストの場合はリスト関数の reverse
。substr $s, $i[, $n]
- 文字列式 $s
の部分文字列 [$i,$i+$n]
を返す。$i
は [0,]
。$n
を省略した場合、または $n>length($s)-$i
の場合、$n=length($s)-m
となる。substr $s, $i, $n, $r
- 同上だが、$s
の部分文字列は $r
で置換される。sprintf $format, @
- 書式 $format
に従ってリスト @
を出力した文字列を返す。hex [$]
- 16進文字列としての式 $
で表される値を返す。$
を省略した場合 $_
。oct [$]
- 8進文字列としての式 $
で表される値を返す。$
を省略した場合 $_
。index $s, $t[, $i]
- 文字列 $s
の部分文字列 $t
を位置 $i
から前に検索して位置 [$i,]
を返す。見つからなかった場合は -1
を返す。$i
を省略した場合 0
。rindex $s, $t[, $i]
- 文字列 $s
の部分文字列 $t
を位置 $i
から後に検索して位置 [$i,]
を返す。見つからなかった場合は -1
を返す。$i
を省略した場合 0
。pack $template, @
- $template
形式に従ってリスト $
からバイナリ文字列を生成する。q//
- 文字列の生成、変数展開なし。qq//
- 文字列の生成、変数展開あり。tr///
- 文字置換の生成、変数展開なし。y///
- 同上qr//
- 正規表現の生成、変数展開あり。m//
- 正規表現による検索の生成、変数展開あり。s///
- 正規表現による置換の生成、変数展開あり。quotemeta [$]
- 文字列式 $
内のすべての非英数字文字をバックスラッシュでエスケープしたものを返す。$
を省略した場合 $_
。pos [$]
- 対象となるスカラ文字列 $
に対する最後のグローバル検索 m//g
が終了した位置を返す。$
を省略した場合 $_
。split
- 説明済み。study [$]
- 対象となるスカラ文字列 $
に対する正規表現検索について予め学習しておく。$
を省略した場合 $_
。binmode handle
- ファイルハンドル handle
をテキストモードではなくバイナリモードで読み書きできるようにし、成功なら真、失敗なら undef
を返す。close [handle]
- ファイルハンドル handle
に関連付けられたファイルやパイプを閉じ、$.
をリセットする。handle
を省略した場合、カレントに選択されたファイルハンドル。die @
- リスト @
を標準エラーに表示して例外を発生させる。eof [handle]
- ファイルハンドル handle
がファイルの最後なら真を返す。handle
を省略した場合、最後に読み込みを行なったファイルハンドル。eof ()
- <>
演算子で読み込まれるファイルリスト @ARGV
や STDIN
のすべてのファイルの最後なら真を返す。fileno handle
- ファイルハンドル handle
のファイル記述子を返す。flock handle, op
- ファイルハンドル handle
を op
で flock(2) する。getc [handle]
- ファイルハンドル handle
に関連付けられたファイルやパイプから1文字読み込む。handle
を省略した場合、標準入力。print [handle [@]]
- ファイルハンドル handle
にリスト @
を表示。但し、$,
で区切られ $\
で終端するが、既定値はどちらも undef
。handle
を省略した場合、カレントに選択されたファイルハンドル。@
を省略した場合 $_
。printf [handle ] $format, @
- ファイルハンドル handle
に書式 $format
に従いリスト @
を表示。read handle, $, $length[, $offset]
- ファイルハンドル handle
からスカラ変数に $length
バイトを読み込み、読み込んだバイト数を返す。say [handle [@]]
- ファイルハンドル handle
にリスト @
を表示。但し、$,
で区切られ ^J
で終端するが、既定値は前者のみ undef
。handle
を省略した場合、カレントに選択されたファイルハンドル。@
を省略した場合 $_
。seek handle, $position, $whence
- ファイルハンドル handle
の位置を指定。$position
は正負のバイト数、$whence
は 0: SEEK_SET
, 1: SEEK_CUR
, 2: SEEK_END
のいずれか。select [handle]
- ファイルハンドル handle
をカレントに選択する。handle
を省略した場合、最後に読み込みを行なったファイルハンドル。tell [handle]
- ファイルハンドル handle
の位置を返す。handle
を省略した場合、最後に読み込みを行なったファイルハンドル。truncate handle|$, $length
- ファイルハンドル handle
もしくは式 $
で表されるファイルを $length
バイト数に切り詰める。warn @
- リスト @
を標準エラーに表示する。write [handle|$]
- ファイルハンドル handle
もしくは式 $
で表されるファイルハンドルにピクチャ書式で出力。format
- write
で使用されるピクチャ書式の宣言。defined [$]
- $
が undef
以外なら真を返す。$
を省略した場合 $_
。ref [$]
- $
がリファレンスなら空でない文字列を返す。$
を省略した場合 $_
。これ以外にも、ファイル検査、ファイル操作、スコープ、制御フロー、プロセス、モジュール、オブジェクト指向、ソケット、System V プロセス間通信、ユーザ・グループ、時刻関連などの標準の組み込み関数がサポートされる。
代表的な Unix コマンドに相当する Perl スクリプトを以下にあげる。
perl -pe ''
このように、cat と同じ perl
スクリプトは「空」となるが、「perl -ne
」でもよいし、「'print'
perl -ne
」でもよいし、「'print $_'
perl -e
」でもよいし、「'print while (<>)'
perl -e
」でもよい。
'while (<>) { print }'
perl -ne 'print if ($. == 1)'
このように、head -n 1 と同じ perl
スクリプトは以上のようになるが、「perl -ne
」の方が効率がよいだろう。
'if ($. == 1) { print; last }'
perl -ne 'print if (eof())'
このように、tail -n 1 と同じ perl
スクリプト以上のようになるが、Awk のように「END{ print }
」では動作せず、この場合「perl -ne
」となる。
'$b = $_; END{ print $b }'
perl -ne 'print if !($. > 8)'
このように、head -n 8 と同じ perl
スクリプトは以上のようになるが、「perl -ne
」の方が効率がよいだろう。
'if (!($. > 8)) { print } else { last }'
さて、`head -n 1`, `tail -n 1`, `head -n n` は以上のように大変簡単であるが、`tail -n n` は少し工夫が必要であり、以下のようになる。
perl -ne ' BEGIN{ $n = 8 } { $A[$.%$n] = $_ } END{ for ($i=0; $i<$n; $i++) { print $A[($.+$i+1)%$n] } }'
ここでは、配列にラウンドロビン的に行を格納していき、最後にそれらを出力している。
このように、tail -n 8 と同じ perl
スクリプトは以上のようになる。
perl -ne 'END{ print $. }'
このように、wc -l と同じ perl
スクリプトは以上のようになる。
sed
では大変面倒になる `wc -c` は perl
だと算術演算があるので簡単である。
perl -ne '{ $l += length() } END{ print $l . "\n" }'
このように、wc -c と同じ perl
スクリプトは以上のようになる。
sed
では大変面倒になる `wc -w` は perl
だと算術演算があるので簡単であるが、少々工夫が必要であり、以下のようになる。
perl -ne ' { $h = $_; while ($h =~ "[^\t\n ]+") { $w++; $h = ${^POSTMATCH} } } END{ print $w . "\n" }'
この '${^POSTMATCH}'
によるマッチの繰り返しは Perl では大域マッチによりさらに簡単に書けて、以下のようになる。
perl -ne ' { $w++ while (/[^\t\n ]+/g) } END{ print $w . "\n" }'
この方法は他に応用が効くのでこれでもよいのだが、置換の手続きが置換の数をカウントしてくれるので、この場合は以下の方が簡単である。
perl -ne ' { $w += (s/[^\t\n ]+//g) } END{ print $w . "\n" }'
このように、wc -w と同じ perl
スクリプトは以上のようになる。
'^$'
`
perl -ne 'print if (/^$/)'
このように、基本正規表現の grep '^$'
と同じ perl
スクリプトは以上のようになるが、Perl では基本正規表現はサポートされないので、他のパターンではPerl正規表現に書き直す必要がある。
'^$'
`
perl -ne 'print unless (/^$/)'
このように、マッチの否定、基本正規表現の grep -v '^$'
と同じ perl
スクリプトは以上のようになるが、Perl では基本正規表現はサポートされないので、他のパターンではPerl正規表現に書き直す必要がある。
'^.+'
`
perl -ne 'print if (/^.+/)'
このように、拡張正規表現の grep -E '^.+'
と同じ perl
スクリプトは以上のようになる。
'^.+'
`
perl -ne 'print unless (/^.+/)'
このように、拡張正規表現のマッチの否定、grep -E -v '^.+'
と同じ perl
スクリプトは以上のようになる。
':'
-f 1,6`perl -F':'
-ane'{ if (@F >= 6) { print $F[0], ":", $F[5], "\n" } else { print } }'
ここで "-F':'
" は "-a" オプションと共にフィールドセパレータを指定するものである。
このように、区切り ':'
のフィールド切り取り、cut -d ':'
-f 1,6 但しフィールド数不足の行はそのまま出力と同じ perl
スクリプトは以上のようになる。
':'
-f 1,6 -s`perl -F':'
-ane'if (@F >= 6) { print $F[0], ":", $F[5], "\n" }'
ここで "-F':'
" は "-a" オプションと共にフィールドセパレータを指定するものである。
このように、区切り ':'
のフィールド切り取り、cut -d ':'
-f 1,6 但しフィールド数不足の行は出力しないと同じ perl
スクリプトは以上のようになる。
この例、1行あたりの文字数(既定値は80)を越えたら改行を挿入する perl
スクリプトを示そう。まずは正規表現を使わない方法:
perl -nle ' BEGIN{ $w = 80 } { $h = $_; while (defined $h) { if (length($h) > $w) { print substr($h, 0, $w); $h = substr($h, $w) } else { print $h; $h = undef } } }'
もしくは、正規表現を使った方法:
perl -nle ' BEGIN{ $w = 80 } { $h = $_; while ($h !~ /^.{$w}$/ && $h =~ /^.{$w}/) { print $&; $h = $' } print $h }'
後者の方が少しだけ単純だ。
このように、fold -b と同じ perl
スクリプトは以上のようになる。
perl -ne 'BEGIN{ open(OUT, ">", "filename.out") } { print; print OUT } END{ close(OUT) }'
このように、標準入力を標準出力とファイルに書き出し、tee と同じ perl
スクリプトは以上のようになる。
'A-Za-z'
'N-ZA-Mn-za-m'
`
この例、ROT13(と呼ばれる暗号化と言うより難読化)は tr コマンドを使うと表題のように簡単に実現できる。そして、perl
には tr コマンドに似た 'y'
コマンドがあるので、以下のようになる。
perl -pe 'y/A-Za-z/N-ZA-Mn-za-m/'
このように、文字置換、tr 'A-Za-z'
'N-ZA-Mn-za-m'
と同じ perl
スクリプトは以上のようになる。
sed
ではいささか面倒になる `cat -n` は perl
だと極めて簡単である。
perl -ne '{ printf("%6d\t%s", $., $_) }'
このように、cat -n と同じ perl
スクリプトは以上のようになる。
sed
では大変面倒になる `cat -n` は perl
だと極めて簡単である。
perl -pe 'if (!/^$/) { $i++; $_ = sprintf("%6d\t%s", $i, $_) }'
このように、cat -b と同じ perl
スクリプトは以上のようになる。
ところで、これらは GNU sed や GNU Awk で示される好例となっている。
sed
ではいずれも 'N'
コマンドで入力行をパターンスペースの末尾に '\n'
に続いて追加し、正規表現にて '^\(.*\)\n\1$'
のように後方参照 '\1'
を利用していることにある。awk
においては、基本的には以下に示す例と同等である。
これらを perl
で実現すると、単純に文字列の比較と制御の組み合わせとなり、順に以下のようになる。
perl -ne 'if ($. == 1) { $h = $_; print } else { if ($h ne $_) { $h = $_; print } }'
perl -ne 'if ($. == 1) { $h = $_ } else { if ($h ne $_) { $h = $_; $d = 0 } else { print if (!$d); $d = !0 } }'
perl -ne 'if ($. == 1) { $h = $_ } else { if ($h ne $_) { print $h if (!$d); $h = $_; $d = 0 } else { $d = !0 } } END{ print $h if (!$d) }'
このように、uniq, uniq -d, uniq -u と同じ perl
スクリプトは以上のようになる。
このタブを複数の空白に置換するコマンドを perl
スクリプトで示そう。まずは正規表現を使った方法:
perl -nle ' BEGIN{ $\ = undef; $n = 8 if (!$n) } { $h = $_; while ($h =~ ("^([^\t]{0," . ($n-1) . "}\t|[^\t]{$n})")) { $u = $&; $h = ${^POSTMATCH}; $p = index($u, "\t"); $p = length($u) if ($p < 0); $u = substr($u, 0, $p); for ($i=0; $i<$n-$p; $i++) { $u = $u . " " } print $u } print $h, "\n" }'
もしくは、正規表現を使わない方法:
perl -nle ' BEGIN{ $\ = undef; $n = 8 if (!$n) } { $l = 0; for ($i=0; $i<length($_); $i++) { $c = substr($_, $i, 1); $d = ($c eq "\t") ? $n - ($l % $n) : 1; if ($c eq "\t") { $c = ""; for ($j=0; $j<$d; $j++) { $c = $c . " " } } print $c; $l += $d } print "\n" }'
両者の効率と柔軟性におけるメリット・デメリットを考えてみるのも興味深い。個人的には、前者の方が判り易くてよいと思うが、例えば、'\b'
が一文字戻るとみなす処理を加えるには後者の方が簡単である。
このように、expand と同じ perl
スクリプトは以上のようになる。
この複数の空白をタブに置換するコマンドを perl
スクリプトを示そう。まずは正規表現を使った方法:
perl -nle ' BEGIN{ $\ = undef; $n = 8 if (!$n) } { $h = $_; while ($h =~ ("^([^\t]{0," . ($n-1) . "}\t|[^\t]{$n})")) { $u = $&; $h = ${^POSTMATCH}; if ($h =~ /^ /) { if ($u =~ / {1,}$/) { $u = ${^PREMATCH} . "\t" } } else { if ($u =~ ("^([^\t]{" . ($n-1) . "})\t\$")) { $u = $1 . " " } if ($u =~ / {2,}$/) { $u = ${^PREMATCH} . "\t" } } print $u } print $h, "\n" }'
ちなみに、最初の 'if'
文は、完全に `unexpand -a` の挙動を再現するためのものである。
もしくは、一文字ずつ調べる方法:
perl -nle ' BEGIN{ $\ = undef; $n = 8 if (!$n) } { $buf = ""; $l = 0; for ($i=0; $i<length(); $i++) { $c = substr($_, $i, 1); $d = ($c eq "\t") ? $n - ($l % $n) : 1; $l += $d; $buf = $buf . $c; if ($l % $n == 0) { if (substr($_, $i+1, 1) eq " ") { if ($buf =~ / +$/) { $buf = ${^PREMATCH} . "\t" } } else { if ($buf =~ ("^([^\t]{" . ($n-1) . "})\t\$")) { $buf = $1 . " " } if ($buf =~ / +$/) { $buf = ${^PREMATCH} . "\t" } } print $buf; $buf = "" } } print $buf, "\n" }'
両者の効率と柔軟性におけるメリット・デメリットを考えてみるのも興味深い。個人的には、前者の方が判り易くてよいと思うが、例えば、'\b'
が一文字戻るとみなす処理を加えるには後者の方が簡単である。
このように、unexpand -a と同じ perl
スクリプトは以上のようになる。
行毎に文字列を反転する BSD rev(1) コマンド。実用したことはないが、sed
で実現するには秀逸な技法が必要であった。しかし、perl
では極めて単純に、文字列を逆順に取り出して出力すればよい。
perl -ne '{ chomp; for ($i=length()-1; $i>=0; $i--) { print substr($_, $i, 1) } print "\n" }'
このように、BSD rev(1) と同じ perl
スクリプトは以上のようになる。
最終行から先頭行まで逆順で出力する GNU tac コマンド、`tail -r` に同じ。これも実用したことはないが、GNU sed における、好ましくない実現例のように、perl
における好ましくない実装は以下の通りである。
perl -ne ' if ($. == 1) { $b = $_ } else { $b = $_ . $b } END{ print $b }'
変数に行を逆順に連結して最後にそれを出力するという処理なので、メモリが足りなくなるか、仮想メモリで非常に遅くなるだろう。しかし、Perl には 'tell'
, 'seek'
がある。より安全な実装は以下のようになる。
perl -e ' open(IN, "$ARGV[0]"); $p[$n++] = tell(IN); while (<IN>) { $p[$n++] = tell(IN) } for ($i=$n-2; $i>=0; $i--) { seek(IN, $p[$i], 0); $b = <IN>; print $b }'
ここではファイルを入力しながらすべての改行の次のファイルハンドルの位置を配列に覚えておき、改めてその配列の逆順にファイルハンドルを移動する処理を行なっている。ファイル全体をオンメモリで処理しないので、先の例よりは安全であるが、改行が膨大に存在する長大なファイルの場合、配列のためのメモリ不足になるかもしれない。よって、以下のような安全な実装も考えられる。
perl -e ' open(IN, "$ARGV[0]"); seek(IN, 0, 2); $p = tell(IN) - 2; while ($p >= 0) { seek(IN, $p--, 0); read(IN, $c, 1) if ($p >= 0); if ($p < 0 || $c eq "\n") { $b = <IN>; print $b } }'
ここでは始めにファイルハンドルの位置をファイルのの末尾に移動し、1バイト読み込みつつ、そのファイルハンドルの位置を1バイトずつ戻す処理を行なっている。ファイル全体をオンメモリで処理しないので、先の例よりは安全である。
この例、1行あたりの制御コードを考慮した文字数(既定値は80)を越えたら改行を挿入する perl
スクリプトを示そう。これはカウントが必要になるので sed
では困難な好例となっている。
perl -nle ' BEGIN{ $\ = undef; $n = 8 if (!$n); $w = 80 if (!$w) } { $l = 0; for ($i=0; $i<length(); $i++) { $c = substr($_, $i, 1); $d = ($c eq "\b") ? (($l > 0) ? -1 : 0) : ($c eq "\r") ? -$l : ($c eq "\t") ? $n - ($l % $n) : 1; if ($l+$d > $w) { print "\n"; $l = $d } else { $l += $d } print $c } print "\n" }'
このように、fold と同じ perl
スクリプトは以上のようになる。
この例、1行あたりの制御コードを考慮した文字数(既定値は80)を越えようとするブランクに変わり改行を挿入する perl
スクリプトを示そう。これもカウントが必要になるので sed
では困難な好例となっているだけでなく、perl
でもかなり複雑にならざるを得ない。
perl -nle ' BEGIN{ $n = 8 if (!$n); $w = 80 if (!$w) } sub increment { my ($l, $c) = @_; ($c eq "\b") ? (($l > 0) ? -1 : 0) : ($c eq "\r") ? -$l : ($c eq "\t") ? $n - ($l % $n) : 1 } { $buf = ""; $l = $len = 0; if ($_ eq "") { print ""; next } for ($i=0; $i<length(); $i++) { $c = substr($_, $i, 1); if ($l + increment($l, $c) > $w) { { $j = $len; while (--$j >= 0 && substr($buf, $j, 1) !~ "[\t-\r ]") {} $space = $j } if ($space != -1) { $space++; printf("%.*s\n", $space, $buf); $buf = substr($buf, $space, $len - $space); $len -= $space; $l = 0; for ($j=0; $j<$len; $j++) { $l += increment($l, substr($buf, $j, 1)) } } else { printf("%.*s\n", $len, $buf); $l = $len = 0 } } $l += increment($l, $c); $buf = substr($buf, 0, $len) . $c; $len++ } if ($len != 0) { printf("%.*s\n", $len, $buf) } }'
ここでユーザ定義関数を効果的に使用していることに注目したい。しかし、もっと簡単になるような気もする…
このように、fold -s と同じ perl
スクリプトは以上のようになる。
この例、ファイルの連続した印字可能な4文字以上の文字列を表示する perl
スクリプトを示す。この例では strings.pl
という実行権のついたファイルに記述するものとする。
#!/usr/bin/perl -l -n -s BEGIN{ $n = 4 if (!$n); if ($t) { if ($t eq "x") { $fmt = "%x %s\n" } elsif ($t eq "o") { $fmt = "%o %s\n" } else { $fmt = "%d %s\n" } } } if (!$t) { s/([\f[:print:]]{$n,})/print $1/ge; } else { printf $fmt, (tell() - (length() + 1) + $-[1]), $& while (/([\f[:print:]]{$n,})/g); }
この strings.pl
では、'-n=4'
, '-t=d|o|x'
オプションが指定でき、このように、オプション解析は Perl の '-s'
オプションに任せると柔軟な perl
スクリプトが書ける。
このように、strings -a と同じ perl
スクリプトは以上のようになる。
さて、ここまでは入力ファイル駆動型のプログラムばかりであったが、そうではなく自律型のプログラムを書くには、Perl では普通に '-n'
や '-p'
オプションを指定しなければよい。
この例、環境変数をすべて表示する perl
スクリプトを示す。
perl -l -e 'for (keys %ENV) { print "$_=$ENV{$_}" }'
連想配列のキーをすべて取り出すには 'keys %'
とする。
このように、printenv と同じ perl
スクリプトは以上のようになる。
この例、プロセス停止まで永遠に "yes^J" を印字し続ける perl
スクリプトを示す。但しこのコマンド、第一引数を指定した場合には "yes" の代わりにそれを印字する仕様となっている。この例では yes.pl
という実行権のついたファイルに記述するものとする。
#!/usr/bin/perl
$y = @ARGV ? $ARGV[0] : 'yes'
;
print $y . "\n" while (!0);
このように、yes と同じ perl
スクリプトは以上のようになる。
この例、二つのファイルのバイトの差異を返す perl
スクリプトを示す。バイナリファイルとしての扱いが可能な Perl での好例となる。
#!/usr/bin/perl -s $blksize = 4096; local($bc, $lc, $rv) = (0, 0, 0); exit 0 if ($ARGV[0] eq $ARGV[1]); open($f[0],'<'
, $ARGV[0]); binmode($f[0]); open($f[1],'<'
, $ARGV[1]); binmode($f[1]); do { local(@b); $rv[0] = read($f[0], $b[0], $blksize); $rv[1] = read($f[1], $b[1], $blksize); $sz = ($rv[0] < $rv[1]) ? $rv[0] : $rv[1]; if (!$l) { for (my $i=0; $i<$sz; ++$i) { $s[0] = substr($b[0], $i, 1); $s[1] = substr($b[1], $i, 1); ++$lc if ($s[0] eq "\n"); if ($s[0] ne $s[1]) { print $ARGV[0], " ", $ARGV[1], " differ: char ", $bc + $i + 1, ", line ", $lc + 1, "\n" if (!$s); $rv = 1; last; } } $bc += $sz; } else { for (my $i=0; $i<$sz; ++$i) { $s[0] = substr($b[0], $i, 1); $s[1] = substr($b[1], $i, 1); if ($s[0] ne $s[1]) { printf "%4d %3o %3o\n", $bc + $i + 1, ord($s[0]), ord($s[1]); $rv = 1; } } $bc += $sz; } if (!($rv[0] == $blksize && $rv[1] == $blksize)) { print STDERR "cmp: EOF on ", ($rv[0] < $rv[1]) ? $ARGV[0] : $ARGV[1], "\n"; $rv = 1; } } while ($rv[0] == $blksize && $rv[1] == $blksize); close($f[0]); close($f[1]); exit $rv;
このように cmp と同じ perl
スクリプトは以上のようになる。
この例、複数のファイルの内容を行毎に結合する perl
スクリプトを示す。但しこのコマンド、'-s'
オプションを指定した場合はファイル毎に改行を除去して結合する仕様となっており、特に難しくはない。この例では paste.pl
という実行権のついたファイルに記述するものとする。
Perl ではファイル操作関数が揃っているので、以下のように sed では困難な、複数のファイルの平行な入力に対応できる。
#!/usr/bin/perl -l -s sub getlinepos { local($f, $p) = @_; my($b) = (undef); return $b if ($p == -1); open(FH, $f); seek(FH, $p, 0) if ($p > 0); $b = <FH>; $p = (!eof(FH)) ? tell(FH) : -1; close(FH); ($b, $p); } $d = "\t" if (!$d); if (!$s) { $c = @ARGV; while ($c) { $S = undef; for ($a = 0; $a < @ARGV; $a++) { $S .= sprintf('%s'
, $d) if ($a != 0); next if (!defined $ARGV[$a]); ($b, $po[$a]) = getlinepos($ARGV[$a], $po[$a]); if (!defined $b) { $ARGV[$a] = undef; --$c; next; } chomp($b); $S .= sprintf('%s'
, $b); } print $S if ($c); } } else { while (<>) { $FNR = $. - $PREVIOUS_NR; chomp; printf '' . $\ if ($FNR == 1 && $PREVIOUS_NR > 0); printf'%s'
, $d if ($FNR != 1); printf'%s'
, $_; } continue { $PREVIOUS_NR = $. if eof; } if ($.) { printf '' . $\; } }
このように、paste と同じ perl
スクリプトは以上のようになる。
この例、複数のファイルの内容を行毎に比較する perl
スクリプトを示す。この例では comm.pl
という実行権のついたファイルに記述するものとする。
入力ファイルは、行でソートされていることが前提となっているので、複数のファイルをキーの文字列の大小で並行して読み進めていけば良い。
#!/usr/bin/perl sub getlinepos { local($f, $p) = @_; my($b) = (undef); return $b if ($p == -1); open(FH, $f); seek(FH, $p, 0) if ($p > 0); $b = <FH>; $p = (!eof(FH)) ? tell(FH) : -1; close(FH); ($b, $p); } while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-1'
) { $s0 = !0 } elsif ($ARGV[0] eq'-2'
) { $s1 = !0 } elsif ($ARGV[0] eq'-3'
) { $s2 = !0 } else { last } shift } $\ = "\n"; @cf = (-1)x@ARGV; $s[0] = $s0; $s[1] = $s1; $s[2] = $s2; for ($a=1; $a<=@ARGV; $a++) { for (my $i=0; $i<=$a; $i++) { $sc[$a]++ if ($s[$i]); } } $km = undef; $c = @ARGV; { $ceq = 0; while ($c) { for ($a = 0; $a < @ARGV; $a++) { $cf[$a] = -1; while (defined $ARGV[$a] && $cf[$a] == -1) { ($b, $po[$a]) = getlinepos($ARGV[$a], $po[$a]); if (!defined $b) { $ARGV[$a] = undef; --$c; if ($c == 0 && defined $km) { if ($ceq + 1 != @ARGV) { printf("%s%s\n", "\t"x($last_a-$sc[$last_a]), $km) if (!$s[$last_a]); } } next; } chomp($b); if (!defined $km) { $km = $b; $cf[$last_a = $a] = undef; $ceq = 0; } else { if ($km lt $b) { if ($ceq + 1 != @ARGV) { printf("%s%s\n", "\t"x($last_a-$sc[$last_a]), $km) if (!$s[$last_a]); } $km = $b; $cf[$last_a = $a] = 1; $ceq = 0; } elsif ($km eq $b) { if ($ceq + 1 != @ARGV) { printf("%s%s\n", "\t"x(@ARGV-$sc[@ARGV]), $b) if (!$s[@ARGV]); ++$ceq; } else { $ceq = 0; } $cf[$last_a = $a] = 0; } else { printf("%s%s\n", "\t"x($a-$sc[$a]), $b) if (!$s[$a]); $cf[a] = -1; $ceq = 0; } } } } } }
このように、comm と同じ perl
スクリプトは以上のようになる。
この例、複数のファイルの内容を行のキー毎に結合する perl
スクリプトを示す。この例では join.pl
という実行権のついたファイルに記述するものとする。
入力ファイルは、キーとなるフィールドでソートされていることが前提となっているので、複数のファイルをキーの文字列の大小で並行して読み進めていけば良い。とは言え、以下のように多少繁雑になるだろう。
#!/usr/bin/perl sub getlinepos { local($f, $p) = @_; my($b) = (undef); return $b if ($p == -1); open(FH, $f); seek(FH, $p, 0) if ($p > 0); $b = <FH>; $p = (!eof(FH)) ? tell(FH) : -1; close(FH); ($b, $p); } sub printout { my($n, $M, $o, $i); $o = undef; for ($i = 0; $i < @ARGV; $i++) { next if (!defined $va[$i]); $n = $i + 1; $M++; if (!defined $o) { $o = $va[$i]; } else { $o .= $, . $va[$i]; } $va[$i] = undef; } print $km, $o if ($o && ($M == @ARGV || (grep { $_ == $n } @na))); } @na = (); @nv = (); $t =' '
; $n1 = 1; $n2 = 1; while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-a'
&& 1 < @ARGV) { shift; push(@na, $ARGV[0]) } elsif ($ARGV[0] eq'-v'
&& 1 < @ARGV) { shift; push(@nv, $ARGV[0]) } elsif ($ARGV[0] eq'-t'
&& 1 < @ARGV) { shift; $t = $ARGV[0] } elsif ($ARGV[0] eq'-1'
&& 1 < @ARGV) { shift; $n1 = $ARGV[0] } elsif ($ARGV[0] eq'-2'
&& 1 < @ARGV) { shift; $n2 = $ARGV[0] } else { last } shift } $, = $t; $\ = "\n"; for ($a = 0; $a < @ARGV; $a++) { $cf[$a] = -1; $kn[$a] = 1; $va[$a] = undef; $po[$a] = 0; } $kn[0] = $n1; $kn[1] = $n2; $km = undef; $c = @ARGV; { while ($c) { for ($a = 0; $a < @ARGV; $a++) { $cf[$a] = -1; while (defined $ARGV[$a] && $cf[$a] == -1) { ($b, $po[$a]) = getlinepos($ARGV[$a], $po[$a]); if (!defined $b) { $ARGV[$a] = undef; --$c; &printout() if (!$c); next; } chomp($b); @f = split($t, $b); $k = $f[$kn[$a]-1]; $v = undef; for ($i = 0; $i < @f; $i++) { next if ($i+1 == $kn[$a]); if (!defined $v) { $v = $f[$i]; } else { $v .= $, . $f[$i]; } } if (!defined $km) { $km = $k; $va[$a] = $v; $cf[$last_a = $a] = 0; } else { if ($km lt $k) { if (grep { $_ == $last_a+1 } @nv) { print $km, $va[$last_a] if ($va[$last_a]); $va[$last_a] = undef; } print $k, $v if ((grep { $_ == $a+1 } @nv) && $last_a == $a); &printout() if (!@nv); $km = $k; $va[$a] = $v if (!@nv); $cf[$last_a = $a] = 1; } elsif ($km eq $k) { $va[$a] = $v if (!@nv); $cf[$last_a = $a] = 0; } else { print $k, $v if ((grep { $_ == $a+1 } @na) || (grep { $_ == $a+1 } @nv)); $va[$a] = undef; $cf[$a] = -1; } } } } } }
このように、join と同じ perl
スクリプトは以上のようになる。但し、正確には単一ファイル内の重複するキーにおける挙動には対応していない。
この例、単一のファイルの内容を行数で複数のファイルに分割する perl
スクリプトを示す。この例では split.pl
という実行権のついたファイルに記述するものとする。
Perl では、`split` コマンドにおける '-b'
オプションによるバイナリファイルとしての分割にも対応可能である。
#!/usr/bin/perl sub outputfilename_digit { local($nf) = @_; sprintf('%s%0*d%s'
, $pre, $n, $nf, $suf); } sub outputfilename_lower { local($nf) = @_; my($b, $d, $r, $q); $b = $d = 26; while (int($nf / $d)) { $d *= $b; } $d /= $b; $xxxxxx = ''; do { $r = int($nf / $d); $nf -= $d * $r; $xxxxxx = $xxxxxx . sprintf('%c'
, $r + 97); $d = int($d / $b); } while ($d); while (length($xxxxxx) < $n) { $xxxxxx = sprintf('%c'
, 0 + 97) . $xxxxxx; } sprintf('%s%s%s'
, $pre, $xxxxxx, $suf); } sub outputfilename { local($nf) = @_; ($d ? &outputfilename_digit($nf) : &outputfilename_lower($nf)); } %units = ('b'
=> 512, # blocks'KB'
=> 1000, # KiloBytes'K'
=> 1024, # KibiBytes'k'
=> 1024, # KibiBytes'MB'
=> 1000*1000, # MegaBytes'M'
=> 1024*1024, # MebiBytes'm'
=> 1024*1024, # MebiBytes'GB'
=> 1000*1000*1000, # GigaBytes'G'
=> 1024*1024*1024, # GibiBytes'g'
=> 1024*1024*1024, # GibiBytes ); $k = undef; $s = !0; $d = undef; $pre = undef; $suf = undef; $n = 2; $lc = 1000; $bc = 0; $f = undef; while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-k'
) { $k = !0 } elsif ($ARGV[0] eq'--verbose'
) { $s = undef } elsif ($ARGV[0] eq'-d'
) { $d = !0 } elsif ($ARGV[0] eq'-f'
&& 1 < @ARGV) { shift; $pre = $ARGV[0] } elsif ($ARGV[0] eq'-x'
&& 1 < @ARGV) { shift; $suf = $ARGV[0] } elsif ($ARGV[0] eq'-a'
&& 1 < @ARGV) { shift; $n = $ARGV[0] } elsif ($ARGV[0] eq'-l'
&& 1 < @ARGV) { shift; $lc = $ARGV[0] } elsif ($ARGV[0] eq'-b'
) { shift @ARGV; if ($ARGV[0] =~ /^(\d+)(KB|MB|GB|[KMGbkmg])?$/) { $bc = $1; $bc *= $units{$2} if ($2); } } else { if (!$f) { $f = $ARGV[0] } else { last } } shift } $pre = $ARGV[0] if (@ARGV); if (!$bc) { $nr = 0; open(of,'>'
, outputfilename($nf = 0)); open(f,'<'
, $f); while ($_ = <f>) { ++$nr; print of $_; if ($nr % $lc == 0) { print $l if (!$s); $l = 0; close(of); open(of,'>'
, outputfilename(++$nf)); } $l += length; } print $l if (!$s); } else { open(f,'<'
, $f); binmode(f); while (read(f, $_, $bc)) { close(of) if (!$nf); open(of,'>'
, outputfilename($nf++)); binmode(of); print of $_; } }
このように、split と同じ perl
スクリプトは以上のようになる。
この例、単一のファイルの内容を行のパターンや行番号で複数のファイルに分割する perl
スクリプトを示す。この例では csplit.pl
という実行権のついたファイルに記述するものとする。
Perl では、'seek'
等のファイル操作関数が揃っているので、なんら問題はない。
#!/usr/bin/perl sub outputfilename { local($nf) = @_; sprintf('%s%0*d%s'
, $pre, $n, $nf, $suf); } %p = %op = (); sub regexp_split { local($rep, $reg, $ofs, $stl) = @_; do { my($ol, $np, $c, $oc) = (0, -$ofs+1, 0, 0); open(of,'>'
, outputfilename($nf++)) if (!$stl); $p[$c++ % $np] = tell(f) if (!($ofs > 0)); while (<f>) { $op[$oc++ % $np] = $ol if (!($ofs > 0)); print of if (!$stl); last if ($ol && $_ =~ /$reg/ && ($ol += length)); $ol += length; $p[$c++ % $np] = tell(f) if (!($ofs > 0)); } if (!($ofs > 0)) { if ($_) { seek(f, $p[($c-1-($np-1)) % $np], 0); $. -= $np; $ol = $op[($oc-1-($np-1)) % $np]; truncate(of, $ol) if (!$stl); } } else { my $n = $ofs; while (--$n > 0 && ($_ = <f>)) { print of if (!$stl); $ol += length; } close(of) if (!$stl); } print $ol . "\n" if (!$s && !$stl); } while ($rep--); } sub lineno_split { local($rep, $ln) = @_; my($nl) = $ln; do { open(of,'>'
, outputfilename($nf++)); my $ol = 0; while ($. + 1 != $nl && ($_ = <f>)) { print of; $ol += length; } $nl += $ln; print $ol . "\n" if (!$s); close(of); } while ($rep--); } $k = undef; $s = undef; $d = undef; $pre = xx; $suf = undef; $n = 2; $f ='-'
; while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-k'
) { $k = !0 } elsif ($ARGV[0] eq'-s'
) { $s = !0 } elsif ($ARGV[0] eq'-d'
) { $d = !0 } elsif ($ARGV[0] eq'-f'
&& 1 < @ARGV) { shift; $pre = $ARGV[0] } elsif ($ARGV[0] eq'-x'
&& 1 < @ARGV) { shift; $suf = $ARGV[0] } elsif ($ARGV[0] eq'-n'
&& 1 < @ARGV) { shift; $n = $ARGV[0] } else { $f = $ARGV[0]; shift; last } shift } open(f, $f); $nf = 0; $ope = undef; while (defined($ope = shift @ARGV)) { $rep = 0; if ($ARGV[0] =~ /^{(\d+)}$/) { $rep = $1; shift @ARGV } if ($ope =~ /^\/(.*)\/([-+]?\d+)?$/) { regexp_split($rep, $1, ($#+ == 2) ? $2 : 0, 0) } elsif ($ope =~ /^\%(.*)\%([-+]?\d+)?$/) { regexp_split($rep, $1, ($#+ == 2) ? $2 : 0, !0) } elsif ($ope =~ /^(\d+)$/) { lineno_split($rep, $1) } } if (!eof(f)) { open(of,'>'
, outputfilename($nf++)); my $ol = 0; while (<f>) { print of; $ol += length; } print $ol . "\n" if (!$s); close(of); } close(f);
実は、この実装例は FreeBSD 由来の実装を多少無駄を省きつつ移植したものであり、負だけでなく零のオフセットのときも seek を必要とする、あまり美しくない実装例である。よって、負のオフセット以外は seek を必要としない制御構造に改めた実装例を以下に紹介する。
#!/usr/bin/perl sub outputfilename { local($nf) = @_; sprintf('%s%0*d%s'
, $pre, $n, $nf, $suf); } sub nextsplit { $previous_stl = $stl; ($ope, $rep, $stl, $ln) = (@ARGV ? shift @ARGV : undef, 0, 0, 0); shift @ARGV if (defined $ope && $ARGV[0] =~ /^{(\d+)}$/ && ($rep = $1)); if ($ope =~ /^[\/\%](.*)[\/\%]([-+]?\d+)?$/) { ($reg, $ofs) = ($1, ($#+ == 2) ? $2 : 0); ($np, $c, $oc) = (-$ofs+1, 0, 0); $stl = $ope =~ /^\%(.*)\%([-+]?\d+)?$/; $ope = 1; } elsif ($ope =~ /^(\d+)$/) { $ln = $1; $ope = 2; } else { $ope = 0; } } %p = %op = (); $k = undef; $s = undef; $d = undef; $pre = xx; $suf = undef; $n = 2; $f ='-'
; while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-k'
) { $k = !0 } elsif ($ARGV[0] eq'-s'
) { $s = !0 } elsif ($ARGV[0] eq'-d'
) { $d = !0 } elsif ($ARGV[0] eq'-f'
&& 1 < @ARGV) { shift; $pre = $ARGV[0] } elsif ($ARGV[0] eq'-x'
&& 1 < @ARGV) { shift; $suf = $ARGV[0] } elsif ($ARGV[0] eq'-n'
&& 1 < @ARGV) { shift; $n = $ARGV[0] } else { $f = $ARGV[0]; shift; last } shift } open(f, $f); $nf = 0; open(of,'>'
, outputfilename($nf++)); nextsplit; $p[$c++ % ($np+1)] = tell(f) if ($ope == 1 && $ofs < 0); $op[$oc++ % ($np+1)] = tell(of) if ($ope == 1 && $ofs < 0 && !$stl); while (<f>) { $p[$c++ % ($np+1)] = tell(f) if ($ofs < 0); if ($ope == 1) { if (/$reg/) { if (!($ofs < 0)) { for (my $i=0; $i<$ofs; $i++) { if (!$stl) { print of; $ol += length; } last if !($_ = <f>); } } if (!$stl) { if ($ofs < 0) { truncate(of, $op[($oc-1-($np-1)) % ($np+1)]); $ol -= tell(of) - $op[($oc-1-($np-1)) % ($np+1)]; } print $ol . "\n" if (!$s); close(of); open(of,'>'
, outputfilename($nf++)); $ol = 0; } if ($ofs < 0) { for (my $i=0; $i<$np-1; $i++) { seek(f, $p[($c-1-($np-$i)) % ($np+1)], 0); my $_ = <f>; print of; $ol += length; } $. -= $np-1; seek(f, $p[($c-1-($np-$np)) % ($np+1)], 0); if (!$stl) { open(tf,'>>'
, outputfilename($nf-2)); truncate(tf, $op[($oc-1-($np-1)) % ($np+1)]); close(tf); } } nextsplit if (!($rep--)); } } elsif ($ope == 2) { if ($. % $ln == 0) { print $ol . "\n" if (!$s); close(of); open(of,'>'
, outputfilename($nf++)); $ol = 0; nextsplit if (!($rep--)); } } if ($previous_stl || !$stl) { print of; $ol += length; } $op[$oc++ % ($np+1)] = tell(of) if ($ope == 1 && $ofs < 0 && !$stl); } print $ol . "\n" if (!$s); close(f); close(of);
このように、csplit と同じ perl
スクリプトは以上のようになる。
この例、GNU coreutils seq コマンドのように、単調増加する数列を生成する perl
スクリプト seq.pl
を示す。
#!/usr/bin/perl $s = "\n"; ($rep, $beg, $dlt, $end) = (undef, 1, 1, undef); while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-f'
&& 1 < @ARGV) { shift; $f = $ARGV[0] } elsif ($ARGV[0] eq'-s'
&& 1 < @ARGV) { shift; $s = $ARGV[0] } else { last } shift } if (@ARGV == 1) { $end = $ARGV[0]; } elsif (@ARGV == 2) { ($beg, $end) = ($ARGV[0], $ARGV[1]); } elsif (@ARGV == 3) { ($beg, $dlt, $end) = ($ARGV[0], $ARGV[1], $ARGV[2]); } else { die; } if (!defined $f) { $f = "%d"; $f = "%g" if ($beg =~ /\./ || $end =~ /\./); } $rep = ($end - $beg)/$dlt; printf $f, $beg if ($rep); for (1 .. $rep) { printf $s . $f, $dlt*$_ + $beg; } print "\n" if ($rep)
このように、GNU coreutils seq と同じ perl
スクリプトは以上のようになる。
この例、BSD jot コマンドのように、単調増減する数列、乱数列、定数列を生成する perl
スクリプト jot.pl
を示す。
#!/usr/bin/perl $s = "\n"; ($rep, $beg, $dlt, $end) = (undef, 1, 1, undef); while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-w'
&& 1 < @ARGV) { shift; $w = $ARGV[0] } elsif ($ARGV[0] eq'-c'
) { $f = "%c" } elsif ($ARGV[0] eq'-b'
&& 1 < @ARGV) { shift; $b = $ARGV[0] } elsif ($ARGV[0] eq'-s'
&& 1 < @ARGV) { shift; $s = $ARGV[0] } elsif ($ARGV[0] eq'-r'
) { $r = !0; $dlt = undef } else { last } shift } if (@ARGV == 1) { $rep = $ARGV[0]-1; } elsif (@ARGV == 2) { ($rep, $beg) = ($ARGV[0]-1, $ARGV[1]); } elsif (@ARGV == 3) { ($rep, $beg, $end) = (($ARGV[0] eq "-") ? undef : $ARGV[0]-1, ($ARGV[1] eq "-") ? undef : $ARGV[1], ($ARGV[2] eq "-") ? undef : $ARGV[2]); } elsif (@ARGV == 4) { ($rep, $beg, $end, $dlt) = (($ARGV[0] eq "-") ? undef : $ARGV[0]-1, ($ARGV[1] eq "-") ? undef : $ARGV[1], ($ARGV[2] eq "-") ? undef : $ARGV[2], ($ARGV[3] eq "-") ? undef : $ARGV[3]); } else { die; } if (!defined $f) { $f = "%d"; $f = "%g" if ($beg =~ /\./ || $end =~ /\./); } if ($w) { if ($w =~ /%/) { $f = $w; } else { $f = $w . $f; } } if ($beg =~ /^\D$/) { $beg = ord($beg) } if ($end =~ /^\D$/) { $end = ord($end) } if ($r) { srand($dlt); $dlt = undef; } if ($rep == -1) {} elsif (defined $rep) { $beg = $end - $dlt*$rep if (!defined $beg); $end = $beg + $dlt*$rep if (!defined $end); $dlt = ($end - $beg)/$rep; } else { $rep = ($end - $beg)/$dlt; } if ($b) { if ($rep == -1) { print $b . $s while (!0); } else { print $b if ($rep); print $s . $b for (1 .. $rep); print "\n" if ($rep); } } elsif ($r) { $dlt = ($end - $beg); if ($rep == -1) { $_ = 0; printf $f . $s, rand($dlt) + $beg while (!0); } else { printf $f, rand($dlt) + $beg if ($rep); printf $s . $f, rand($dlt) + $beg for (1 .. $rep); print "\n" if ($rep); } } else { if ($rep == -1) { $_ = 0; printf $f . $s, $dlt*$_++ + $beg while (!0); } else { printf $f, $beg if ($rep); printf $s . $f, $dlt*$_ + $beg for (1 .. $rep); print "\n" if ($rep); } }
オリジナルと微妙に既定の書式の扱いが異なるが、ほぼ等価な実装となっている。
このように、BSD jot と同じ perl
スクリプトは以上のようになる。
この例、GNU coreutils shuf コマンドはオプションの有無によって以下の機能を持つ。
'-r'
のとき無限回数または '-n times'
で指定回数で生成。'-r'
のとき無限回数または '-n times'
で指定回数で生成。'-r'
のとき無限回数または '-n times'
で指定回数で生成。
ここでは、shuf.pl
という実行権のついたファイルに記述するものとし、以下のように使用するものとする。
#!/usr/bin/perl -s use feature "say"; if (!defined $r) { if ($e) { srand(); while (@ARGV) { say splice(@ARGV, int(rand(@ARGV)), 1); } } elsif ($i) { ($lo, $hi) = (1, 6); ($lo, $hi) = ($1, $2) if ($i =~ /(\d+)-(\d+)/); !($lo > $hi) || die; @a = (); for (my $i=$lo; !($hi<$i); ++$i) { push(@a, $i); } srand(); while (@a) { say splice(@a, int(rand(@a)), 1); } } else { unshift(@ARGV, "-") unless (@ARGV); open($f, $ARGV[0]); @a = (); push(@a, tell($f)); while (<$f>) { push(@a, tell($f)); } srand(); while (@a) { seek($f, splice(@a, int(rand(@a)), 1), 0); $b = <$f>; print $b; } close($f) if ($f != STDIN); } } else { if ($e) { srand(); if (defined($n)) { for (1..$n) { say $ARGV[int(rand(@ARGV))]; } } else { while (!0) { say $ARGV[int(rand(@ARGV))]; } } } elsif ($i) { ($lo, $hi) = (1, 6); ($lo, $hi) = ($1, $2) if ($i =~ /(\d+)-(\d+)/); !($lo > $hi) || die; $a = int($hi - $lo + 1); srand(); if (defined($n)) { for (1..$n) { say int(rand($a)) + $lo; } } else { while (!0) { say int(rand($a)) + $lo; } } } else { unshift(@ARGV, "-") unless (@ARGV); open($f, $ARGV[0]); @a = (); push(@a, tell($f)); while (<$f>) { push(@a, tell($f)); } srand(); if (defined($n)) { for (1..$n) { seek($f, $a[int(rand(@a))], 0); $b = <$f>; print $b; } } else { while (!0) { seek($f, $a[int(rand(@a))], 0); $b = <$f>; print $b; } } close($f) if ($f != STDIN); } }
このように、shuf と同じ perl
スクリプトは以上のようになる。
POSIX sort コマンドは主に以下の3つの機能がある。
また、'-u'
オプションで重複する行の出力の抑止、'-r'
オプションで逆順、'-d|-f|-i'
オプションで順に、blank と alnum のみの比較、小文字を大文字と見なして比較、印字可能文字のみの比較でソートする。
さらに、'-k key'
オプションでソートのキーフィールド指定、'-t char'
オプションでフィールド区切りを指定できる。'-k key'
, '-t char'
オプションの実装は多少複雑になるので、行そのものをキーとするソートの実装を示す。
#!/usr/bin/perl $r = 1; while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-r'
) { $r = -1 } elsif ($ARGV[0] eq'-d'
) { $d = !0 } elsif ($ARGV[0] eq'-f'
) { $f = !0 } elsif ($ARGV[0] eq'-i'
) { $i = !0 } elsif ($ARGV[0] eq'-u'
) { $u = !0 } else { last } shift } sub normalize_line { my($b) = @_; chomp $$b; $$b =~ s/[^[:blank:][:alnum:]]//g if ($d); $$b = uc($$b) if ($f); $$b =~ s/[^[:print:]]//g if ($i); } sub compare_lines { my($a, $b) = @_; normalize_line(\$a); normalize_line(\$b); ($a cmp $b)*$r; } { unshift(@ARGV, "-") unless (@ARGV); open(FH, $ARGV[0]); my $b0 = <FH>; while (my $b = <FH>) { if ((!$u && compare_lines($b0, $b) == 1) || ($u && compare_lines($b0, $b) != -1)) { print STDERR "sort: $ARGV[0]:$.: disorder: $_\n"; exit 1; } $b0 = $b; } close(FH); }
行を読み込み、前の行と比較して 1 以外なら正常終了となる。'-u'
オプションのときは 0 つまり重複する行も異常終了となる。
#!/usr/bin/perl sub getlinepos { local($f, $p) = @_; my($b) = (undef); return $b if ($p == -1); open(FH, $f); seek(FH, $p, 0) if ($p > 0); $b = <FH>; $p = (!eof(FH)) ? tell(FH) : -1; close(FH); ($b, $p); } $OFH = STDOUT; $r = 1; while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-o'
&& 1 < @ARGV) { shift; open($OFH,'>'
, $ARGV[0]) } elsif ($ARGV[0] eq'-r'
) { $r = -1 } elsif ($ARGV[0] eq'-d'
) { $d = !0 } elsif ($ARGV[0] eq'-f'
) { $f = !0 } elsif ($ARGV[0] eq'-i'
) { $i = !0 } elsif ($ARGV[0] eq'-u'
) { $u = !0 } else { last } shift } sub normalize_line { my($b) = @_; chomp $$b; $$b =~ s/[^[:blank:][:alnum:]]//g if ($d); $$b = uc($$b) if ($f); $$b =~ s/[^[:print:]]//g if ($i); } sub compare_lines { my($a, $b) = @_; normalize_line(\$a); normalize_line(\$b); ($a cmp $b)*$r; } { my(@a, @p) = ((), ()); for (my $a = 0; $a < @ARGV; $a++) { push @a, $a; $p[$a] = 0; } my $b0; while (@a > 1) { @a = sort { (my $A, undef) = getlinepos($ARGV[$a], $p[$a]); (my $B, undef) = getlinepos($ARGV[$b], $p[$b]); compare_lines($A, $B); } @a; (my $b, $p[$a[0]]) = getlinepos($ARGV[$a[0]], $p[$a[0]]); print $OFH ($b0=$b) if (!$u || ($u && (!defined $b0 || compare_lines($b0, $b) != 0))); for (my $a = 0; $a < @a; $a++) { splice @a, $a, 1 if ($a < @a && $p[$a[$a]] == -1); } } while ($p[$a[0]] != -1) { (my $b, $p[$a[0]]) = getlinepos($ARGV[$a[0]], $p[$a[0]]); print $OFH ($b0=$b) if (!$u || ($u && (!defined $b0 || compare_lines($b0, $b) != 0))); } }
各ファイルはソート済みであることが前提としてあるので、すべてのファイルを並行して読み、それらの行を並び変えて先頭のファイルの行のみを出力して進めばよい。
#!/usr/bin/perl $OFH = STDOUT; $r = 1; while (@ARGV) { if ($ARGV[0] eq'--'
) { shift; last } elsif ($ARGV[0] eq'-o'
&& 1 < @ARGV) { shift; open($OFH,'>'
, $ARGV[0]) } elsif ($ARGV[0] eq'-r'
) { $r = -1 } elsif ($ARGV[0] eq'-d'
) { $d = !0 } elsif ($ARGV[0] eq'-f'
) { $f = !0 } elsif ($ARGV[0] eq'-i'
) { $i = !0 } elsif ($ARGV[0] eq'-u'
) { $u = !0 } else { last } shift } sub normalize_line { my($b) = @_; chomp $$b; $$b =~ s/[^[:blank:][:alnum:]]//g if ($d); $$b = uc($$b) if ($f); $$b =~ s/[^[:print:]]//g if ($i); } sub compare_lines { my($a, $b) = @_; normalize_line(\$a); normalize_line(\$b); ($a cmp $b)*$r; } my @file_position_list = (); sub file_position_getline { my($file_position) = @_; open(FH,'<'
, ${$$file_position{name}}); seek(FH, $$file_position{position}, 0); my $b = <FH>; close(FH); return $b; } { local $a = 0; while ($a < @ARGV) { open(FH,'<'
, $ARGV[$a]); do { push(@file_position_list, { name => \$ARGV[$a], position => tell(FH), }); } while (<FH>); close(FH); ++$a; } @file_position_list = sort { my $A = file_position_getline($a); my $B = file_position_getline($b); compare_lines($A, $B); } @file_position_list; my $b0; for my $file_position (@file_position_list) { my $b = file_position_getline($file_position); print $OFH ($b0=$b) if (!$u || ($u && (!defined $b0 || compare_lines($b0, $b) != 0))); } }
各ファイルの行頭のファイルポジションをファイル名とのペアで保持しておき、それを行の比較でソートすればよい。
このように、sort (但し、キーフィールド指定なし) と同じ perl
スクリプトは以上のようになる。