2019年5月13日月曜日

[Perl] 05. 文字列処理

今回は文字列の処理についてのメモ。



文字列の結合
文字列を結合したい場合には . (ピリオド)を使う。
# 結合
$moji = 'aaa' . 'bbb';

print "$moji\n";

$moji_1 = 'AAA';
$moji_2 = 'BBB';

print "$moji_1$moji_2\n";

# 配列に区切り文字を入れて結合
@array = ('aaa', 'bbb', 'ccc');
$moji_3 = join '+', @array;
print "$moji_3\n";
実行結果
aaabbb
AAABBB
aaa+bbb+ccc

文字列の分割
文字列を分割したい場合は、split 関数を使用する。split 関数の第一引数は正規表現となっている。

split(/PATTERN/[,EXPR[,LIMIT]])

 /PATTERN/ : セパレートする文字列 (正規表現)
 EXPR : 対象の文字列
 LIMIT : 最大分割数

$moji_1 = 'aaa,bbb,ccc';
$moji_2 = 'AAA BBB    CCC';

# , で分割
@array = split(/,/, $moji_1);
print "@array\n";

# 複数の空白で分割
@array = split(/ +/, $moji_2);
print "@array\n";
実行結果
aaa bbb ccc
AAA BBB CCC

LIMIT は省略できるが、省略すると「分割後の末尾にある空要素は全て消す」という謎仕様になっているので注意。改行コードがある場合は、改行コードが末尾の文字列となるので問題ない。
CSVファイルなどを扱う場合、改行コードを消した後に split 関数を使って分割すると期待する要素数が得られなくなる場合がある。改行コードを消さなければいい話なのだが、当たり前のように消しちゃう。LIMIT に -1 を設定すると、分割後の末尾に空要素があっても消されなくなる。
$moji = '0,,2,,,';
$count = 0;

# , で分割 (LIMIT 省略)
@array = split(/,/, $moji);
foreach (@array){
    print "[$count] = $_\n";
    $count++;
}

print "\n";
$count = 0;

# , で分割 (LIMIT -1)
@array = split(/,/, $moji, -1);
foreach (@array){
    print "[$count] = $_\n";
    $count++;
}
実行結果
[0] = 0
[1] =
[2] = 2

[0] = 0
[1] =
[2] = 2
[3] =
[4] =
[5] =

パターンマッチ
パターンマッチ演算子と呼ばれる =~ を使って、正規表現で書かれたパターンを含む文字列かどうかを判断する。
=~!~ にすることで、パターンを含まないかどうかを判断することもできる。
$word = 'ABCDEF';

# C を含む
if ($word =~ /C/) {
    print "match!\n";
}

# X を含まない
if ($word !~ /X/) {
    print "match!\n";
}

抽出
文字列の抽出は、正規表現のパターンマッチの例でも出てきたグループ化を使う。
$word = "ABC[123]DEF";

# 先頭/末尾の1文字を抽出
$word =~ /(^.)(.*)(.$)/;
print "$1 $3\n";

# [] の中身を抽出
$word =~ /\[(.*)\]/;
print "$1\n";

# [] の前後を抽出
$word =~ /(.*)(\[.*\])(.*)/;
print "$1 $3\n";
実行結果
A F
123
ABC DEF

上記の例だと [] が複数ある場合は注意が必要。最短一致という手法が必要になる場合がある。最短一致は量指定子 (+ や {} など) の後ろに ? を付けることで実現可能。
$word = "[ABC]123[DEF]";

# [] の中身を抽出 ("ABC]123[DEF" が対象となる)
$word =~ /\[(.*)\]/;
print "$1\n";

# 最初の [] の中身を抽出
$word =~ /\[(.*?)\]/g;
print "$1\n";

