特殊記号が入っている文字列を一括で変更する Linux コマンド (grep + xargs + sed)

This image is generated with ChatGPT-4, and edited by the author.
The penguin part is inspired by the Linux's Tux (Free license).
作成日:2024年09月18日(水) 00:00
最終更新日:2024年09月18日(水) 18:02
カテゴリ:その他
タグ:  Tips トラブル解決

"/" などの特殊記号が入っている文字列を一括で置き換えるのに少し苦労しましたので,備忘録として残しておきます.

こんにちは.高山です.
Webサイトをメンテナンスしていると,内部リンクを張替えなきゃいけない場合があります.
(カテゴリやタグを変えたい場合など)

そのような場合は Linux のコマンドで一括で変えられると便利で,実際に WEB で探すとそれっぽい情報が直ぐに見つかります.
ですが今回は内部リンクを張り替えたかったので,"/" のような特殊記号が入っている文字列を置き換える必要がありました.

解決法を見つけるのに少し苦労しましたので,今後のために記事に残しておこうと思います.

1. 実際の解決方法

いきなり結論から書いてしまいますが,今回は下記のコマンドで上手く行きました.

grep -l "[置き換えたい文字列]" [検索ファイル] | xargs sed -i"[バックアップファイルの拡張子]" -e 's![置き換えたい文字列]![置き換え後の文字列]!g'

"!" は 文字列の中に含まれていない記号であれば (例えば "@" や "#") 大丈夫なようです.

例えば,マークダウンファイル上に記入してある内部リンク rootdir/subdir1/file1rootdir/subdir2/file1 に置き換えたい場合は下記のようになります.

grep -l "rootdir/subdir1/file1" *.md | xargs sed -i".back" -e 's!rootdir/subdir1/file1!rootdir/subdir2/file1!g'

次節ではコマンドの中身を簡単に説明します.

2. コマンドの中身

今回のコマンドは次に示す 3 種類のコマンドの組み合わせでできています.

  • grep: ファイルや標準入力など (ストリームと言います) からパターンにマッチする行を検索します.
  • xargs: 標準入力を読み込んで,それを引数として指定したコマンドを実行します.
  • sed: ストリームを読み込んで,スクリプトに従って文字列を変換します.

細かく説明するのは大変なので,今回の話に関係する箇所だけかいつまんで説明します.

2.1 grep

grep はファイルなどからパターンにマッチする行を検索するコマンドです.
例えば,下記のような内容のファイルがあるとします.

cat test.md
rootdir/subdir1/file1
rootdir/subdir2/file1
rootdir/subdir3/file1
rootdir/subdir4/file1
rootdir/subdir5/file1

上のファイルに対して grep を実行すると,下記のような出力が得られます.

grep "rootdir/subdir1/file1" test.md
rootdir/subdir1/file1

-l オブションを付けて呼び出すと,一致文字列があるファイル名が得られます.

grep -l "rootdir/subdir1/file1" test.md
test.md

今回は -l オプションを付けて grep を呼び出すことで置き換えたい文字列があるファイル郡を抽出しています.

2.2 xargs

xargs は標準入力を読み込んで,それを引数として指定したコマンドを実行します.
細かい話は置いておいて,あるコマンドの出力を別のコマンドに渡したい場合によく使います.

例えば,下記のコマンドで grep で検索したファイル郡の中身を表示することができます.

grep -l "rootdir/subdir1/file1" test.md | xargs cat
rootdir/subdir1/file1
rootdir/subdir2/file1
rootdir/subdir3/file1
rootdir/subdir4/file1
rootdir/subdir5/file1

今回は grep で抽出したファイル群を,次で説明する sed に渡す役目をしています.

2.3 sed

sed はストリームを読み込んで,スクリプトに従って文字列を変換します.
スクリプトはコマンドライン上で直接記入することも,ファイルに記入することもできます.
今回はコマンドライン上で記入しています.

sed を使って文字列を置き換える例を下記に示します.

sed -e 's/dir/_dir/' test.md
root_dir/subdir1/file1
root_dir/subdir2/file1
root_dir/subdir3/file1
root_dir/subdir4/file1
root_dir/subdir5/file1

-e の次の引数にスクリプトを記入します.

スクリプト内の s は文字列を置き換える命令で下記のように使います.

sed -e 's/[置き換えたい文字列]/[置き換え後の文字列]' [入力ファイル]

スクリプト末尾に g を付けた場合は,一致する文字列全てを置き換えます.

sed -e 's/dir/_dir/g' test.md
root_dir/sub_dir1/file1
root_dir/sub_dir2/file1
root_dir/sub_dir3/file1
root_dir/sub_dir4/file1
root_dir/sub_dir5/file1

-i オプションを付けると,バックアップを作って対象ファイルを書き換えます.

sed -i".back" -e 's/dir/_dir/g' test.md
ls
test.md test.md.back
cat test.md
root_dir/sub_dir1/file1
root_dir/sub_dir2/file1
root_dir/sub_dir3/file1
root_dir/sub_dir4/file1
root_dir/sub_dir5/file1
cat test.md.back
rootdir/subdir1/file1
rootdir/subdir2/file1
rootdir/subdir3/file1
rootdir/subdir4/file1
rootdir/subdir5/file1

2.4 特殊記号 "/" が含まれているときの sed の使い方

今回少し苦労した点は,"/" が含まれているときの sed スクリプトの書き方です.

スクリプト内の s/[置き換えたい文字列]/[置き換え後の文字列]/e の区切り線に "/" が使われているため,文字列内の "/" はどのように処理すれば良いのだろう? と悩みました.
"\" でエスケープできるのではと思い試してみましたが,上手くいきませんでした.

仕方なく公式のマニュアルを見てみたところ (長いのであまり読みたくなかったのです(^^;)) ,第3.3項 "The s Command" の第4段落目に "/" は他の文字で置き換えられると書いてありました.

そこで下記のように "/" を "!" で置き換えて試してみると,期待通りに置き換えることができました.

sed -i".back" -e 's!rootdir/subdir1/file1!rootdir/subdir2/file1!g' test.md
cat test.md
rootdir/subdir2/file1
rootdir/subdir2/file1
rootdir/subdir3/file1
rootdir/subdir4/file1
rootdir/subdir5/file1

他の文字も試してみましたが,同じ記号を使っている分には問題なく動きそうです.


今回は,特殊記号 "/" が入っている文字列を一括で変更する Linux コマンド を紹介しましたが,如何でしたでしょうか?

どうも昔から Linux コマンドを覚えるのが苦手で毎回同じ内容を調べてしまいます.
少しずつ本サイトにもまとめていって自分の業務効率化にもつなげていきたいですね.

今回紹介した話が,同じようなタスクでお困りの方に少しでも参考になれば幸いです.