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

整数の四則演算

整数の四則演算は、式を$(( )) で囲みます。$(()) の内側は、スペースを含んでも大丈夫です。

$ echo $((1 + 2)) 3 $ echo $(( 1 + 2 )) 3 $ echo $(( (1 + 2) * 3 )) 9

引き算の結果を変数に設定します。

$ x=$((1 - 2)) $ echo $x -1

変数同士の四則演算もできます。

$ a=2 $ b=3 $ x=$(($a * $b)) $ echo $x 6
演算子意味参考
+足し算$((1 + 2))
-引き算$((1 - 2))
*掛け算$((1 * 2))
/割り算$((1 / 2))
%余り$((1 % 2))

残念ながら、小数点の計算はできません。エラーになります。

$ echo $((1.2 + 1.3)) bash: 1.2 + 1.3: 構文エラー: 無効な計算演算子です (エラーのあるトークンは ".2 + 1.3")

小数点の計算をしたいときは、どうしたらいのかな?

次のbcコマンドで説明するわ

比較演算もできます。

$ echo $((1 <= 2)) 1 $ echo $((1 > 2)) 0 $ echo $((1 == 2)) 0 $ echo $((1 != 2)) 1
演算子意味参考
==等しいequal
!=異なるnot equal
>左辺は右辺より大きいgreater than
>=左辺は右辺以上greater than or equal
<左辺が右辺未満less than
<=左辺は右辺以下less than or equal

ビット演算もできます。AND演算(論理積)、OR演算(論理和)、XOR演算(排他的論理和)です。

$ echo $((1 & 2)) 0 $ echo $((1 | 2)) 3 $ echo $((1 ^ 1)) 0
演算子意味参考
&AND、論理積$((1 & 2))
|OR、論理和$((1 | 2))
^XOR、排他的論理和$((1 ^ 2))

なんでもできるんだね

難読スクリプトになりやすいから、ほどほどにね

小数点の計算

小数点の計算をしたいときは、bcコマンドを使います。計算式を標準入力で渡します。

bcって何の略かな?

英語版wikipediaには、
basic calculator(基本的な計算機)
bench calculator(卓上計算機)
とあるわね

$ echo "1.2 + 2.5" | bc 3.7

結果を変数に設定したいときは、$() で囲みます。

$ x=$(echo "1.2 + 2.5" | bc) $ echo $x 3.7

割り算の小数点の桁数を指定するには、scaleを指定します。

$ a=10 $ b=3 $ echo "scale=2; $a / $b" | bc 3.33

割り算以外の小数点の桁数を指定するには、scaleを指定して、最後に1で割ります。

$ a=10 $ b=3 $ echo "scale=2; $a + $b" | bc 13 $ echo "scale=2; ($a + $b) / 1" | bc 13.00

整数の比較

if文の条件式で比較をする場合、文字列として比較するのか、整数として比較するのか、で演算子が違います。小数点を比較すると、エラーになります。

$a=10 $b=20 if [ $a -eq $b ]; then echo "equals" else echo "not equals" fi