# 最後の [] の中身を抽出 (.+ は .* でも可)
$word =~ /.+\[(.*?)\]/g;
print "$1\n";
実行結果
ABC]123[DEF
ABC
DEF

マッチした文字列を配列に格納することもできます。
$word = "[ABC]123[DEF]";

# 連続するアルファベットのみ抽出して配列に格納
@array = ( $word =~ /([A-Z]+)/g );

print "$array[0]\n";
print "$array[1]\n";
実行結果
ABC
DEF

また、文字列の抽出する位置が決まっている場合は substr を使う方法もある。引数は以下の通り。

substr(EXPR,OFFSET[,LENGTH[,REPLACEMENT]])

 EXPR : 対象の文字列
 OFFSET : 文字位置
 LENGTH : 文字の長さ (省略可)
 REPLACEMENT : 置換する文字列 (省略可)

OFFSET は 0 始まりなので注意。OFFSET がマイナスの場合は文字列の末尾から数えた位置となる。LENGTH を省略した場合は末尾までとなる。LENGTH がマイナスの場合は文字列の末尾から数えた位置までを意味する。
$word = 'ABCDEFG';

# 先頭1文字目から2文字
$str = substr($word, 1, 2);
print "$str\n";

# 先頭1文字目から末尾まで
$str = substr($word, 1);
print "$str\n";

# 先頭1文字から末尾2文字まで
$str = substr($word, 1, -2);
print "$str\n";

# 末尾の3文字目から2文字
$str = substr($word, -3, 2);
print "$str\n";

# 末尾の3文字目から2文字を置換
substr($word, -3, 2, 'XX');
print "$word\n";
実行結果
BC
BCDEFG
BCDE
EF
ABCDXXG

パスからファイル名やディレクトリ名を抽出する場合は File::Basename モジュールを使う。
use File::Basename;

$file_path = '/home/work/file.txt';
$regexp = qr/\..*$/;  # 文字列の末尾が .<文字> である

# ベース名の取得
$basename = basename $file_path;
print "$basename"."\n";

# ディレクトリ名の取得
$dirname = dirname $file_path;
print "$dirname"."\n";

# ベース名とディレクトリ名の取得
($basename, $dirname) = fileparse $file_path;
print "$basename $dirname"."\n";

# ファイル名、ディレクトリ名、拡張子を取得
($filename, $dirname, $suffix) = fileparse($file_path, $regexp);
print "$filename $dirname $suffix"."\n";
実行結果
file.txt
/home/work
file.txt /home/work/
file /home/work/ .txt

置換
正規表現を使って文字列の置換を行うことができる。

対象の文字 = s/置換前文字列/置換後文字列/修飾子;
$word = 'ABC123ABC123';

# 先頭の 123 を 456 に置換
$word =~ s/123/456/;
print "$word\n";

# 全ての ABC を DEF に置換 (修飾子 g を使用)
$word =~ s/ABC/DEF/g;
print "$word\n";
実行結果
ABC456ABC123
DEF456DEF123

また、置換を行う場合も最短一致が必要となる場合がある。
$word = "[ABC]123[DEF]";

# [] の中身を置換 ("ABC]123[DEF" が対象となる)
$word =~ s/\[(.*)\]/[XXX]/;
print "$word\n";

$word = "[ABC]123[DEF]";

# [] の中身を置換 ("ABC" と "DEF" が対象となる)
$word =~ s/\[(.*?)\]/[XXX]/g;
print "$word\n";
実行結果
[XXX]
[XXX]123[XXX]

よく使う置換をまとめてみた。(削除ばっかり...)
$word =~ s/\n//;       # 改行削除
$word =~ s/\r|\n//g;   # 改行削除 (CR,LF)

$word =~ s/(^\s*)//g;  # 先頭の空白を削除
$word =~ s/(^\t*)//g;  # 先頭のTabを削除

$word =~ s/\s//g;      # 空白を全て削除

余談ですが、chomp 関数を使うと改行を削除できる。但し、復帰 CR (\r) は削除してくれないので注意。(もしかして Windows で Perl 使うときは CR も削除してくれるのかな?)
$word = "ABCDEF\n";

chomp($word);  # 改行削除 (\n)

変換
1文字毎の変換について。
対象の文字 = tr/変換対象の文字/変換後の文字/修飾子;

正規表現は使わないので注意。tr を y としても同じ動きとなる。修飾子は3つ。

c 変換対象以外の文字を変換する
d 変換対象以外の文字を削除する
s 連続した文字を1文字にまとめる

$word1 = "ABCXXXCBA";
$word2 = "ABCXXXCBA";

$word1 =~ tr/ABC/123/;  # A→1, B→2, C→3 に変換
$word2 =~ tr/X/4/s;     # X→4 に変換 (修飾子 s により1文字にまとめられる)

print "$word1\n";
print "$word2\n";
実行結果
123XXX321
ABC4CBA

範囲指定を使って、大文字を小文字に、または小文字を大文字に変換できる。便利。
$word1 = "abCDef";
$word2 = "abCDef";

$word1 =~ tr/A-Z/a-z/;  # 大文字→小文字
$word2 =~ tr/a-z/A-Z/;  # 小文字→大文字

print "$word1\n";
print "$word2\n";
実行結果
abcdef
ABCDEF

出現回数
指定文字列の出現回数を調べる方法。色々なやり方があるが、とりあえず例を3種類挙げてみる。
例として数字以外の文字 (\D) が3回連続しているパターンの数を調べる。
$word = "ABCDEF";

$count1++ while ( $word =~ /\D{3}/g );
$count2 = ( ()= $word =~ /\D{3}/g );
$count3 = @{ [$word=~ /\D{3}/g] };

print "$count1 $count2 $count3\n";
実行結果
2 2 2

上記の場合は 'ABC' 'DEF' の2つとなる。これを 'ABC' 'BCD' ... といったように再帰的(?)な出現回数を調べたい場合は以下。
$word = "ABCDEF";

# "ABAB" の数
$count1++ while ( $word =~ /(?=\D{3})/g );
$count2 = ( ()= $word =~ /(?=\D{3})/g );
$count3 = @{ [$word=~ /(?=\D{3})/g] };

print "$count1 $count2 $count3\n";
実行結果
4 4 4

文字数
文字列の長さを調べたい場合は length 関数を使う。改行も1文字と認識されるので注意。
$word1 = "ABCDEF";
$word2 = "ABCDEF\n";

$count1 = length($word1);
$count2 = length($word2);

print "$count1 $count2\n";
実行結果
6 7

文字列の繰り返し
文字列 x 数字 で文字列を連続させることができる。
$sep_msg = '-' x 20;
print $sep_msg."\n";
実行結果
--------------------


0 件のコメント:

コメントを投稿