タブ区切りファイル編
ファイル変換編
その他のファイル操作編
その他の操作編
固定長ファイル編


仕事で使える(かもしれない)Perlコード (タブ区切りファイル編)
タブ区切りの2ファイルの各行各列の値を比較し、差異があれば出力する

($infile1, $infile2) = @ARGV;
$infile2 || die "usage: coldiff.pl file1 file2";

sub readFile {
    my($fileName) = @_; open(INFILE, $fileName) || die $!;
    my(@data) = <INFILE>; close(INFILE); return @data;
}

@data1 = &readFile($infile1);
@data2 = &readFile($infile2);

$lineno = 1;
foreach (@data1){
    $line1 = $_;
    chomp($line1);
    $line2 = shift(@data2);
    chomp($line2);
    exit if (! $line1) || (! $line2);
    @rowdata1 = split(/\t/, $line1);
    @rowdata2 = split(/\t/, $line2);
    $x = 1;
    foreach (@rowdata1){
        $data1 = $_;
        $data2 = shift(@rowdata2);
        if($data1 ne $data2){
            print "${lineno}行目\t${x}列目の値は違います:\t[$data1]\t[$data2]\n";
        }
        $x++;
    }
    $lineno++;
}

タブ区切りのテキストファイルを読み、行のnフィールド目が前の行と同じであれば、その行は省いて出力する

my($field) = shift(@ARGV) || die "usage: uniqfield.pl {fieldIndex} {fileNames}";
my($prev) = "aaa"; # 対象フィールドの値としてとりえない文字列を設定すること
while(<>){
    chomp; $line = $_; @arr = split;
    if($arr[$field] ne $prev){
        print $line . "\n";
        $prev = $arr[$field];
    }
}

コマンドラインで指定する$fieldは、0から開始です。

タブ区切りのデータを、HTMLのTABLE形式に変換する その1
これはWebアプリケーションの画面サンプルを作りたいときやなんかに使います。 そのままだと本当に簡単なので、 各行にチェックボックスや入力欄も追加するサンプルにしてみました。 なお、全体を囲むtableタグは省略しています。

while(<>){
    print "<tr>\n";
    @a = split(/\t/, $_);
    foreach $td (@a){
        print "<td>$td</td>";
    }
    print <<AAA;
<td><input type="checkbox"/></td><td><input type="text" size="12"/></td>
</tr>
AAA
}

タブ区切りのデータを、HTMLのTABLE形式に変換する その2
これも同じですが、行末の列に空フィールドが含まれていると、 chompしたり引数なしでsplitしたりすると消えてしまうので、 その防止策を入れています。 また、最初の行は見出し行(th要素)にする、 特定の列には右詰(text-align:right)のスタイルを入れる、 などの追加を行っています。

$first = 1;
while(<>){
    @a = split(/\t/);
    print "<tr>";
    $col = 0;
    foreach (@a){
        s/[\r\n]//g;
        if($first){
            print "<th>" . $_ . "</th>";
        } elsif($col == 4 || $col == 6 || $col == 7) {
            print "<td style='text-align:right'>" . $_ . "</td>";
        } else {
            print "<td>" . $_ . "</td>";
        }
        $col++;
    }
    print "</tr>\n";
    if($first){
        $first = 0;
    }
}


仕事で使える(かもしれない)Perlコード (ファイル変換編)
aaa.sh ⇒ AAA.sh 一括変換
カレントディレクトリにある.shファイルの主ファイル名を、全て大文字に変換します。

@files = <*.sh>;
foreach (@files){
    $newname = $oldname = $_;
    $newname =~ "(.+)\.sh";
    $mainname = $1;
    $mainname =~ tr/[a-z]/[A-Z]/;
    $newname = $mainname . ".sh";
    print "$oldname ==> $newname\n";
    rename($oldname, $newname) || die $!;
}

ファイル内の特定のパターンの変換
awkっぽい処理ですね。 テキストファイル内にある "AAA=bbb"という行を、"AAA=BBB" に置き換えて出力します。

