ablog

不器用で落着きのない技術者のメモ

Perlワンライナー集

障害対応でのログ解析、ソースコードの調査といったテキスト処理で使った Perl ワンライナー集です。
マルチライナーやいけてないのもありますw


Perl ワンライナーの好きなところ

  • PerlOracle Database (10g以降) に同梱されているので、Windows プラットフォームでも使える*1
  • awksedgrep正規表現の書き方などをそれぞれ覚えれない。awksedgrep でできることはだいたい Perl でできるので、Perl に絞ると覚えることを減らせる*2
  • 最小限の労力で最大限の仕事ができる。ちょっとしたプログラムを書くような処理でも Perl ワンライナーを使うとたった1行で済むことがあります*3

Perlワンライナー

一部、Perl 以外に bash、find、xargs なども含んでいます。

  • レコードセパレータを変更する
perl -wple ...       # 行モード(デフォルト)
perl -00 -wle ...    # 段落モード(1つ以上の空行をレコードセパレータと認識する)
perl -0777 -wle ...  # ファイルモード(ファイル全体を1レコードとして認識する)
  • カスタムフィールドセパレータを使う(改行コードをフィールドセパレータとする)
perl -00 -F'\n' -lane 'print $F[1] if($F[0] =~ /neo/)' hoge.txt
  • テキストファイルの特定の行だけ表示する
perl -lne 'print if $.<2' file  # 1行目だけ表示する
perl -pe 'exit if $. > 10' file # 10行目まで表示する
perl -ne 'print if 2.. 5' file  # 2行目から5行目まで表示する
  • CSV の列数をカウントする
perl -F, -lane 'printf("%s:%d\n",$ARGV,$#F);$.>0 and close ARGV' *.csv
  • CSV の任意の列のみ抽出する(1列目、3列目、11列目以外を抽出する)
perl -F, -lane 'print join(",",@F[1,3..9,11..$#F])' *.csv
  • ファイル中の空行を削除する
perl -i.org -ne '/^\s*$/ or print' test.sql
  • ダブルクオートをシングルクオートに置換する
perl -ple 's/\"/\'/g' hoge.csv
  • 「ORA-」メッセージをエラー番号別に集計する
perl -nle 'BEGIN{%h=();}/(ORA-[0-9]+)/ and $h{$1}++;END{map{print qq/$_:$h{$_}/} keys %h;}' alert_orcl.log
  • Java アプリケーションのログから発生した Exception の回数を集計する
perl -wnle 'BEGIN{%h=();}/([a-zA-Z]*Exception)/ and $h{$1}++;END{map{print "$_:$h{$_}"} sort keys %h;}' *.log
  • リスナーログから接続元ホスト毎に接続回数を算出する
perl -nle 'if(/(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}).*CONNECT_DATA.*HOST=([\w\-\.]+).*HOST=([\w\-\.]+)/i){$h->{qq/$2,$3/}->{count}++;$h->{qq/$2,$3/}->{date}=$1};END{map{print qq/$_,$h->{$_}->{count},$h->{$_}->{date}/} keys %$h}' listener.log
  • 再帰的にファイル名に接頭辞をつける
perl -MFile::Find -e 'find sub{rename($_,"prefix_$_") if -f}, @ARGV' .
  • テキストファイル中の最長行の文字数を求める
