perlのワンライナーで文字列を置換する(初心者向け)
たまに、趣味や仕事でperlのワンライナーで文字列置換をするのですが、そのたびに置換方法を忘れているので、ワンライナーで文字列を置換する時に知っておきたいことをまとめました。
目次
- perlのワンライナーで置換表現のサンプル
- perlでのファイル文字列置換方法を知っていたほうがいい理由
- 文字列を置換するperlワンライナーの書き方
- 最低限覚えておきたいperlコマンドのオプション
- 色々な正規表現
- シングルクウォートで囲むかダブルクウォートで囲むか
- ワンライナーの記述で参考になるサイト
perlのワンライナーで置換表現のサンプル
サーバの管理でよく遭遇する例です。
1. /etc/passwdの設定ファイルを修正する
/etc/passwdのバックアップファイル(ファイル名の末尾に.backupの文字列を付与)を作成して、ユーザのデフォルトshellをbashからzshに変更する
perl -pi.backup -e 's;/bin/bash;/bin/zsh;g' /etc/passwd diff -U0 /etc/passwd.backup /etc/passwd @@ -1 +1 @@ -root:x:0:0:root:/root:/bin/bash +root:x:0:0:root:/root:/bin/zsh
2. apacheの設定ファイルを修正する
apacheの設定ファイルで、バックアップファイル(ファイル名の末尾に日付を付与)を作成して、MinSpareThreadsを75から125に変更する。
(^\s*MinSpareThreads\s+)の箇所は、(行の先頭からの空白文字列)MinSpareThreads(空白文字列)の文字列と一致して、一致した箇所が${1}に挿入されています。
perl -pi.$(date +%Y%m%d) -e 's;(^\s*MinSpareThreads\s+)\d+;${1}125;g' httpd-mpm.conf diff -U0 httpd-mpm.conf.20180131 httpd-mpm.conf @@ -46 +46 @@ - MinSpareThreads 75 + MinSpareThreads 125 @@ -63 +63 @@ - MinSpareThreads 75 + MinSpareThreads 125 @@ -83 +83 @@ - MinSpareThreads 25 + MinSpareThreads 125 @@ -97 +97 @@ - MinSpareThreads 5 + MinSpareThreads 125
ちなみに、正直にいうと$1を${1}にしないといけないことがわからなくて、stackoverflowに質問したところ、数分で回答が来ました。早い。
How to avoid wrong variable name interpretation in perl one-liner - Stack Overflow
ここから先は、上記例を理解するための解説です。
perlでのファイル文字列置換方法を知っていたほうがいい理由
sed
下記のリンク先の通り、sedは同じコマンドでもOSによって挙動の変わることがあります。複数のサーバを管理するのであれば、perlでの置換の方がより広い対象に適用できるので、学習コストを小さく出来ます。
環境に依存しないワンライナーを書くならsedよりperlの方がいい - Qiita
ruby
rubyは、そもそもデフォルトではインストールされていないサーバが多いです。ファイル文字列を置換する方法を覚えるのであれば、perlの方がより多くのサーバで利用出来るので効率的です。
文字列を置換するperlワンライナーの書き方
file1.txtという文字列をバックアップファイルの作成なしで置換する場合は、以下の形式で記述します。(perlコマンドのオプションについては後述)
perl -pi -e 'XXXXXXX' file1.txt
'XXXXXXX'の箇所には、どのように文字列の置換をするのかPerlのプログラムで記述します。's;A;B;g' とすると、Aという文字列をBという文字列で置換させるということになります。sは置換用の演算子を表していて、gが有ると置換対象文字列の該当する文字列を全て置換。gが無いと置換対象文字列の最初に該当する文字列のみ置換となります。また、iをgの位置に一緒に付与すると、大文字と小文字を区別せずに置換するようになります。
gが無い場合は、各行で該当する最初の文字列のみ置換される。
# echo -e "aabbaabb\naabbaabbaabb" | perl -pe 's;aa;bb;' bbbbaabb bbbbaabbaabb
gが有る場合は、各行で該当する文字列全てに対して置換される。
# echo -e "aabbaabb\naabbaabbaabb" | perl -pe 's;aa;bb;g' bbbbbbbb bbbbbbbbbbbb
iが有る場合は、大文字・小文字を区別せずに該当する文字列を置換する。(gが無いので各行の最初に該当する文字列のみ置換)
# echo -e "AAbbaabb\naabbAAbbaabb" | perl -pe 's;aa;bb;i' bbbbaabb bbbbAAbbaabb
最低限覚えておきたいperlコマンドのオプション
以下のオプションは、perlのワンライナーで置換するときに知っていた方がよいものです。
-e: 引数に指定した文字列をperlのプログラムと認識する
perl -e "print \"Hello World\n\"" Hello World
-p: ファイルやパイプの入力データに対して、行単位で処理・出力する
各入力行は、$_という変数で表現される。ちなみに、$.は現在行。下記の例では、パイプからの入力データに対して、各行の3番目の文字列をZに置き換えている。
echo -e "aaa\nbbb\nccc\nddd" | perl -pe 'substr($_,2,1,Z)' aaZ bbZ ccZ ddZ
-i: 入力ファイルの内容を更新して上書きする
このオプションは、-i <ファイル名> とすれば、指定したファイルをperlの処理後の結果で上書きする。-i<拡張子> <ファイル名> とすれば、指定された拡張子つきのバックアップファイルの作成と元ファイルの上書きをする。-iと拡張子の文字列の間にスペースは必要ない。
バックアップファイルを作成しない場合
$ cat sample.txt aaa $ perl -pe 's;aaa;bbb;g' -i sample.txt $ cat sample.txt bbb
バックアップファイルを作成する場合
$ cat sample2.txt ccc $ perl -i.backup -pe 's;ccc;ddd;g' sample2.txt $ cat sample2.txt ddd $ ls -l sample2* -rw-rw-r--. 1 vagrant vagrant 4 Jan 22 12:33 sample2.txt -rw-rw-r--. 1 vagrant vagrant 4 Jan 22 12:33 sample2.txt.backup
色々な正規表現
正規表現には種類があります。これを知らないと、perlで正規表現を利用した置換をする際、意図した置換にたどり着くのに苦労するかもしれません。
知っておいたほうがいい正規表現の種類は、
です。正規表現で表せる表現幅は、BRE < ERE < Perlの順番で広がっていきます。Perlの正規表現は他のソフトウェアでも利用できるように、Perlの正規表現を取り入れたPCRE(Perl Compatible Regular Expressions)という汎用ライブラリが作られており、Apache HTTPやPostfixなどで利用されています。
正規表現の違いをgrepコマンドで見ていきます。grep コマンドは、オプションによって利用する正規表現を変更できます。基本正規表現は、-G。拡張正規表現は、-E。Perlの正規表現は、-P(Macではこのオプションは無し)です。(オプションの指定なしのデフォルトは、-Gオプションの基本正規表現です。)
実際に、"aという文字列が2個以上3個以下で連続して繋がっている文字列"を正規表現で表すと、BREでは、 a\{2,3\}, EREでは、 a{2,3}となり、BREとEREで正規表現の書き方が異なります。
BREモードでのgrepの動作
echo aa | grep "a\{2,3\}" aa echo aa | grep "a{2,3}" (aaはひっかからない!)
EREモードでのgrepの動作
echo aa | grep -E "a\{2,3\}" (aaはひっかからない!) echo aa | grep -E "a{2,3}" aa
このように、正規表現を書くときにはどの正規表現が利用されるのか意識していないと、意図する文字列が引っかからない可能性があります。
BRE(basic regular expression: 基本正規表現)、ERE(extended regular expression: 拡張正規表現)、Perlの正規表現を簡単にまとめると次のようになります。
BRE | ERE | Perlの正規表現 | 意味 |
^ | ^ | ^ | 文字列の先頭(行の場合は行の先頭)に一致する。 |
$ | $ | $ | 文字列の末尾(行の場合は行の行末)に一致する。 |
∗ | ∗ | ∗ | ∗記号の直前の文字に対する、0回以上の繰り返しに一致する。 |
\+ | + | + | +記号の直前の文字に対する、1回以上の繰り返しに一致する。 |
\? | ? | ? | ?記号の直前の文字に対する、0回、もしくは1回の繰り返しに一致する。 |
\( \) | ( ) | ( ) | ()に囲まれたパターンに一致した文字列を記憶する。記憶した一致文字列には、$0...$9の記号でアクセスできる。 |
\{m,n\} | {m,n} | {m,n} | {}記号の直前の文字に対する、m回以上・n回以下の繰り返しに一致する。 |
\{m\} | {m} | {m} | {}記号の直前の文字に対する、m回の繰り返しに一致する。 |
\{m,\} | {m,} | {m,} | {}記号の直前の文字に対する、m回以上の繰り返しに一致する。 |
このサイトで、記述した正規表現が意図した通りのパターンか確認できます。pcre(php)というのがPerlの正規表現の場合です。
Online regex tester and debugger: PHP, PCRE, Python, Golang and JavaScript
シングルクウォートで囲むかダブルクウォートで囲むか
perlのプログラムをシングルクウォートで囲んだ場合と、ダブルクウォートで囲んだ場合では動作は変わります。
シングルクウォートで囲まれた文字列は、その文字列のまま解釈されてperlプログラムが実行されます。一方、ダブルクウォートで囲まれた文字列の場合は、変換の必要な文字列を変換してperlプログラムが実行されます。
例えば、以下の例では、"s;$a;$b;g"の箇所が、"s;apple;banana;g"と変換されてからperlのプログラムが実行されています。
a=apple b=banana echo "my favorite is apple" | perl -pe "s;$a;$b;g" test
こちらの例では、"s;$(logname);testuser;g"の箇所が、"s;vagrant;testuser;g"と変換されてからperlのプログラムが実行されています。(lognameコマンドは、現在のユーザのログイン名を表示します。)"s;`logname`;testuser;g"の場合も、"s;vagrant;testuser;g"と変換されています。
id | perl -pe "s;$(logname);testuser;g" uid=1000(testuser) gid=1000(testuser) groups=1000(testuser) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 id | perl -pe "s;`logname`;testuser;g" uid=1000(testuser) gid=1000(testuser) groups=1000(testuser) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
このように、ダブルクウォートの内部では、$, `, \ が特別な文字と解釈されます。特別な文字と解釈されないようにするためには、\$, $`, \\というように、特別な文字の前に\を付与します。
ちなみに、シングルクウォートとダブルクウォートで文字列の解釈が異なるのは、perlだからではなくて、bashをシェルとして利用しているからです。このシングルクウォートとダブルクウォートの変換の違いは他のコマンドを利用した時も現れます。このstackoverflowのページには、シングルクウォートとダブルクウォートでの比較がわかりやすくテーブル形式で記載した返答があります。
shell - Difference between single and double quotes in Bash - Stack Overflow
ワンライナーの記述で参考になるサイト
Perlの正規表現
perlre - perldoc.perl.org
正規表現のパターンを確認
Online regex tester and debugger: PHP, PCRE, Python, Golang and JavaScript
pcre(php)というのがPerlの正規表現。