perl -i.orig conv.pl *.sh
のようにすれば、複数のファイルを一括して変換できます。

while(<>){
    if ($_ =~ /AAA=(.+)/) {
        $a = $1;  $a =~ tr/a-z/A-Z/;  $_ =~ s/$1/$a/;
    }
    print;
}

TomcatのXMLファイルの変換
もう一つ似た例を。Tomcatのconf/Catalina/localhost/*.xml をサーバ移行に伴い一括変換する例です。
この例では、

  • docBase=".../.../..." の絶対パスを削除して docBase="..." だけにする
  • path="..."を削除する
  • DBのURLを決まった値に変更する
  • DBパスワードを"PASSWORD_RDS"に変更する
を一気に行っています。

#! /bin/perl
while(<>){
    s|docBase=".*/(\w+)"|docBase="$1"|;
    s|path=".*"||;
    s|C:/usr/.*stdb|rdsinst1.aaa.ap-northeast-1.rds.amazonaws.com|;
    s|password=".*"|password="PASSWORD_RDS"|;
    print;
}

Perlのワンライナーを/bin/shスクリプトのヒアドキュメントを使って複数行に書きたい
これ、できないと思っていたら次の方法で可能だと分かりました。

#! /bin/sh

/bin/perl -i.orig -p - ./application.properties << 'EOF'
if(/DBQ=/i){
  s|1521|1523|;
  s|=PI|=RDSDB1|ig;
  s|uzz.nsnhnkmmkk.co.jp|rdsinst1.aaa.ap-northeast-1.rds.amazonaws.com|;
  s|PASSWORD=.+|PASSWORD=PASSWORD_RDS|i;
}
EOF

注意点は-eを指定しないこと、引数の前に-を置くことです。
どこかの順番を間違えるとエラーになるので、注意が必要です。


仕事で使える(かもしれない)Perlコード (その他のファイル操作)
ファイルを最終編集時刻でソートして古い方から5つ表示する
こういう処理は2024年の今でもPerlが最も早く手短に書ける気がします。

use v5.10;
use Time::Piece;
my $count = 0;
foreach my $f (sort {(stat($a))[9] <=> (stat($b))[9]} (glob "/the/path/of/*.txt")){
    my $mtime = (stat($f))[9];
    my $lmtime = localtime($mtime);
    my $lmstr = $lmtime->strftime("%Y/%m/%d %H:%M:%S");
    say "${f}\t最終編集時刻=${lmstr}";
    if(++$count >= 5){ last; }
}

ある拡張子のファイルを数える
「Perlの覚え書きメモ」で触れた、「再帰的にディレクトリを検索するサンプル」 の拡張です。 コマンドラインで(基点ディレクトリ、拡張子)を受け取り、 基点ディレクトリ以下を再帰的に検索し、 拡張子を含むファイルの一覧と最後にファイル数を数えて出力します。

  • 拡張子は.をつけて指定する必要はありません。
  • 名前が合致すれば、ディレクトリやリンクも数える対象になります。

(my($basedir, $ext) = @ARGV) || die "parameter expected.\n";

sub showdir {
    my($indir, $ext) = @_;
    opendir(INDIR, $indir) || die "opening $indir:" . $!;
    my(@files) = readdir(INDIR);
    closedir(INDIR);

    foreach (@files){
        if($_ ne '.' && $_ ne '..'){
            $path = $indir . "/" . $_;

            if($path =~ /\.${ext}$/){
                print $path . "\n";
                $count++;
            }

            if(-d $path){
                & showdir($path, $ext);
            }
        }
    }
}

$count = 0;
& showdir($basedir, $ext);
print $count . "\n";

自分用階層的ls
似た例で、自分用「tree /F」コマンドと言いますか、再帰的lsでファイル名、サイズ、最終編集日時だけを表示するPerlスクリプトです。 スクリプトをいじれば、除外したいファイルやディレクトリ、表示したいファイル情報をカスタマイズできるので、 例えば複数個所のファイルリストを取得して、必要な情報だけで比較するような場合に使えると思います。

