findでファイルを探し,sedでファイル名を変え,xargsで処理する方法
「あるディレクトリ以下の,すべてのjpgをpngに変換する」ということを, findとxargsとsedでやろうとしてハマりました.
環境
- macOS 10.15.3 (Catalina)
- ImageMagick 7.0.9-12 Q16 x86_64 2019-12-28
問題
カレントディレクトリ以下のすべてのjpgファイルを, ImageMagickのconvertコマンドでpngファイルに変換しようとして, 次のようにしたところ,sedでの文字列置き換えが効かず,「???」となりました. (1つめのファイルはファイル名に空白を含む.)
$ find . -name "*.jpg"
./00 01.jpg
./0002.jpg
./0003.jpg
$ find . -name "*.jpg" -print0 | xargs -0 -I {} echo convert "{}" "$(sed 's/\.jpg$/\.png/' <<<"{}")"
convert ./00 01.jpg ./00 01.jpg
convert ./0002.jpg ./0002.jpg
convert ./0003.jpg ./0003.jpg
(実行されるコマンドを確認するためここではechoしてますが,変換の際にechoは付けていません. echoを付けずに実行すると,当然jpgからjpgに変換されるだけになります.)
sedの変換が効かない・・・??
原因
これは,コマンド展開がxargsの変数置換より前に実行されるのが原因でした.
sedの結果は{}
になり,それがその後xargsで変数置換されるので,
結果sedによる変換が効いていないようになってしまいます.
cf. bash - xargs: command substitution $(…) with pipe doesn’t work - Stack Overflow
解法
以下のブログ記事が見つかったので試してみたのですが, xargsがスペースを区切りと認識するので,スペースを含むようなパスがある場合はうまく動きませんでした(おしい!).
cf. [find,sed,xargs mv] ディレクトリ/ファイルの名前一括変更 - nullptr
$ find . -name "*.jpg" | sed 'p;s/\.jpg$/\.png/' | xargs -n 2 echo convert
convert ./00 01.jpg
convert ./00 01.png
convert ./0002.jpg ./0002.png
convert ./0003.jpg ./0003.png
そこで,次のような方法を考えました.これならスペースを含むパスがあっても大丈夫です.
$ find . -name "*.jpg" -print0 | xargs -0 -I {} bash -c 'f="{}"; echo convert "$f" "$(sed "s/\.jpg$/\.png/" <<<"$f")"'
convert ./00 01.jpg ./00 01.png
convert ./0003.jpg ./0003.png
convert ./0002.jpg ./0002.png
echoを外し,ちゃんと変換できていることも確認できました👍
$ find . -name "*.jpg" -print0 | xargs -0 -I {} bash -c 'f="{}"; convert "$f" "$(sed "s/\.jpg$/\.png/" <<<"$f")"'
$ find . \( -name "*.jpg" -or -name "*.png" \) -print0 | xargs -0 file
./00 01.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=5, xresolution=74, yresolution=82, resolutionunit=2, datetime=2020:04:11 13:47:37], baseline, precision 8, 640x907, components 3
./00 01.png: PNG image data, 640 x 907, 8-bit/color RGB, non-interlaced
./0002.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=5, xresolution=74, yresolution=82, resolutionunit=2, datetime=2020:04:11 13:47:37], baseline, precision 8, 640x907, components 3
./0002.png: PNG image data, 640 x 907, 8-bit/color RGB, non-interlaced
./0003.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=5, xresolution=74, yresolution=82, resolutionunit=2, datetime=2020:04:11 13:47:37], baseline, precision 8, 640x907, components 3
./0003.png: PNG image data, 640 x 907, 8-bit/color RGB, non-interlaced