bashシェルスクリプト入門2

筆者がよく使うシェルスクリプトの処理をまとめました。

PNG画像を一括してJPEG画像に変換したいとき

PNG画像をJPEG画像に変換するには、ImageMagickのconvertコマンドを使います。a.pngをa.jpgに変換するには、次のように使います。

$ convert a.png -quality 85 a.jpg

lsによるファイル一覧

ターミナルで ls *.png とすると、ターミナルの幅をいっぱいに使って、1行あたり複数ファイルを表示します。

$ ls *.png a.png b.png c.png

ところが、標準出力をリダイレクトしたり、パイプに送ると、1行1ファイルで表示します。

$ ls *.png | cat a.png b.png c.png

次の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

xargsは、次のようにコマンドを実行したんですね。ls から渡されたpngファイル名を2箇所で使っているので、--replaceオプションと "{}" 、"{}.jpg" が必要でした。

convert a.png -quality 85 a.png.jpg convert b.png -quality 85 b.png.jpg

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"

cvt_jpg85.sh単体では、次のように実行します。

./cvt_jpg85.sh a.png ./cvt_jpg85.sh b.png

このような、コマンドと1つの引数だけの場合は、xargsの--replaceオプションが不要です。

$ ls *.png | cat a.png b.png $ ls *.png | xargs -l ./cvt_jpg85.sh $ ls *.jpg | cat a.jpg b.jpg

--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

このpngファイル一覧から、次のコマンドの実行につなげたいわけです。

convert "png/img 1.png" -quality 85 "jpg/img 1.jpg" convert" png/img 2.png" -quality 85 "jpg/img 2.jpg"

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

basenameコマンドは、パスからディレクトリ部分を削除して、拡張子を含むファイル名部分だけを表示するコマンドです。

$ basename "png/a.png" a.png

ここでは使っていませんが、dirnameコマンドは、パスのディレクトリ部分だけを表示します。

dirname、basenameもよく使うわね

$ dirname "png/a.png" png

ファイル名から拡張子を削除するには、${name%.*} とします。拡張子を取り出すには、${name##*.} とします。

$ name="a.b.c.png" $ echo $name a.b.c.png $ echo ${name%.*} png $ echo ${name##*.} a.b.c

呪文みたいだね

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

配列と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

配列は、ディレクトリ名やファイル名にスペースを含むと、その途中のスペースで区切ってしまいます。

$ 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

きっと対応方法はあるんでしょうけど、わからないので、スペースを含む場合は、while readを使っています。

タイトルとURLをコピーしました