perl -nle '$a=length if($a<length);END{print $a}' orcl_ora_879.trc 
perl -MList::Util=max -lne 'push(@a,length);END{print max(@a)}' orcl_ora_879.trc 
perl -0777 -MEncode::Guess -wne '$e=guess_encoding($_,qw/euc-jp shiftjis 7bit-jis/);print "$ARGV:".$e->name."\n" if(ref($e))' **/*
  • 任意のディレクトリ配下にどのような拡張子のファイルがどれだけあるか集計する
perl -MFile::Find -MFile::Basename -e 'find sub{$h{(fileparse($_,qw{\.[^\.]+$}))[2]}++ if -f},@ARGV;END{map{print "$_:$h{$_}\n"}keys %h}' .
  • 2つ以上の空白文字は1つにするがシングルクオートで囲まれたリテラルは無視する
perl -pe 's/\G((\x27[^\x27]*\x27[^\x27]*?)*[^\x27]*?)[ ]+/$1 /g' foo.c
  • レコードセパレータが1つ以上の空行で、レコード内に改行文字を1つ含むデータを1行1レコードに変換する
perl -i.org -00 -pe 's/^(.*)\n(.*)\n+$/$1 $2\n/' aaa.txt
  • 1ファイル中に「DECODE」と「DISTINCTまたはUNIQUE」を含みかつ「ORDER BY」を含まないファイルをリストアップする
find . -type f -print0|xargs -0 perl -0777 -nle '/decode/i and /distinct|unique/i and !/order\s*by/i and print qq/$ARGV:$./;eof and close ARGV'
find . -type f -name '*.sh' -print0|xargs -0 perl -i.org -0777 -pe 's/(\s*#![\w\s\/]+\/bash)/$1\nexport LANG=C\n/'
  • ASCIIコードを文字に変換する
perl -e 'map{print chr($_)} @ARGV' 112 101 114 108  # ASCIIコードが10進数の場合
perl -e 'map{print chr(hex($_))} @ARGV' 70 65 72 6C # ASCIIコードが16進数の場合
  • メモリを大量消費する
perl -e 'while(1){$i++;$h{$i}=$i}' 
  • CPU使用率を 100% にする
perl -e 'while(1) {}'
  • CPU使用率を 100% にする(4コアを使う)
for i in {1..4}
do
perl -e 'while(1){}' &
done
  • iostat の出力結果を特定の列でソートするこの例では、デバイスsdk だけに絞って、(11列目) でソートしています。
perl -lane '/^sdk/ and push(@tmp,[@F]);END{map{print join(qq/ /,@{$_})}sort{$a-[11]<=>$b->[11]}@tmp}' iostat.log
  • V$SYSSTAT から特定のデータベース統計情報の差分を出す
perl -F, -lane '/global cache blocks lost/ and printf(qq/%s,%s\n/,$F[0],$F[2]-$tmp) and $tmp=$F[2]' sysstat.log
  • V$SYSSTAT から特定の統計に絞らずにデータベース統計情報の差分を出す
perl -F, -lane 'printf(qq/%s,%s,%s\n/,$F[1],$F[0],$F[2]-$h{$F[1]})if(exists($h{$F[1]}));$h{$F[1]}=$F[2]' sysstat.log
  • vmstat の出力結果から時間帯毎のCPU使用率を算出する
perl -lane '$.>2 and @t=split(q/:/,$F[1]) and $h->{$t[0]}->{sum}+=$F[21]+$F[22] and $h->{$t[0]}->{cnt}++;END{map{printf(qq/%02d\t%.1f\n/,$_,$h->{$_}->{sum}/$h->{$_}->{cnt})}sort keys %$h}' vmstat.log
  • top の行頭に時刻を追加
perl -ne '/^top - (\d\d:\d\d:\d\d)/ and $t=$1;print qq/$t $_/' top.log > top_time.log
  • 2行にまたがっている iostat のログを1行にする
perl -pe '/sssn[0-9]+s:/ and chop' iostat.txt > iostat_tmp.txt
perl -pe 's/(sssn[0-9]+s:[\/\w]+)iostat\s[0-9\/]{10}\s[0-9:]{8}/$1/' iostat_tmp.txt > iostat_mod.txt
perl -i -MEncode -pe 'Encode::from_to($_,"utf8","shiftjis");' *.txt"
  • ファイル名一括置換
perl -0777 -ne '$o=$ARGV;$ARGV=~s/\s/-/g;rename($o,$ARGV);' *.jpg
  • 16進数から10進数に変換
perl -e 'map{print hex($_)} @ARGV' 4a98
  • 10進数から16進数に変換
perl -e 'printf qq/%X/, 19096'
  • 中途半端にCPU使用率を上げる
perl -MTime::HiRes -e 'while(1){for(1..10000){};Time::HiRes::sleep(0.0001)}'
  • strace をグラフ化するために加工する
perl -lane '$F[2]=~s/^([a-z]+).*/$1/; $h->{$F[1]}->{$F[2]}++;END{map{$t=$_ and print $t;map{print qq/$t\t$_\t$h->{$t}->{$_}/} keys %{$h->{$_}}} keys %$h}'' strace.log
  • top を時系列・PID別にグラフ化するために加工する
