web-dev-qa-db-ja.com

Perlは複数の文字列を同時に置き換えます

文字列内の複数の文字列を置き換える方法はありますか?たとえば、文字列hello world what a lovely dayそしてwhatlovelyを別のものに置き換えたい。

$sentence = "hello world what a lovely day";
@list = ("what", "lovely"); # strings to replace
@replist = ("its", "bad"); # strings to replace with
($val = $sentence) =~ "tr/@list/@replist/d";
print "$val\n"; # should print "hello world its a bad day"..

それが機能しない理由はありますか?

ありがとう。

12
Travv92

まず第一に、trはそのようには機能しません。詳細についてはperldoc perlopを参照してください。ただし、trは音訳を行い、置換とは大きく異なります。

この目的のために、置き換えるより正しい方法は

# $val
$val =~ s/what/its/g;
$val =~ s/lovely/bad/g;

「同時」変更はかなり難しいことに注意してください。ただし、たとえば、

%replacements = ("what" => "its", "lovely" => "bad");
($val = $sentence) =~ s/(@{[join "|", keys %replacements]})/$replacements{$1}/g;

(もちろん、文字列をメタ文字に置き換えるには、エスケープが必要になる場合があります。)

これは、用語の非常に緩い意味でまだ同時ですが、ほとんどの目的で、置換が1つのパスで行われるかのように機能します。

また、"what"ではなく"it's""its"に置き換える方が正しいです。

13
muhmuhten

tr///dはリクエストとは関係がないため、主に機能していません(tr/abc/12/dはaを1に置き換え、bを2に置き換え、cを削除します)。また、デフォルトでは、配列はタスクに役立つ方法で正規表現に補間されません。また、ハッシュルックアップやサブルーチン呼び出しなどのロジックがないと、s///操作の右側で決定を下すことはできません。

タイトルの質問に答えるために、次の方法で複数の置換を同時に(つまり、便利な連続で)実行できます。

#! /usr/bin/env Perl
use common::sense;

my $sentence = "hello world what a lovely day";

for ($sentence) {
  s/what/it's/;
  s/lovely/bad/
}

say $sentence;

ここで試みるようなことを行うには:

#! /usr/bin/env Perl
use common::sense;

my $sentence = "hello world what a lovely day";

my %replace = (
  what => "it's",
  lovely => 'bad'
);

$sentence =~ s/(@{[join '|', map { quotemeta($_) } keys %replace]})/$replace{$1}/g;

say $sentence;

このような置換を多数行う場合は、最初に正規表現を「コンパイル」してください。

my $matchkey = qr/@{[join '|', map { quotemeta($_) } keys %replace]}/;

...

$sentence =~ s/($matchkey)/$replace{$1}/g;

編集:

そして、配列補間についての私の意見を拡張するために、$"を変更することができます。

local $" = '|';
$sentence =~ s/(@{[keys %replace]})/$replace{$1}/g;
# --> $sentence =~ s/(what|lovely)/$replace{$1}/g;

配列にすでにキーがある場合はそうかもしれませんが、これは実際にはここでは改善されません。

local $" = '|';
$sentence =~ s/(@keys)/$replace{$1}/g;
6
Julian Fondren