累計学習時間と今日の学習時間を合計してくれるシェルスクリプトを作った

## はじめに

毎日、日報を書いていて、今日の学習時間と累計学習時間の合計を計算するのが面倒なので、そのためのシェルスクリプトを作りました。

## 作成したもの

### 引数として累計学習時間と今日の学習時間を入力する

厳密に考えれば、引数がちゃんとhhmm形式かどうかまでチェックすべきですが、用途が限られるのでそこまでやっていません。

```shell:add_times.sh
#!/bin/bash

# 引数があるかチェック
if [ -z $1 ]; then
    echo "第一引数として累計学習時間数をhhmm形式で入力してください"
    exit
fi

if [ -z $2 ]; then
    echo "第二引数として今日の時間数をhhmm形式で入力してください"
    exit
fi

# 引数から時間と分を抽出
time1=$(echo $1 | cut -dh -f1 | sed 's/^0\+\([1-9][0-9]*\)$/\1/')
min1=$(echo $1 | cut -dh -f2 | cut -dm -f1 | sed 's/^0\+\([1-9][0-9]*\)$/\1/')
time2=$(echo $2 | cut -dh -f1 | sed 's/^0\+\([1-9][0-9]*\)$/\1/')
min2=$(echo $2 | cut -dh -f2 | cut -dm -f1 | sed 's/^0\+\([1-9][0-9]*\)$/\1/')

# 時間と分を合計
total_time=$((time1 + time2))
total_min=$((min1 + min2))

# 分が60以上の場合は時間に補正
if [ $total_min -ge 60 ]; then
    total_time=$((total_time + total_min / 60))
    total_min=$((total_min % 60))
fi

# 結果を表示
echo "$(printf %02d $total_time)h$(printf %02d $total_min)m"
```

#### 使用例

```shell
$ ./add_times.sh 540h45m 3h25m
544h10m
```

###  改良版：引数として今日の学習時間だけを入力する

考えてみると、累計学習時間をいちいち手で打つのは面倒です。
別のテキストファイル`total_time.txt`に保存しておいてそれを読み取るようにしました。

```total_time.txt
540h45m
```

```shell:improved_add_times.sh
#!/bin/bash

# 累計時間数を記録しているテキストファイルのパスを指定
total_time_text_file_name="total_time.txt"

# テキストファイルが空でないかチェック
if [ ! -s $total_time_text_file_name ]; then
    echo "$total_time_text_file_name に累計時間数をhhmm形式で記載してから実行してください"
    exit
fi

# 累計学習時間の読み取り
read read_total_time < $total_time_text_file_name
time1=$(echo $read_total_time | cut -dh -f1 | sed 's/^0\+\([1-9][0-9]*\)$/\1/')
min1=$(echo $read_total_time | cut -dh -f2 | cut -dm -f1 | sed 's/^0\+\([1-9][0-9]*\)$/\1/')

# 引数チェック
if [ -z $1 ]; then
    echo "第一引数として今日の学習時間数をhhmm形式で入力してください"
    exit
fi

# 引数から今日の学習時間と分を抽出
time2=$(echo $1 | cut -dh -f1 | sed 's/^0\+\([1-9][0-9]*\)$/\1/')
min2=$(echo $1 | cut -dh -f2 | cut -dm -f1 | sed 's/^0\+\([1-9][0-9]*\)$/\1/')


# 時間と分を合計
total_time=$((time1 + time2))
total_min=$((min1 + min2))

# 分が60以上の場合は時間に補正
if [ $total_min -ge 60 ]; then
    total_time=$((total_time + total_min / 60))
    total_min=$((total_min % 60))
fi

# 結果を表示
result="$(printf %02d $total_time)h$(printf %02d $total_min)m"
echo $result

# 結果をファイルに保存
echo $result > $total_time_text_file_name
```

#### 使用例

```shell
$ ./improved_add_times.sh 3h25m
544h10m
```

`total_time.txt`も更新されます。

```total_time.txt
544h10m
```

## 解説

### Bashのif文のオプション

- ファイル・ディレクトリのオプション
    - `s`オプション：サイズが 0 より大きければ真
    - 上記のスクリプトでは"指定されたテキストファイルが空でなければ真"となる

- 文字列長のオプション
    - `z`オプション：文字列長が 0 なら真
    - 上記のスクリプトでは"引数の文字が0字以上なら真"となる

- 数値の比較オプション
    - `ge`オプション：以上なら真
    - 上記のスクリプトでは"分の合計が60以上なら真"となる

#### 参考

https://qiita.com/wakayama-y/items/a9b7380263da77e51711


### Bashの算術演算

Bashの中で計算を行うには`(())`で囲みます。

#### 参考

https://qiita.com/Ping/items/f9b5175085026462b082#double-parentheses--

### cutコマンド

hhmm形式の時・分から時刻だけ・分数だけを取り出すためにcutコマンドを使用しています。
cutコマンドは、`d`オプションで区切り文字を設定し、`f`オプションで区切られた項目の中で表示したい項目が何番目かを指定します。

```shell
# hで区切って最初の項目を表示
$ echo 544h01m | cut -dh -f1
544

# hで区切って2番目の項目を表示 
$ echo 544h01m | cut -dh -f2
01m

# hで区切って2番目の項目を表示した後、さらにmで区切って最初の項目を表示
$ echo 544h01m | cut -dh -f2 | cut -dm -f1
01
```

####  参考

https://www.ibm.com/docs/ja/aix/7.1?topic=c-cut-command

### sedコマンド

cutコマンドで時・分だけを切り出せました。
しかし、このままだと0始まり（例：01）だと、足し算をしたときに8進数演算になってしまいます。
sedを利用して0始まりのときは先頭の0を取ります。

```bash
sed 's/^0\+\([1-9][0-9]*\)$/\1/'
```

- `sed 's/置換前/置換後'`
- `^0\+`：0の1回以上の繰り返し
- `[1-9][0-9]*`：0以外の数字で始まる数字
- `\(\)`：キャプチャグループを`\`でエスケープ
- `\1`：キャプチャグループにマッチした部分を取り出す

```bash
$ echo 01 | sed 's/^0\+\([1-9][0-9]*\)$/\1/'
1
$ echo 001 | sed 's/^0\+\([1-9][0-9]*\)$/\1/'
1
```

#### 参考

https://totech.hateblo.jp/entry/2014/12/05/082807


## 終わりに

bashはそんなに頻繁に書かないので、if文のオプションが全然覚えられなくて毎回調べている気がする。。まあ調べればわかるのでいいんですが。