grep -hA 50 '^top -' TopExaWatcher_db01.txt|perl -lane '/top - ([\d:]+)/ and $t=$1;$F[0]=~/\d+/ and printf (qq/%s %s%s\n/,$t,$_,lc(s
printf(q/%X/,$F[0])))' > TopExaWatcher_db01_ts_50.txt
  • 素のASHをCSVなどデリミタ区切りに変換する
perl -ane '/^-+/ and map {$l=length($_);$l++;print qq/a$l/} @F' ash.lst
a11a11a76a2a11a16a11a11a11a14a2a17a11a25a14a21a65a20a17a31a31a12a9a22a26a16a20a15a14a19a11a65a11a11a11a65a11a65a11a65a11a65a14a11a8a12a12a17a25a17a2a13a14a15a13a16a65a18a17a17a11a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a13a49a65a65a65a65a11a65a17a22a14a18a17a11a23a24a20a21a28a14a21a1001
perl -nle '@s=unpack(q/a11a11a76a2a11a16a11a11a11a14a2a17a11a25a14a21a65a20a17a31a31a12a9a22a26a16a20a15a14a19a11a65a11a11a11a65a11a65a11a65a11a65a14a11a8a12a12a17a25a17a2a13a14a15a13a16a65a18a17a17a11a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a13a49a65a65a65a65a11a65a17a22a14a18a17a11a23a24a20a21a28a14a21a1001/, $_);@t=map{$_=~s/^ *(.*?) *$/$1/;$_} @s;print join(q/|/, @t)' ash.lst > ash_mod.txt 
$ perl -MTime::Piece -e 'printf(qq/%08d %08d %08d %08d %s\n/,$_,2..4,$t=localtime->datetime) for 1..50000000'
00000001 00000002 00000003 00000004 2015-07-28T14:23:18
00000002 00000002 00000003 00000004 2015-07-28T14:23:18
00000003 00000002 00000003 00000004 2015-07-28T14:23:18
perl -lane '($s)=$F[1]=~/^(^\w+).*/;($t)=$F[$#F]=~/<([0-9\.]+)>/;print qq/$F[0]\t$t\t$s\t@F[1..$#F-1]/' strace.13859 > strace.13859.tsv
  • NULL文字除去
perl -pe 's/\x00//g' foo.log
  • ExaWatcher の iostat のログ行頭にタイムスタンプを追加する
find -name '*.dat' -print0|xargs -0 -n1 perl -ne '/^(\d+\/\d+\/\d+ \d+:\d+:\d+)/ and $t=$1;print qq/$t $_/' > iostat.log
  • さらに特定の時間帯だけ抽出する
perl -nle '/^05\/23\/\d+ 1[1-6]/ and print' iostat.log > iostat_0523_11-16.log
  • tkprof のプリプロセッサ Perl ワンライナー(13080594 の WA)
    • tkprof で整形すると SQL_ID と PLAN_HASH_VALUE が抜けるケースがあるけど、これで生トレースを加工してから tkprof で整形するとOK
    • SELECT で始まるSQLしか対応していないw
perl -nle 'BEGIN{$f=0};/^SELECT/ and $f=1;/^END OF STMT/ and $f=2;if($f==1){$s.=qq/$_ /}elsif($f==2){print qq/$s\nEND OF STMT/;$s=q//;$f=0}else{print}' foo.trc > foo_oneline.trc
  • cat の際に行頭にファイル名の一部(日付など)を挿入する
