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


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

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

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

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を自動入力

yesnoコマンドを使う

yes | ./build_opencv.sh 3.4.10

yesはずっとyを出力する

$ yes
y
y
y
...

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

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

trapset -Eを使うと便利

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

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

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

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

#!/bin/bash
set -eo pipefail

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

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

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

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

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

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

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

$ sudo rm -rf directory_name
$ echo $?
0

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

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

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

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

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

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

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の中に含むとよい

#!/bin/bash

set -e

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

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

#!/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 のパスワードを入力なしで使うには
シェルスクリプトでset -eしているときに処理を中断せずエラーを扱う方法