if [$a -eq $b];は
[10: コマンドが見つかりません
になってしまったよ

[]の内側は、スペースが必要よ。

演算子意味参考
-eq等しいequal
-ne異なるnot equal
-gt左辺は右辺より大きいgreater than
-ge左辺は右辺以上greater than or equal
-lt左辺が右辺未満less than
-le左辺は右辺以下less than or equal

小数点の比較

bcコマンドを使います。条件が成立すれば、1を、成立しなければ、0を返します。

$ a=1.1 $ b=0.9 $ c=$("$a < $b | bc) $ echo $c 0

等しいは、"="を2個

C言語の比較演算子と同じね

演算子意味参考
==等しいequal
!=異なるnot equal
>左辺は右辺より大きいgreater than
>=左辺は右辺以上greater than or equal
<左辺が右辺未満less than
<=左辺は右辺以下less than or equal

文字列の比較

まず、等しいか、異なるか、長さが0か、1以上かを調べます。

if [ "A" = "A" ]; then echo "equals" fi if [ "A" != "B" ]; then echo "not equals" fi if [ "A" ]; then echo "length > 0" fi if [ -n "A" ]; then echo "length > 0" fi if [ -z "" ]; then echo "length == 0" fi

変数を比較するときは、ダブルクォートで囲みます。

a="A" b="A" if [ "$a" == "$b" ]; then echo "equals" fi

= も使えたよ?

=、==、どちらも使えるのよ

文字列の比較は、[ ] ではなくて、[[ ]] で囲みます。

ASCIIコード順で大小を調べます。大文字どうしなら "A" < "Z"、小文字どうしなら "a" < "z" です。

a="aaa" b="bbb" if [[ "$a" < "$b" ]]; then echo "$a < $b" fi if [[ "$a" > "$b" ]]; then echo "$a > $b" fi

"Z" と "a" と比較すると、"Z" のほうが小さいけど?

はい、ASCIIコード順では、大文字のほうが小さいので、"Z" < "a" です。"a" を "Z" より小さいと判断するためには、2つの文字列を大文字に揃えるか、小文字に揃えます。

大文字にするには、${a^^}、小文字にするには、${a,,} を使います。

a="ZZZ" b="aaa" if [[ "${a}" < "${b}" ]]; then echo "${a} < ${b}" else echo "${a} >= ${b}" fi if [[ "${a^^}" < "${b^^}" ]]; then echo "${a^^} < ${b^^}" else echo "${a^^} > ${b^^}" fi if [[ "${a,,}" < "${b,,}" ]]; then echo "${a,,} >= ${b,,}" else echo "${a,,} >= ${b,,}" fi

<= や >= はあるのかな?

残念ながら、ないわね

正規表現と一致しているかを調べることもできます。右辺の正規表現は、ダブルクォートで囲みません。また、// でも囲みません。

正規表現で一致した結果は、BASH_REMATCH配列に入ってきます。BASH_REMATCH[0]が、一致した全体。BASH_REMATCH[1]がカッコで囲った部分一致です。

s=hoge1234 if [[ $s =~ ^[a-z]+([0-9]*)$ ]]; then echo "match" echo ${BASH_REMATCH[0]} # hoge1234 echo ${BASH_REMATCH[1]} # 1234 fi if [[ $s =~ ([a-z]+)([0-9]+) ]]; then echo "match" echo ${BASH_REMATCH[0]} # hoge1234 echo ${BASH_REMATCH[1]} # hoge echo ${BASH_REMATCH[2]} # 1234 fi
演算子意味参考
= または ==左右の文字列が等しい
!=左右の文字列が異なる
-n文字列の長さが1以上
-z文字列の長さが0
<左辺が右辺より小さい
>左辺が右辺より大きい
=~右辺が正規表現

文字列置換

ファイルパスのディレクトリ部分を取り出したいなら、dirnameコマンド

$ dirname "/home/taro/xxx/yyy.txt" /home/taro/xxx $ dir=$(dirname "/home/taro/xxx/yyy.txt") $ echo $dir /home/taro/xxx

ファイルパスのファイル名(拡張子を含む)を取り出したいなら、basenameコマンド

$ basename "/home/taro/xxx/yyy.txt" yyy.txt $ fname_ext=$(basename "/home/taro/xxx/yyy.txt") $ echo $fname_ext yyy.txt

ファイル名の拡張子を除く部分を取り出したいとき、${変数%.*}

$ fname_ext=$(basename "/home/taro/xxx/yyy.zzz.txt") $ fname=${fname_ext%.*} yyy.zzz

ファイル名の拡張子を取り出したいとき、${変数##*.}

$ fname_ext=$(basename "/home/taro/xxx/yyy.zzz.txt") $ ext=${fname_ext##*.} txt $ fname_ext=$(basename "/home/taro/xxx/yyy_zzz_txt") $ ext=${fname_ext##*.} yyy_zzz_txt $ fname=${fname_ext%.*} yyy.zzz

ファイル名の拡張子を取り出したいとき、ピリオドを含まない場合があるとき、

$ fname_ext=$(basename "/home/taro/xxx/yyy_zzz_txt") $ echo $fname_ext yyy_zzz_txt $ ext=${fname_ext##*.} $ echo $ext yyy_zzz_txt $ fname=${fname_ext%.*} $ echo $fname yyy_zzz_txt $ ext=${fname_ext/$fname/} $ echo $ext (カラ文字列)

ピリオドを含まないケースは考えなくてもいいんじゃない?

作っているシェクスクリプトの汎用性によるわね。
自分しか使わないなら、ls *.png や find . -name "*.png"で絞り込んでおいたらどうかしら。

拡張子pngをjpgに置換したいとき、ディレクトリ名に".png"を含まないなら、

$ png_path="/home/taro/xxx/yyy.png" $ jpg_path=${png_path//.png/.jpg} echo $jpg
演算子/コマンド意味参考
dirnameディレクトリ部分を取り出すdirname "xx/yy"
basenameファイル名(拡張子を含む)を取り出すbasename "xx/yy"
${変数#パターン}文字列先頭からの最短マッチ部分を削除する${s##*.}
拡張子を取り出す
${変数##パターン}文字列先頭からの最長マッチ部分を削除する
${変数%パターン}文字列末尾からの最短マッチ部分を削除する${s%.*}
拡張子を削除する
${変数%%パターン}文字列末尾からの最長マッチ部分を削除する
${変数/検索文字列/置換}最初にマッチした箇所を置換する${s/.png/.jpg}
${変数//検索文字列/置換}マッチした全ての箇所を置換する${s//A/a}

ファイルテスト

通常ファイルの存在チェックは、

if [ -f "myfile.txt" ]; then echo "-f myfile.txt" fi

ディレクトリの存在チェックは、

if [ -d "tmp" ]; then echo "-d tmp" fi

シンボリックリンクの存在チェックは、

if [ -L "myfile.txt" ]; then echo "-L myfile.txt" fi

存在していないファイルをrmしようとすると、「を削除できません: そのようなファイルやディレクトリはありません」と表示されてしまいます。

そのために存在チェックしてから削除しているのなら、存在チェックせずに、rm -f してみください。ファイルがない場合に、rm -f しても、さきほどのエラーメッセージは表示されません。

演算子意味参考
-eファイルが存在する-e "a.txt"
-f通常ファイルが存在する-f "a.txt"
-dディレクトリが存在する-d "logs"
-Lシンボリックリンクが存在する-L "a.txt"
-hハードリンクが存在する-h "a.txt"
-sファイルの長さが1以上-s "a.txt"
演算子意味参考
-r読み出し権限がある-r "a.txt"
-w書き込み権限がある-w "a.txt"
-x実行権限がある-x "a.sh"

プロンプトに自動入力する:expect

コマンドによっては、確認のため yかnを入力待ちになったり、ユーザ名やパスワードの入力待ちになることがあります。

ターミナルで手入力しているときは、y や n を入力すればいいのですが、cronやJenkinsで無人で定期処理しているときは、入力待ちのまま停止してしまいます。

このようなときは、expectコマンドを使います。標準ではインストールされていないので、インストールしてください。

Mac OSの場合

$ brew install expect

Ubuntuの場合

$ sudo apt isntall expect

架空のコマンド yes_noが、"(y/n):" とプロンプトを表示して、入力待ちになるとします。自動で "y" を入力するには、

#!/bin/bash -ue expect -c " set timeout 10 spawn yes_no expect \"(y/n):\" send \"y\n\" expect eof exit "

\が多くて読みにくいね

expect -c "〜" の文字列の中で、ダブルクォートを指定するためね。

spawnでコマンドを起動します。もしyes_noコマンドに引数123をつけて起動したいとき、spawn yes_no 123のように指定します。

spawn \"yes_no 123\"と指定すると、"yes_no 123"コマンドが見つからないエラーになります。

正しい spawn yes_no 123 間違い spawn \"yes_no 123\"

古いファイルを検索する

findコマンドの -mtimeを使います。

30日以上を経過した .logファイルを検索するには、

$ find . -mtime +30 -name "*.log"

30日以上経過した .logファイルを検索したいけれども、ディレクトリ"2020.log"もヒットしてしまうので、ディレクトリは除外するには、

$ find . -mtime +30 -name "*.log" -type f

10ファイルのうち、最新7ファイルを残して、古い3ファイルを削除するには?

ls -t で更新日時の新しい順で表示します。その一覧を8行目から表示します。tail --lines=8は、最後の8行を表示しますが、+をつけた tail --lines=+8 は、8行目以降を表示します。

$ ls -t | tail --lines=+8

ls -t の結果にディレクトリを含んでしまう場合、find の -type fでフィルターします。

$ ls -t | xargs -l find . -type f -name | tail --lines=+8

find の -type fを使わなくてもいいように、
ls -t "*.log" としたほうがシンプルね。

この結果を xargs や while read で処理します。

$ ls -t | tail --lines=+8 | xargs -l rm

rmするときは、くれぐれも注意してね

演算子意味参考
-name patternファイル名が一致する-name "*.log"
-path patternディレクトリを含めたパスが一致する-path "*/log/*"
-mtime +n更新日時が n * 24時間以上経過している-mtime +7
-type f通常ファイル-type f
-type dディレクトリ-type d
-type lシンボリックリンク-type l
タイトルとURLをコピーしました