perl -nle '$ARGV=~s/.*(\d{2}n\d{1})_(\d{8})\.log$/$1 $2/;print qq/$ARGV $_/' *.log
  • 複数の sar -u のログをマージする際にファイル名のホスト名と日付を行頭に挿入する(ログローテートが 9:40 の場合)
perl -lane '$ARGV=~s/.*(\d{2}n\d{1})_(\d{8})\.log$/$1 $2/;($h,$d)=split(q/ /,$ARGV);$F[0]=~/^(0[0-8]:\d{2}:\d{2}|09:[0-3][0-9]:\d{2})/ and $d++; print qq/$h $d $_/' *.log
  • ログの一部を切り出す
perl -lane '/^1006092509/ .. /^1006092609/ and print' iostat.log
  • 改行コードを置換する
find . -type f -name '*.trc' -print0|xargs -0 -n1 perl -i.org -pe 's/\r\n/\n/g'
  • SQLトレース加工
# 生トレースのSQL文を一行に変換
ls *trc|while read LINE
do
perl -nle 'BEGIN{$f=0};/^\*\*\* ([\d\s-:.]+)/ and $t=$1;/^\s*(SELECT|INSERT|UPDATE|DELETE|MERGE|\-\-)/i and $f=1;/^END OF STMT/ and $f=2;if($f==1){$s.=qq/$_ /}elsif($f==2){print qq/$t|$s\nEND OF STMT/;$s=q//;$f=0}else{print}' $LINE > $LINE.ol
done

# tkprof で整形
ls *.ol|while read LINE
do
tkprof ${LINE} ${LINE}.tkprof aggregate=no
done

# サマリ抽出
ls *.tkprof|xargs -n1 perl -lane '/^(\d{4}-\d{2}-\d{2} [\d:\.]+)/ and $t=$1;/^OVERALL TOTALS/ and close;/(SQL ID:.*)/ and $s=$1;/^Execute/ and $x=$F[1];/^total/ and print qq/$ARGV $t $s $x $_/' > summary.txt

# 実行時間帯抽出
perl -nle '/^\*\*\* ([\d\s-:.]+)/ and $t=$1;/sqlid=\x27(\w+)\x27/ and print qq/$ARGV $t $1/' *.trc
FOO=bar; perl -sle 'print $var' -- -var=$FOO
  • iostat の1回目の出力は捨てて、デバイス名と avgqu-sz のみ出力する
iostat -dx 1|perl -lane '/^Device:/ and $c++;print qq/em_result=$F[0] $F[8]/ if($c>=2 and !/^(Device:|Linux|$)/)'
  • 接続先ごとにestablishされたクライアントからのアクセス数を日付は関係なくカウントする(discus_hamburg さん作)
$ perl -nle 'if(/(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}).*CONNECT_DATA.*HOST=([\w\-\.]+).*HOST=([\w\-\.]+).* establish *. (.+) . .*/i){$h->{qq/$2,$3,$4/}->{count}++;$h->{qq/$2,$3,$4/}->{date}=$1};END{map{print qq/$_,$h->{$_}->{count}/} keys %$h}' listener.log
lampeyes.macdeoracle,127.0.0.1,orcl,46
lampeyes.macdeoracle,127.0.0.1,orcl12c,12
__jdbc__,127.0.0.1,orcl,85
__jdbc__,192.168.56.101,orcl,3
find . -name '*.txt' -print0|xargs -0 -n1 perl -lane 'if($F[6]!=q/404/ or $F[0]!~/^[0-9\-]+/ or $F[5]!~/\.js$/){next};@l=split(q/:/,$F[1]);$h->{qq/$ARGV $F[0] $l[0] $F[5] $F[6]/}++; END{map{print qq/$_ $h->{$_}/} keys %$h}'
  • LMONトレースからDRM所要時間、READ MOSTLY LOCK 解除時間をEXCELでグラフ化するための加工
    • 前処理
