筆者がよく使うシェルスクリプトの処理をまとめました。
PNG画像を一括してJPEG画像に変換したいとき
PNG画像をJPEG画像に変換するには、ImageMagickのconvertコマンドを使います。a.pngをa.jpgに変換するには、次のように使います。
$ convert a.png -quality 85 a.jpg
Code language: Bash (bash)
lsによるファイル一覧
ターミナルで ls *.png とすると、ターミナルの幅をいっぱいに使って、1行あたり複数ファイルを表示します。
$ ls *.png
a.png b.png c.png
Code language: Bash (bash)
ところが、標準出力をリダイレクトしたり、パイプに送ると、1行1ファイルで表示します。
$ ls *.png | cat
a.png
b.png
c.png
Code language: Bash (bash)
次のxargsコマンドは、この1行1ファイルを想定しています。
xargsによる繰り返し
まず、カレントディレクトリのPNG画像をJPEG変換します。
lsコマンドでpngファイル一覧、1行1ファイルの一覧を取得します。xargsコマンドで1行ずつ読み込みながら、convertコマンドでJPEG変換処理します。
-l (小文字のエル) は、1行づつ処理する、という意味です。
--replace または -I(大文字のアイ)は、コマンド部分の{}を読み込んだ1行、つまりpngファイル名で置換します。
pngファイル名に空白を含んでいるかもしれないので、ダブルクォートで囲みます。
$ ls *.png | cat
a.png
b.png
$ ls *.png | xargs -l --replace convert "{}" -quality 85 "{}.jpg"
$ ls *.jpg | cat
a.png.jpg
b.png.jpg
Code language: Bash (bash)
xargsは、次のようにコマンドを実行したんですね。ls から渡されたpngファイル名を2箇所で使っているので、--replaceオプションと "{}" 、"{}.jpg" が必要でした。
convert a.png -quality 85 a.png.jpg
convert b.png -quality 85 b.png.jpg
Code language: Bash (bash)
a.jpg ではなく、a.png.jpgになってしまったわ
はい、生成したjpgファイルを、スクリプトが処理するなら、a.png.jpgでも問題ないと思いますが、人間が扱うなら、a.jpgのほうが自然かもしれません。
"a.png"から拡張子を削除して、".jpg"を追加して、convertする処理を、別ファイル cvt_jpg85.sh にします。
#!/bin/bash -ue
png=$1
jpg=${png%.*}.jpg
convert "$png" -quality 85 "$jpg"
Code language: Bash (bash)
cvt_jpg85.sh単体では、次のように実行します。
./cvt_jpg85.sh a.png
./cvt_jpg85.sh b.png
Code language: Bash (bash)
このような、コマンドと1つの引数だけの場合は、xargsの--replaceオプションが不要です。
$ ls *.png | cat
a.png
b.png
$ ls *.png | xargs -l ./cvt_jpg85.sh
$ ls *.jpg | cat
a.jpg
b.jpg
Code language: Bash (bash)
--replaceオプションや{}がなくなって、すっきりしたわ
findによるファイル一覧
次に、pngディレクトリ下にPNG画像、jpgディレクトリ下にJPEG画像のように分ける場合です。
今回はfindコマンドとwhile readコマンドを使います。
findコマンドはファイル検索コマンドで、検索開始ディレクトリと -nameオプションでファイル名パターンを指定します。"*.png" のようにワイルドカードを指定するときは、ダブルクォートで囲んでください。
findコマンドは検索結果を1行1ファイルで表示します。パス名も含んでいます。
$ find png -name "*.png"
png/img 1.png
png/img 2.png
Code language: Bash (bash)
このpngファイル一覧から、次のコマンドの実行につなげたいわけです。
convert "png/img 1.png" -quality 85 "jpg/img 1.jpg"
convert" png/img 2.png" -quality 85 "jpg/img 2.jpg"
Code language: Bash (bash)
while readによる繰り返し
while文とreadコマンドを使います。
while read pngの最後のpngは変数名です。1行づつ読みこんで、変数pngに設定して、do〜doneを処理します。
cvt_all_png_to_jpg.sh
#!/bin/bash -ue
dst_dir=jpg
mkdir -p $dst_dir
find png -name "*.png" | while read png
do
name=$(basename "$png")
jpg=$dst_dir/${name%.*}.jpg
convert "$png" -quality 85 "$jpg"
done
Code language: Bash (bash)
basenameコマンドは、パスからディレクトリ部分を削除して、拡張子を含むファイル名部分だけを表示するコマンドです。
$ basename "png/a.png"
a.png
Code language: JavaScript (javascript)
ここでは使っていませんが、dirnameコマンドは、パスのディレクトリ部分だけを表示します。
dirname、basenameもよく使うわね
$ dirname "png/a.png"
png
Code language: JavaScript (javascript)
ファイル名から拡張子を削除するには、${name%.*} とします。拡張子を取り出すには、${name##*.} とします。
$ name="a.b.c.png"
$ echo $name
a.b.c.png
$ echo ${name%.*}
png
$ echo ${name##*.}
a.b.c
Code language: PHP (php)
呪文みたいだね
cvt_all_png_to_jpg.shを実行してみます。jpgディレクトリ下にjpgファイルができました。
$ ls png/ | cat
a.png
b.png
$ ./cvt_all_png_to_jpg.sh
$ ls jpg/ | cat
a.jpg
b.jpg
Code language: Bash (bash)
配列とfor in による繰り返し
ディレクトリ名やファイル名にスペースを含まない場合、配列とfor inを使うことができます。
cvt_all_png_to_jpg_forin.sh
#!/bin/bash -ue
dst_dir=jpg
mkdir -p $dst_dir
png_arr=($(find png -name "*.png"))
echo "png_arr:" ${png_arr[@]}
echo "length:" ${#png_arr[@]}
for png in ${png_arr[@]}
do
name=$(basename "$png")
jpg=$dst_dir/${name%.*}.jpg
convert "$png" -quality 85 "$jpg"
done
Code language: Bash (bash)
配列は、ディレクトリ名やファイル名にスペースを含むと、その途中のスペースで区切ってしまいます。
$ ls png/ | cat
img 1.png
img 2.png
$ png_arr=($(find png -name "*.png"))
$ for png in ${png_arr[@]}; do echo $png; done
png/img
1.png
png/img
2.png
Code language: Bash (bash)
きっと対応方法はあるんでしょうけど、わからないので、スペースを含む場合は、while readを使っています。