🐚

【Bash】ツール作りでよく使う機能チートシート

に公開

はじめに

  • ミニツール作成で何度も書く機能があるため、自分用のメモとして作成しました。

スクリプト実行時のパスを変数に格納する

  • スクリプト実行時、パスの変更が起こる前に、
CURRENT_DIR="${pwd}"

ルート権限で実行しているか確認する

if [ "$(id -u)" -ne 0 ]; then
  echo "This script must be run as root. Please use sudo or switch to the root user."
  exit 1
fi
  • lshw/var/log以下のファイルを確認したいときなど、ルート権限がないと正常に動作しないときに使用

引数が足りない場合に処理を終了する

if [ $# -ne 2 ]; then
  echo "Usage: $0 <path_1> <path_2>"
  exit 1
fi
  • $0で起動中のシェルスクリプト名を出力できる

引数を変数に格納する

PATH_1="$1"
PATH_2="$2"

データを配列にマッピングする

例: 改行によって区切られたデータが入っている./exapmles.txtをマッピングする

mapfile -t CMDS < ./examples.txt

マッピング後の配列に対して順番に処理を実行する場合の書き方は以下のようになる

for cmd in "${CMDS[@]}"; do
...
done

処理が完了するのを待つ

同期処理の場合

cp ./something1.sh ./something2.sh &
wait
echo "copied"
  • 子プロセスのexit後に次の処理を実行する書き方
    • 以下のような処理(同期処理)の場合はこの書き方で処理完了を待てる
      • ファイル操作(cp, rmなど)
      • テキスト処理(grep, sedなど)
      • パッケージインストールコマンド(apt installなど)
      • 普通のシェル組み込み関数
  • ほか、systemd serviceのうちType=oneshotと書かれているものはコマンドの終了 = 起動のメインプロセスの終了であるため、このコマンドで処理完了をキャッチできる

非同期処理の場合

until systemctl is-active --quiet something.service; do
  sleep 1
done
  • コマンドのexit = 処理完了ではないことがありうる場合は上記のようにuntilwhileを使う
TIMEOUT=30
while ! systemctl is-active --quiet something.service; do
  if [ "$TIMEOUT" -le 0 ]; then
    echo "Timeout."
    exit 1
  fi
  sleep 1
  (( TIMEOUT-- ))
done
  • 一定時間でタイムアウトさせたい場合はこのように書く

日時情報を入れた文字列を生成する

timestamp=$(date +"%Y%m%d_%H%M%S")
log_file="logCollect_${timestamp}.log"
  • ログファイルを各回別々に生成する時や1つのログファイルに追記していくときに使用

標準出力と同じものをログに書き込む

exec > >(tee -a "$log_file") 2>&1
  • これを最初に書いておけば、ほぼ同内容のスクリプトを標準出力とログ出力で書き分ける必要がなくなり、コードが見やすくなる

使う例

exec > >(tee -a "./sample.txt") 2>&1
echo -e "\n--- System Information ---"
uname -a
cat /etc/os-release
...

使わない例

echo -e "Collecting system information..." | tee -a "$log_file"
system_info=$(uname -a)
echo "$system_info" | tee -a "$log_file"
os_release=$(cat /etc/os-release)
echo -e "OS release info:\n$os_release" | tee -a "$log_file"
...

エラーがあったらスクリプトを即終了する

set -euo pipefail
  • uオプション: 未定義の変数を参照した際にエラーを発生させる
  • eオプション: スクリプト内で終了コードが0以外になった(コマンドが失敗した)場合、即座にスクリプトを終了する
  • oオプション + pipefailフラグ: pipeでつないだコマンドの終了ステータスを「最後のコマンドの終了ステータス」から「エラーになった最後のコマンドの終了ステータス」に変更する
    • エラーになったコマンドがなければ0が返る
  • 破壊的な変更を及ぼすスクリプトの場合や開発中に使える

エラーがあってもスクリプトを続行する

systemctl restart XXX || true
  • 失敗した場合でも構わず続行していい場合(ログ収集が目的の場合など)やエラーハンドリング不要な箇所の場合に使える

ユーザに確認してから処理する

read -p "Do you want to proceed the reset? [y/N]: " ans
if [ "$ans" =~ ^[Yy]$ ]; then
  echo "Proceeding..."
  # 後続の処理
else
  echo "Canceled."
fi
  • pオプション: 質問文(後続の引用符部分)を画面に表示する
    • 破壊的な変更をする前に出力するとユーザーフレンドリー

実行したコマンドのPIDを格納する

command_A &
A_PID=$! # command_AのPIDが格納される
kill $A_PID

スクリプトの終了時に特定の処理を行う

cleanup() {
...
}

trap cleanup SIGINT SIGTERM

参考

https://qiita.com/YumaInaura/items/e3a19c6e2815d7deee33
https://qiita.com/bsdhack/items/4f408b8b856a54e1c1b9
https://qiita.com/qiita2021user/items/d5076be602b80b5d95b2
https://atmarkit.itmedia.co.jp/ait/articles/1705/26/news013.html
https://kitsunetanukidashi.hatenablog.com/entry/2014/07/12/231833
https://qiita.com/ko1nksm/items/44bef2b8487a07e7a870
https://qiita.com/a_yasui/items/ec4f75b300410af8958d
https://qiita.com/blueskyarea/items/b96c5b6cae02db4b9665
https://tech.kurojica.com/archives/51690/

Discussion