perl -lane '/^\*\*\*([0-9\-:\.\s]+)/ and $t=$1;print qq/$t $_/' orcl1_lmon_4287.trc > orcl1_lmon_4287_ts.trc
perl -nle '/Begin DRM|READMOSTLY object id .* dissolve affinity from instance|drm freeze complete|End DRM/ and print' orcl1_lmon_4287_ts.trc > orcl1_lmon_4287_b-e.trc
grep -A 1 'dissolve affinity from instance' orcl1_lmon_4287_b-e.trc > orcl1_lmon_4287_mostly.trc
perl -nle '/Begin DRM|End DRM/ and print' orcl1_lmon_4287_ts.trc > orcl1_lmon_4287_drm.trc
    • DRM所要時間
perl -lane '/Begin DRM\((\d+)\)/ and $b=$1 and $h->{$b}->{begin}=qq/@F[0..1]/;/End DRM\((\d+)\)/ and $e=$1 and $h->{$e}->{end}=qq/@F[0..1]/;/\d+  object id ([\d\.]+)/ and push(@{$h->{$b}->{drm}},$1);END{map{$k=$_; map{print qq/$k%$h->{$k}->{begin}%$h->{$k}->{end}%$_/} @{$h->{$_}->{drm}} } keys %$h}' orcl1_lmon_4287_ts.trc > orcl1_lmon_4287_drm.txt
    • READ MOSTLY LOCK 解除時間
perl -lane '/Begin DRM\((\d+)\)/ and $b=$1 and $h->{$b}->{begin}=qq/@F[0..1]/;/End DRM\((\d+)\)/ and $e=$1 and $h->{$e}->{end}=qq/@F[0..1]/;/  READMOSTLY object id ([\d\.]+)/ and push(@{$h->{$b}->{mostly}},$1);END{map{$k=$_; map{print qq/$k%$h->{$k}->{begin}%$h->{$k}->{end}%$_/} @{$h->{$_}->{mostly}} } keys %$h}' orcl1_lmon_4287_ts.trc > orcl1_lmon_4287_mostly.txt
  • CSV にダブルクオートで囲まれてない列があれば囲う
perl -pe 's/,([^"])/,"$1/g;s/([^"]),/$1",/g' test.csv
  • Statspack レポートの特定のセクションを CSV 化する(デリミタは"%"にしている)
grep -A 8 'Top 5 Timed Events' *.txt|perl -nle '($f,$d)=$_=~/^(\w+)\.txt-(.+)$/;@t=split(q/_/,$f);@s=unpack(q/a41a13a12a9a7/,$d);map{printf(qq/%s\%/, $_)} @t;print join(q/%/,@s)' 
grep -A 4 'Host CPU' *.txt|perl -nle '($f,$d)=$_=~/^(\w+)\.txt-(.+)$/;@t=split(q/_/,$f);@s=unpack(q/a7a8a10a8a8a8/,$d);map{printf(qq/%s\%/, $_)} @t;print join(q/%/,@s)' 
grep -A 3 'Virtual Memory Paging' *.txt|perl -nle '($f,$d)=$_=~/^(\w+)\.txt-(.+)$/;@t=split(q/_/,$f);@s=unpack(q/a42a16/,$d);map{printf(qq/%s\%/, $_)} @t;print join(q/%/,@s)' 
grep -A 9 'Instance CPU' *.txt|perl -nle '($f,$d)=$_=~/^(\w+)\.txt-(.+)$/;@t=split(q/_/,$f);@s=unpack(q/a42a10a15/,$d);map{printf(qq/%s\%/, $_)} @t;print join(q/%/,@s)' 
grep -A 19 'Time Model System Stats' *.txt|perl -nle '($f,$d)=$_=~/^(\w+)\.txt-(.+)$/;@t=split(q/_/,$f);@s=unpack(q/a36a21a9/,$d);map{printf(qq/%s\%/, $_)} @t;print join(q/%/,@s)' 
grep -A 17 'Load Profile' *.txt|perl -nle '($f,$d)=$_=~/^(\w+)\.txt-(.+)$/;@t=split(q/_/,$f);@s=unpack(q/a17a20a18a12a11/,$d);map{printf(qq/%s\%/, $_)} @t;print join(q/%/,@s)' 
 perl -nle '/^WAIT.*nam=\x27(.*)\x27 ela= (\d+).*tim=(\d+)/ and print qq/$3|$1|$2/' +ASM2_ora_1896.trc
  • ログから特定期間のみ抽出する
