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

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

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

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

$ convert a.png -quality 85 a.jpgCode language: Bash (bash)

lsによるファイル一覧

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

$ ls *.png
a.png    b.png    c.pngCode language: Bash (bash)

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

$ ls *.png | cat
a.png
b.png
c.pngCode 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.jpgCode language: Bash (bash)

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

convert a.png -quality 85 a.png.jpg
convert b.png -quality 85 b.png.jpgCode 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.pngCode 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.jpgCode 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.pngCode 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"
doneCode language: Bash (bash)

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

$ basename "png/a.png"
a.pngCode language: JavaScript (javascript)

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

dirname、basenameもよく使うわね

$ dirname "png/a.png"
pngCode language: JavaScript (javascript)

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

$ name="a.b.c.png"
$ echo $name
a.b.c.png
$ echo ${name%.*}
png
$ echo ${name##*.}
a.b.cCode 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"
doneCode 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.pngCode language: Bash (bash)

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

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