ottijp blog

findでファイルを探し,sedでファイル名を変え,xargsで処理する方法

2020-05-13 Tags: findxargssedImageMagick

「あるディレクトリ以下の,すべての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

ottijp
Satoshi SAKAO (@ottijp)

都内でアプリケーションエンジニアをしています

...