perl -nle '/23:36:15/ ... /23:36:25/ and print' foo.log
  • CloudWatch Logs のログのJSONの「u'」を「'」に変換する。「\x27」はシングルクオートをエスケープするためにアスキーコードで表現している。
perl -pe 's/u(\x27)/$1/g' foo.json|
  • DBA_HIST_ACTIVE_SESS_HISTORY から空白を除去する。
$ perl -i.org -pe 's/¥s{2,}//g' dash_2016-11-30_154240.csv
  • タイムスタンプ書式をドイツ式から米式に変換する。
perl -i.org -pe 's/(\d{2}-\w{3}-\d{2}) (\d{2})\.(\d{2})\.(\d{2})\.(\d{6}) \w{2}/$1 $2:$3:$4.$5/g' *.csv
  • 空行とコメント行を削除する
perl -nle '/(\s*#|^$)/ or print' pgpool.conf
  • PostgreSQL のテーブル定義から型の種類をランキング表示する
perl  -F'\|' -lane 'print $F[1] if($#F==4 and $F[1] !~ /型/)' table_list.txt |sort|uniq -c|sort -nr
  • テキスト中からSQLテキストを削除する
    • 0777 でファイルモードで開いている
    • s 修飾子でシングルラインモードを有効にし、メタキャラクタ "." がニューライン(改行)にマッチするようにしている。
      • ".+?" の "?" でマッチする最短のシーケンスを探す。
    • このケースでは小文字で書かれているクエリを対象外としたかったため、"i" 修飾子をつけていない。
perl -0777 -pe 's/\n(WITH|SELECT|INSERT|UPDATE|DELETE|\/\*\*).+?;//gs;s/(WITH|SELECT|INSERT|UPDATE|DELETE|\/\*\*).+?\n//g' perf_log.txt > perf_log_delete_query.txt
$ perl -nle 'print if /statement latencies in milliseconds:/ .. eof' exec_concurrent_query_by_pgbench_adhoc_l1_user_sample_query3_2022-05-30-100729.log 
statement latencies in milliseconds:
        6.816250        select c_city, s_city, d_year, sum(lo_revenue) as revenue 
        0.000000        from awssampledb.customer, awssampledb.lineorder, awssampledb.supplier, awssampledb.dwdate
        0.000000        where lo_custkey = c_custkey
        0.000000        and lo_suppkey = s_suppkey
        0.000000        and lo_orderdate = d_datekey
        0.000000        and (c_city='UNITED KI1' or
        0.000000        c_city='UNITED KI5')
        0.000000        and (s_city='UNITED KI1' or
        0.000000        s_city='UNITED KI5')
        0.000000        and d_yearmonth = 'Dec1997'
        0.000000        group by c_city, s_city, d_year
        0.000000        order by d_year asc, revenue desc;
  • Redshift 向けのダミーデータを生成する
perl -e 'printf(qq/%08X|%08X|%s|%s\n/,$_,$_,q/2022-01-01/,q/2022-01-01/) for 1..300000000'|gzip -c > table1.csv.gz &
perl -e 'printf(qq/%s|%s|%08X|%08X|%08X|%s|%s|%s|%s|%s|%s|%08X\n/,q/2022-01-01/,1,$_,$_,$_,1,$_,$_,$_,q/ABC/,$_,$_) for 1..300000000'|gzip -c > table2.csv.gz &

*1:Oracle Database使い向け

*2:記憶力がよくない人向け

*3:Perlに限りませんが