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

整数の四則演算

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

$ echo $((1 + 2))
3

$ echo $(( 1 + 2 ))
3

$ echo $(( (1 + 2) * 3 ))
9
Code language: Bash (bash)

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

$ x=$((1 - 2))
$ echo $x
-1Code language: Bash (bash)

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

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

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

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

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

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

比較演算もできます。

$ echo $((1 <= 2))
1

$ echo $((1 > 2))
0

$ echo $((1 == 2))
0

$ echo $((1 != 2))
1
Code language: Bash (bash)
演算子意味参考
==等しい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))
0Code language: Bash (bash)
演算子意味参考
&AND、論理積$((1 & 2))
|OR、論理和$((1 | 2))
^XOR、排他的論理和$((1 ^ 2))

なんでもできるんだね

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

小数点の計算

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

bcって何の略かな?

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

$ echo "1.2 + 2.5" | bc
3.7Code language: Bash (bash)

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

$ x=$(echo "1.2 + 2.5" | bc)
$ echo $x
3.7Code language: Bash (bash)

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

$ a=10
$ b=3

$ echo "scale=2; $a / $b" | bc
3.33Code language: Bash (bash)

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

$ a=10
$ b=3

$ echo "scale=2; $a + $b" | bc
13

$ echo "scale=2; ($a + $b) / 1" | bc
13.00
Code language: Bash (bash)

整数の比較

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

$a=10
$b=20

if [ $a -eq $b ]; then
  echo "equals"
else
  echo "not equals"
fiCode language: Bash (bash)

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
Code language: Bash (bash)

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

a="A"
b="A"

if [ "$a" == "$b" ]; then
  echo "equals"
fi
Code language: Bash (bash)

= も使えたよ?

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

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

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
Code language: Bash (bash)

"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
Code language: Bash (bash)

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

残念ながら、ないわね

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

正規表現で一致した結果は、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
Code language: Bash (bash)
演算子意味参考
= または ==左右の文字列が等しい
!=左右の文字列が異なる
-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/xxxCode language: Bash (bash)

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

$ basename "/home/taro/xxx/yyy.txt"
yyy.txt

$ fname_ext=$(basename "/home/taro/xxx/yyy.txt")
$ echo $fname_ext
yyy.txtCode language: Bash (bash)

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

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

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

$ 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.zzzCode language: Bash (bash)

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

$ 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

(カラ文字列)Code language: Bash (bash)

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

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

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

$ png_path="/home/taro/xxx/yyy.png"
$ jpg_path=${png_path//.png/.jpg}
echo $jpgCode language: Bash (bash)
演算子/コマンド意味参考
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"
fiCode language: Bash (bash)

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

if [ -d "tmp" ]; then
  echo "-d tmp"
fiCode language: Bash (bash)

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

if [ -L "myfile.txt" ]; then
  echo "-L myfile.txt"
fiCode language: Bash (bash)

存在していないファイルを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 expectCode language: Bash (bash)

Ubuntuの場合

$ sudo apt isntall expectCode language: Bash (bash)

架空のコマンド 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
"Code language: Bash (bash)

\が多くて読みにくいね

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\"
Code language: Bash (bash)

古いファイルを検索する

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

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

$ find . -mtime +30 -name "*.log"Code language: Bash (bash)

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

$ find . -mtime +30 -name "*.log" -type fCode language: Bash (bash)

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

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

$ ls -t | tail --lines=+8Code language: Bash (bash)

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

$ ls -t | xargs -l find . -type f -name | tail --lines=+8Code language: Bash (bash)

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

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

$ ls -t | tail --lines=+8 | xargs -l rmCode language: Bash (bash)

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をコピーしました