整数の四則演算
整数の四則演算は、式を$(( )) で囲みます。$(()) の内側は、スペースを含んでも大丈夫です。
$ echo $((1 + 2))
3
$ echo $(( 1 + 2 ))
3
$ echo $(( (1 + 2) * 3 ))
9
Code language: Bash (bash)
引き算の結果を変数に設定します。
$ x=$((1 - 2))
$ echo $x
-1
Code language: Bash (bash)
変数同士の四則演算もできます。
$ a=2
$ b=3
$ x=$(($a * $b))
$ echo $x
6
Code 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))
0
Code 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.7
Code language: Bash (bash)
結果を変数に設定したいときは、$() で囲みます。
$ x=$(echo "1.2 + 2.5" | bc)
$ echo $x
3.7
Code language: Bash (bash)
割り算の小数点の桁数を指定するには、scaleを指定します。
$ a=10
$ b=3
$ echo "scale=2; $a / $b" | bc
3.33
Code 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"
fi
Code 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/xxx
Code language: Bash (bash)
ファイルパスのファイル名(拡張子を含む)を取り出したいなら、basenameコマンド
$ basename "/home/taro/xxx/yyy.txt"
yyy.txt
$ fname_ext=$(basename "/home/taro/xxx/yyy.txt")
$ echo $fname_ext
yyy.txt
Code language: Bash (bash)
ファイル名の拡張子を除く部分を取り出したいとき、${変数%.*}
$ fname_ext=$(basename "/home/taro/xxx/yyy.zzz.txt")
$ fname=${fname_ext%.*}
yyy.zzz
Code 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.zzz
Code 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 $jpg
Code 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"
fi
Code language: Bash (bash)
ディレクトリの存在チェックは、
if [ -d "tmp" ]; then
echo "-d tmp"
fi
Code language: Bash (bash)
シンボリックリンクの存在チェックは、
if [ -L "myfile.txt" ]; then
echo "-L myfile.txt"
fi
Code 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 expect
Code language: Bash (bash)
Ubuntuの場合
$ sudo apt isntall expect
Code 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 f
Code language: Bash (bash)

10ファイルのうち、最新7ファイルを残して、古い3ファイルを削除するには?
ls -t で更新日時の新しい順で表示します。その一覧を8行目から表示します。tail --lines=8は、最後の8行を表示しますが、+をつけた tail --lines=+8 は、8行目以降を表示します。
$ ls -t | tail --lines=+8
Code language: Bash (bash)
ls -t の結果にディレクトリを含んでしまう場合、find の -type fでフィルターします。
$ ls -t | xargs -l find . -type f -name | tail --lines=+8
Code language: Bash (bash)

find の -type fを使わなくてもいいように、
ls -t "*.log" としたほうがシンプルね。
この結果を xargs や while read で処理します。
$ ls -t | tail --lines=+8 | xargs -l rm
Code 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 |