2022年2月6日日曜日

findコマンドとsedコマンドを連携して、ディレクトリ内にある文字列を置き換える


要約

下記のコマンドで「.git」と「.pio」ディレクトリ以外のファイルの「Topick」を「Topic」に書き換えられます。
find -type f \
-not -path "*/.git/*" \
-not -path "*/.pio/*"\
| xargs sed -i -e "s/Topick/Topic/g"

下記のコマンドで「.git」と「.pio」ディレクトリ以外のファイルの「my-smbols.lib」を「my-symbols.kicad_sym」に書き換えつつ、「(type Legacy」または「type "Legacy"」の「Legacy」を「KiCad」に書き換えられます。
find -type f \
-not -path "*/.git/*" \
-not -path "*/.pio/*"\
| xargs sed -i \
-e "s/\((type \"\?\)Legacy\()\"\?.*\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"

背景

findやsedとは、bash環境などで呼び出せるコマンドです。
findはファイルやディレクトリの列挙、sedは文字列操作を行えます。
sedは「stream editor」の略のようです。

一部のディレクトリは除外しつつ文字列を置き換えたいことがたまに発生し、その度調べ直していたので、備忘録を兼ねて記事として方法を残します。

使ったもの

  • Ubuntu20.04のbash環境
    v5.0.17
  • findコマンド
    v4.7.0
  • sedコマンド
    v4.7

findでディレクトリを除外してファイルの一覧を出す: -not -path を利用

findコマンドは「-type f 」を渡すと、実行したディレクトリを親としてそれに含まれるファイルを一覧にして表示します。
find -type f

gitを使っている場合.gitディレクトリが生成されており、それはgitが管理しているので、今回の置き換え処理では無視します。
findコマンドは「-not」機能があるので、それを付けると除外できます。
find -type f -not -path "*/.git/*"

複数のディレクトリを除外したい場合は、ディレクトリの分 -not -path を追加します。
find -type f -not -path "*/.git/*" -not -path "*/my-kicad-symbols/*"

参考: How to exclude a directory in find . command

sedコマンドの実行例と使える機能を例示しているサイト

この記事では自分が実行した例を残しますが、それで利用した以外の機能は紹介しません。他の機能も知りたい場合は「sedコマンド」などで検索してみてください。
自分は下記のサイトを参考にしました。

sedコマンド | コマンドの使い方(Linux)

しかしながら、自分が試した環境では()で括った情報の展開に\1や\2が必要でしたが、それについては説明が無かったので、sedの更新によって呼び出し方が変わっている部分があるのかもしれません。

sedコマンドで特定の文字列が含まれる行の表記を変更する: 正規表現の括弧を利用

今回は下記の行を対象とします。
置き換え方針は「my-symbols.lib」を「my-symbos.kicad_sym」に置き換えつつ、同じ行にある「Legacy」を「KiCad」に置き換えます。
(lib (name my-symbols)(type Legacy)(uri some-path/my-symbols.lib))
^^^^^^ ^^^^^^^^^^^^^^

正規表現機能「-e」を利用して「my-symbols.lib」を「my-symbos.kicad_sym」に置き換え

文字列を置き換えるには「s/置き換え対象/置き換え後の文字/g」などの正規表現よる置き換え記述を-e機能の引数としてsedコマンドに渡します。
今回の例だと
"s/my-symbols.lib/my-symbols.kicad_sym/g"
となります。
echoコマンドとパイプ「|」を利用して動作確認します。
echo "(lib (name my-symbols)(type Legacy)(uri some-path//my-symbols.lib))" | sed -e "s/my-symbols.lib/my-symbols.kicad_sym/g"
(lib (name my-symbols)(type Legacy)(uri some-path//my-symbols.kicad_sym))

期待通りに置き換わりました。

「my-symbols.lib」を「my-symbos.kicad_sym」に置き換えつつ、「(type Legacy)」を「(type KiCad)」にする

s///gを利用した置き換えには正規表現を利用できます。
この正規表現は、()で括った部分を置き換え後の文字列に展開する機能を備えているため、それを利用します。
「(type Legacy)」と「my-symbos.kicad_sym」で対象となる行を見つけ、LegacyをKiCadに、my-symbols.libをmy-symbols.kicad_symに置き換える記述はこうなります。
"s/\((type \)Legacy\().*\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"

要所を解説します。

「(type 」を括弧で括って一致する箇所を探っています。
正規表現の括弧はバッククオートを直前につけて、対象の文字列と区別しています。
括弧で括った部分は\1や\2で置き換え後の文字列で展開可能なので、「(type 」を\1として展開しています。
"s/\((type \)Legacy\().*\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"
^^^^^^^^^^ ^^
(lib (name my-symbols)(type Legacy)(uri some-path/my-symbols.lib))
^^^^^^

(type Legaryとmy-symbols.libの間の文字列には閉じ括弧「)」と任意の文字列が来るので、それを括弧で括って指定しています。
任意の文字列は「.」、それが任意の長さ続くことは*で表現できます。
\1として先ほどの「(type 」を展開しているので、この一致した部分は\2として置き換え後の文字列に展開しています。
"s/\((type \)Legacy\().*\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"
^^^^^^^ ^^
(lib (name my-symbols)(type Legacy)(uri some-path/my-symbols.lib))
^^^^^^^^^^^^^^^^

ではechoコマンドで結果を見てみます。
echo "(lib (name my-symbols)(type Legacy)(uri some-path/my-symbols.lib))" | sed -e "s/\((type \)Legacy\().*\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"
(lib (name my-symbols)(type KiCad)(uri some-path//my-symbols.kicad_sym))
期待通りにLegacyがKiCadに、my-symbols.libがmy-symbols.kicad_symに置き換わりました。

"が存在して「(type "Legacy")」だったとしても「(type "KiCad")」にする

「"」でLegacyが括られていても置き換えたい場合は、「該当する文字が1か0」を意味する「?」を利用して下記のように記述します。
"s/\((type \"\?\)Legacy\()\"\?\.*)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"
^^^^ ^^^^
動作を確認してみます。

「"」有り
echo "(lib (name my-symbols)(type \"Legacy\")(uri some-path/my-symbols.lib))" | sed -e "s/\((type \"\?\)Legacy\().*\"\?\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"
(lib (name my-symbols)(type "Legacy")(uri some-path/my-symbols.lib))

「"」無し
echo "(lib (name my-symbols)(type Legacy)(uri some-path/my-symbols.lib))" | sed -e "s/\((type \"\?\)Legacy\()\"\?.*\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"
(lib (name my-symbols)(type KiCad)(uri some-path/my-symbols.kicad_sym))

「"」の有無に関わらず変換できました。

ファイルに対してsedを適用: -i機能

echoコマンドと-e機能で動作確認を行ってきましたが、ファイルに対して実施するには-i機能を利用します。

下記のファイルを対象とします。
sed-test.txt
(sym_lib_table
(lib (name teensy)(type Legacy)(uri some-path/teensy_library/teensy.lib)(options "")(descr ""))
(lib (name my-symbols)(type Legacy)(uri some-path/my-symbols.lib)(options "")(descr ""))
)

-i -e機能で置き換えを実施します。
sed -i -e "s/\((type \"\?\)Legacy\()\"\?.*\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g" sed-test.txt

期待通りに置き換わりました。
sed-test.txt
(sym_lib_table
(lib (name teensy)(type Legacy)(uri some-path/teensy_library/teensy.lib)(options "")(descr ""))
(lib (name my-symbols)(type KiCad)(uri some-path/my-symbols.kicad_sym)(options "")(descr ""))
)

findとsedを組み合わせる: コマンドのパイプ「|」と「xargs」を使う

コマンドはパイプ「|」を使うことで結果を次のコマンドに渡せます。
また「xargs」を「|」の後に書くことで、「|」の前で生成された結果を「xargs」のあとで実行するコマンドの引数として利用できます。
findで見つけたファイルの一覧をsedコマンドに渡して実行するには「| xargs」でfindとsedを繋げれば良いです。

下記のディレクトリを対象とします。
dorectory-root
|- sed-test.txt
|- protected-dir
|- sed-test.txt

directory-rootのsed-test.txtとprotected-dir/sed-test.txtは同じです。
$ cat sed-test.txt 
(sym_lib_table
(lib (name teensy)(type Legacy)(uri some-path/teensy_library/teensy.lib)(options "")(descr ""))
(lib (name my-symbols)(type Legacy)(uri some-path/my-symbols.lib)(options "")(descr ""))
)
$ diff sed-test.txt protected-dir/sed-test.txt
# 実行結果として何も表示されないので、同一を意味します。

protected-dirは除外するfindコマンドと組み合わせてsedコマンドを実行します。
find -type f -not -path "*/protected-dir/*" | xargs sed -i -e "s/\((type \"\?\)Legacy\()\"\?.*\)my-symbols.lib/\1KiCad\2my-symbols.kicad_sym/g"

sed-test.txtは置き換わり、protected-dir/sed-test.txtは置き換わっていません。
$ cat sed-test.txt 
(sym_lib_table
(lib (name teensy)(type Legacy)(uri some-path/teensy_library/teensy.lib)(options "")(descr ""))
(lib (name my-symbols)(type KiCad)(uri some-path/my-symbols.kicad_sym)(options "")(descr ""))
)
$ diff sed-test.txt protected-dir/sed-test.txt
3c3
< (lib (name my-symbols)(type KiCad)(uri some-path/my-symbols.kicad_sym)(options "")(descr ""))
---
> (lib (name my-symbols)(type Legacy)(uri some-path/my-symbols.lib)(options "")(descr ""))f

対象のファイルを絞り込んで期待通りに置き換えを実施できました。

おわり

findコマンドで対象となるディレクトリを絞り込み、sedコマンドで置き換えたい行の絞り込みと文字列の置換を行えました。

参考

How to exclude a directory in find . command
sedコマンド | コマンドの使い方(Linux)

変更履歴

2022.09.08 必要な情報を思い出しやすいように要約を追加しました。

0 件のコメント :