#! /bin/perl
#
# 再帰的にディレクトリ階層を辿り、ファイル名、サイズ、最終編集日時を表示する
#
# 2025/05/02 新規作成
#
use v5.10;
use strict;
use Time::Piece;

(my($basedir, $ext) = @ARGV) || die "parameter expected.\n";

& walksub($basedir);

sub walksub {
    my($indir) = @_;
    opendir(INDIR, $indir) || die $!;
    my(@files) = sort {$a cmp $b} readdir(INDIR);
    closedir(INDIR);

    foreach my $fileName (@files){
        next if($fileName eq '.' || $fileName eq '..');
        # 不要なディレクトリを除外
        next if($fileName eq '.git' || $fileName eq '.svn' ||
                $fileName eq 'logs' || $fileName eq 'work' || $fileName eq 'disabled');

        my $path = $indir . "/" . $fileName;

        if(-d $path){
            & walksub($path);
        } else {
            # 不要なファイルを除外
            next if($fileName =~ /\.bak$/i || $fileName =~ /\.orig$/i ||
                    $fileName =~ /\.ddd$/i || $fileName =~ /^ddd/i ||
                    $fileName =~ /\.log$/i || $fileName =~ /\d\d\d\d\d\d\d\d$/i);
            printFileInfo($path);
        }
    }
}

sub printFileInfo {
    my($path) = @_;

    my $size = (-s $path);  # ファイルのサイズ

    # ファイルの最終編集日時
    my $mtime = (stat($path))[9];
    my $lmtime = localtime($mtime);
    my $lmstr = $lmtime->strftime("%Y/%m/%d %H:%M:%S");

    # 絶対パスの先頭から起点ディレクトリ部分までを除外する。
    # その後ろに/もあれば除外する
    # \Qと\Eは正規表現を無視するオプションで、バックスラッシュや|などを正規表現として解釈しなくなる
    my $rpath = $path; $rpath =~ s/\Q${basedir}\E\/?//;

    say "${rpath}\t${size}\t${lmstr}";
}


仕事で使える(かもしれない)Perlコード (その他の操作)
「1.2.3」のような「本の章立て」みたいなデータを並び替える
「1.2.3」のような文字列をそのままASCIIコード順に並び替えると「1.10」が「1.1」と「1.2」の間に来てします。
これを正しく並び替える方法は色々考えられますが、一例として下の例では「.1」または「{行頭}1」 を「.01」と左0詰して2桁に揃えた後、元々の「10」が「010」になっているのを後ろ2桁に削り直しています。
なおこの例は「1.100.3」のような3桁以上の数字には非対応です。

use v5.10;
my @sections = qw(2.7  1.3  1.10.3  2.4.5  1.6.1  1.2.3);
sub mysort {
    # s///gは引数を直接上書き更新してしまうので、
    # 元の値を保持したいならローカル変数を使う
    my($a1, $b1) = ($a, $b);
    $a1 =~ s/(\.|\A)(\d)/.0\2/g;
    $a1 =~ s/\d(\d\d)/\1/g;
    $b1 =~ s/(\.|\A)(\d)/.0\2/g;
    $b1 =~ s/\d(\d\d)/\1/g;
    $a1 cmp $b1;
}
foreach (sort mysort(@sections)){ say; }

ヘッダと明細部のあるデータの整形
ヘッダ部と明細部を持つ帳票データをテキストで出力するとき、

ヘッダ部1 明細部1行目
ヘッダ部1 明細部2行目
ヘッダ部1 明細部3行目
ヘッダ部2 明細部1行目
...
概ねこういう風に出力して、帳票ソフトもヘッダ部のブレークキーを検出しながら明細部を繰り返し印刷できるようにしますが、 帳票ソフトの制限でそうできない場合?こうするしかないですね。
ヘッダ部1 明細部1行目 明細部2行目 明細部3行目
ヘッダ部2 明細部1行目 ...
こんな感じに整形するサンプルです。

use v5.10;
use strict;
@ARGV = ("indata.txt");

