シェルスクリプトで自動インストールスクリプトを作成する際の注意点メモ

よくミスるのでメモです。

手動手順を自動実行するスクリプトのエラーを確実に検出する点で、ソフトウェアテストの小ネタの仲間に入れて下さい。。。（思想的には共通部分があるはず、、）


ホスト環境に開発環境構築する場合などを想定

# `sudo apt-get update`時にパスワードを聞かれないようにする（参考）

sudoersを更新することで可能

```
$ sudo visudo    # 以下の行に追加する
# Allow members of group sudo to execute any command
%sudo  ALL=(ALL:ALL) NOPASSWD:ALL
```

上記だと、セキュリティ的に良くないことも多いと思うので、`username`を指定する場合は以下

```
username ALL=NOPASSWD: ALL
```

もしくは、`sudo -S`でパスワードをパイプで受け取る

```
echo 'passwd' | sudo -S apt-get update
```

# 対話処理は先に実行しておく

例えばOpenCVビルド時は対話処理が必要なので先に実行しておく

```
sudo apt-get update -y
sudo apt-get install -y gdm3  ## 対話ウィンドウが出たら --> OK --> gdm3 を選択
```

```
cd ~
git clone https://github.com/mdegans/nano_build_opencv
cd nano_build_opencv
./build_opencv.sh 3.4.10
```

# `yes/no`が聞かれる場合に`y/n`を自動入力

`yes``no`コマンドを使う

```
yes | ./build_opencv.sh 3.4.10
```

`yes`はずっと`y`を出力する

```
$ yes
y
y
y
...
```

# エラー発生時はエラー終了させる

１部だけエラーになっても素通りすると後から分かりづらいので、エラー側に倒す

#### `trap``set -E`を使うと便利

```shell
# エラーが起こったら異常終了させる
set -E

function failure(){
    echo "error end!!"
    exit 1
}

# エラー発生時にコールする関数を設定 
trap failure ERR
```

#### `|`を使ってパイプで結果を渡す途中のエラーも検知する場合は、`set -eo pipefail`を設定すると便利

```shell
#!/bin/bash
set -eo pipefail

sl | ls
echo "don't show this comment"
```

#### 部分的にエラー処理したい場合は、`$?`で結果を判断しても良いかもしれないです。

```shell
ls test.txt
RESULT=$?   # no such file or firectoryのとき1がはいる
if [ $RESULT -ne 0 ];then
    echo "no file"
fi
```

#### エラー関数がある場合は、`||`で結果を渡しても良いかもしれないです。

```shell
function func_error(){
    echo "error"
}
ls test.txt || func_error # no such file or firectoryのときfunc_errorが実行される
```

# 前処理で`rm -r`する場合はエラーにならないようにする

`rm -rf`だと、指定したdirectoryがない時にエラーにならない

```shell
$ sudo rm -rf directory_name
$ echo $?
0
```

`rm -r`だと、指定したdirectoryがない時にエラーになる

```shell
$ sudo rm -r directory_name
rm: directory_name: No such file or directory
$ echo $?
1
```

# `$HOME/.bashrc`にリダイレクトする場合

`$HOME/.bashrc`読み込み時に実行したいコマンドがある場合は`'シングルクオーテーション'`で囲む

```shell
echo 'eval `dbus-launch --sh-syntax`' >> $HOME/.bashrc
```

逆に、`$HOME/.bashrc`へのリダイレクト時に実行したいコマンドがある場合は`"ダブルクオーテーション"`で囲む

```shell
echo "echo `date`" >> $HOME/.bashrc
```

`'シングルクオーテーション'`の場合は文字列のまま、`"ダブルクオーテーション"`の場合はコマンドが実行された結果が展開される。

```
$ echo 'echo `date`'
echo `date`
$ echo "echo `date`" 
echo 2020年 11月21日 土曜日 23時22分15秒 JST
```

# ライブラリバージョンチェック

厳密にライブラリバージョンの一致が必要な場合は、チェックをしても良さそう。

```
# ex. opencv pythonのバージョンを調べる場合
python3 -c "import cv2 ;print(cv2.__version__);"
```

# 複数回実行時にエラーにしない

インストール手順アップデートなどにより、、同じスクリプトを複数回実行する場合がある。
この場合に異常側になるべく倒さない方が便利な時もある。
例えば、既にrosをインストールしている場合はスキップ側に倒すなどした方がいい時もある。。

```
function install_ros(){
    # check if already install ros
    rosversion -d
    ret=$?
    if [ $ret -eq 0 ];then
       echo "ros already installed, skip install ros"
       return 0
    fi

    # install ros
    # ...
}
```

ただし`-e`指定してる場合はエラーになるので、この場合は`if`の中に含むとよい

```shell
#!/bin/bash

set -e

if [ ! -z `env | grep "ROS_DISTRO"` ];then
    echo "ROS_DISTRO found"
else
    echo "ROS_DISTRO not found"
fi
```

環境変数よりコマンド実行結果をみた方が良い場合もある

```shell
#!/bin/bash

set -e

if [ ! -z `rosversion -d` ];then
    echo "ROS_DISTRO found"
else
    echo "ROS_DISTRO not found"
fi
```

# 周辺パッケージアップデートの影響をなるべく回避する

開発が活発なライブラリを使う場合は、アップデート時のデグレを引くこともある。
branch指定、もしくは動作確認時点のバージョン指定により回避する。

```
git clone http://github.com/test/repository
git checkout xxx  # branch指定、もしくは動作確認時点のバージョン指定
```

機械学習ライブラリ系は、特に変化が大きいのでバージョン固定する方がよさそうです。
但し、周辺パッケージの変化に気付き難くなるので一概に解決とは言えない。。

# その他

何かより良い方法がありましたらアドバイス頂けると幸いです。

# 参考
[sudo のパスワードを入力なしで使うには](https://qiita.com/RyodoTanaka/items/e9b15d579d17651650b7)
[シェルスクリプトでset -eしているときに処理を中断せずエラーを扱う方法](https://sousaku-memo.net/php-system/1164)