# 1頁に含まれる明細行の最大数。この数に達すると改ページします。
my $DETAILS_PER_PAGE = 6;
# 明細部の列数。行末を空要素で埋めるときに使います。
my $DETAIL_FIELD_NUM = 4;

# キー値の切り替わり(キーブレーク)の検知用
my $prev_key = "dummy";
# 出力バッファ。先頭がヘッダ部で、後ろに明細部を連結していきます。
my $buf = "";
# 現在バッファに付与した明細データの数
my $dcount = -1;
# ページ番号。ヘッダが変わらずに改ページするたび加算します。
my $page   = -1;

while(<>){
    chomp;
    my @rowdata = split(/\t/);

    # キーブレークはヘッダ部全体で比較してももちろんいいですが、
    # ここでは明示的にキー値列を抜き出して比較しています。
    my $key = $rowdata[3];
    my $head   = join("\t", @rowdata[0..4]);
    my $detail = join("\t", @rowdata[5..$#rowdata]);

    if($key eq $prev_key){  # キー値が前の行と同じだった
        $buf .= "\t" . $detail;
        if(++$dcount == $DETAILS_PER_PAGE){
            $buf .= "\t" . $page;
            say $buf;
            $page++;
            $dcount = 0;
            $buf = $head;
        }
    } else {  # キー値が前の行と違った(キーブレーク)
        if($dcount > 0){
            # 余った明細部分を空要素で詰める
            $buf .= "\t" x ($DETAIL_FIELD_NUM * ($DETAILS_PER_PAGE - $dcount));
            $buf .= "\t" . $page;  # 末尾にページ番号のフィールドを付与
            say $buf;
        }
        $buf = $head . "\t" . $detail;
        $page = 1;
        $dcount = 1;
        $prev_key = $key;
    }
}
if($dcount > 0){
    # 余った明細部分を空要素で詰める
    $buf .= "\t" x ($DETAIL_FIELD_NUM * ($DETAILS_PER_PAGE - $dcount));
    $buf .= "\t" . $page;  # 末尾にページ番号のフィールドを付与
    say $buf;
}


仕事で使える(かもしれない)Perlコード (固定長ファイル編)
長年このファイルの先頭に書いていましたが、今時固定長ファイルを使う機会は (さすがに仕事とはいえでも)ほぼ亡くなりましたので、末尾に回しました。悲しい〜…こともないです。

固定長ファイルのxバイト目からnバイトでソートする

(my($x, $n) = @ARGV) || die "parameter expected.";
sub by_char {
    $a1 = substr($a, $x, $n); $b1 = substr($b, $x, $n); return $a1 cmp $b1;
}
@lines = <STDIN>;
@sorted = sort by_char @lines;
foreach (@sorted){ print; }

固定長ファイルのxバイト目からnバイトがtargetに等しい行を出力する

(my($x, $n, $target) = @ARGV) || die "parameter expected.";
while(<STDIN>){ if(substr($_, $x, $n) eq $target){ print; } }

固定長ファイルのxバイト目からnバイトの値と出現回数を集計する

(my($x, $n) = @ARGV) || die "parameter expected.";
while(<STDIN>){
    $a = substr($_, $x, $n); $arr{$a}++;
}
foreach (keys(%arr)){
    print $_ . "\t" . $arr{$_} . "\n";
}

固定長ファイルの各行の先頭n文字を固定の文字に置き換える
これは簡単なので「ワンライナー」でやってみましょう。 下の例では、固定長っぽいテキストファイルの全ての行の先頭3文字を、 「ABC」に置き換えてfixed.datに上書きします。 元のファイルは.BAKをつけてfixed.dat.BAKに退避されます。
なお、ワンライナーでは、文末にセミコロンは不要です。

perl -i.BAK -pe "$_ = 'ABC' . substr($_, 3)" fixed.dat

ファイルの各行を1列目のASCII順でソートする
最も原始的な例です。いろいろアレンジできるかも。

@lines = <>;
@sorted = sort {$a cmp $b;} @lines;
foreach (@sorted){
    print;
}

Misc. Topics Top

(first uploaded 2005/10/09 last updated 2025/05/02, URANO398)