プロジェクト

全般

プロフィール

Bashシェルスクリプティング

スタイルガイド

シェルスクリプトのスタイルガイドの参照

Google Shell Style Guilde
https://google.github.io/styleguide/shellguide.html

シェル構文

for文

数値範囲

~$ for value in {0..10}; do echo $value; done
0
1
:
10
~$

~$ for suffix in {01..04}; do echo $suffix; done
01
02
03
04
~$

カウンタによる制御

for (( i=0; i < $max; i++ )); do
    echo $i
done
  • 丸括弧は2重指定

if文

条件の記述

[]で条件を記述 [[]]で条件を記述
if [ "$x" -gt 0 ]; then
    echo x is plus
fi
if [[ $x -gt 0 ]]; then
    echo x is plus
fi
[]はコマンド
* 変数参照をダブルクォートで囲わないと、変数未定義の時エラー
[[]] は構文

bashでは、[[]] を使う方が応用範囲が広いのでおすすめらしいです。

複数条件
  • if [[ $x -gt 0 && $x -lt 10 ]]; then
    [[]]の中でAND条件(&&)、OR条件(||)を記述できます。
正規表現
  • if [[ fruit =~ (apple|banana|cacao) ]]; then
    =~ で正規表現のマッチを記述できます。

関数

関数の定義と呼び出し

引数がない場合
関数の定義 関数の呼び出し
function foo () {
    echo foo called
}

foo
引数がある場合
関数の定義 関数の呼び出し
function foo () {
    echo foo called with argument $1
}

foo arg1

関数の中では、引数を$1, $2, ... で参照します。
関数の呼び出しでは、関数名の後に、引数を空白区切りで列挙します。

関数の戻り値

数値(0-255)を関数の戻り値として返却することができます。

関数の定義 関数の呼び出し
function foo () {
    return 10
}

foo arg1
echo $?

数値計算

算術式展開 $((算術式))

算術式を解釈、計算し、計算結果に置き換えてコマンドを実行します。

HOURS_OF_DAY=24
echo $((160 / HOURS_OF_DAY))

算術式の中で、変数は$なしで使用できます。整数同士の除算結果は小数点以下切り捨てで整数となります。(160.0と小数点表記の算術式を使うと小数点の結果が得られる)

算術式評価 ((算術式))

HOURS_OF_DAY=24
((DAYS = 160 / HOURS_OF_DAY))

ファイルに関する操作

パス名からファイル名を取り出し(最後の'/'より以降の文字列)

basename

~$ basename /path/to/alfa.omega
alfa.omega
~$
xargsでbasenameを使用しても意図した振る舞いとならない
~$ ls foo
1.dat 2.dat 3.dat
~$ find foo | xargs basename
1.dat

xargsで複数の引数をbasenameに渡しても、basenameが1つしか引数を取らないためです。
代替手段は、

  • ~$ find foo | awk -F/ '{print $NR}'
  • ~$ find foo -printf '%f\n'

文字列置換

前方一致最長マッチ

~$ f=/path/to/alfa.omega
~$ echo ${f##*/}
alfa.omega
~$

パス名からディレクトリ名を取り出し(最後の'/'の前までの文字列)

dirname

~$ dirname /path/to/alfa.omega
/path/to
~$

文字列置換

後方一致最短マッチ

~$ f=/path/to/alfa.omega
~$ echo ${f%/*}
/path/to
~$

パス名から拡張子を取り出し(最後の'.'より後ろの文字列)

~$ f=/path/to/alfa.omega
~$ echo ${f##*.}
omega
~$

パス名から拡張子を除いたファイル名(基底名)を取り出し

~$ f=/path/to/alfa.omega
~$ basename $f .omega
alfa
~$

ファイルの存在

実行可能なファイルの存在

[[ -x file ]]

ファイルの存在(ディレクトリは除外)

[[ -f file ]]

ディレクトリの存在

[[ -d directory ]]

ファイルまたはディレクトリの存在

[[ -e path ]]

ワイルドカードで指定したファイルの存在

  • ls でのワイルドカードは、該当ファイルが多数の時に Argument list too long が出る可能性がある
  • compgen -G "~/work/*.txt" を使うのが良さそう
if compgen -G "~/work/*.txt" > /dev/null; then
    echo "files are exist" 
fi

ディレクトリ操作

実行スクリプトのディレクトリを取得

$(cd $(dirname $0); pwd)
  • サブシェルで実行するので、続くシェルスクリプトのカレントディレクトリは元のまま

ディレクトリの存在を確認

if [[ -d $dir ]]; then
    echo "$dir is a directory." 
else
    echo "$dir is NOT a directory." 
fi

日時

日付・時刻の取得

dateコマンド

今の日時を指定の書式で出力します。

date <+書式>

記号 意味
%Y 西暦4桁
%m 月2桁 (01-12)
%d 日2桁 (01-31)
%H 時2桁 (00-23)
%M 分2桁 (00-59)
%S 秒2桁 (00-59)
%s UNIX時刻の秒(1970年1月1日0時UTCからの経過秒数)

日付・時刻の計算

T.B.D.

文字列

一致

正規表現

if [[ "ABCXYZ" =~ ^[A-Z]*Z$ ]];

  • 左辺に文字列、=~ で右辺に正規表現式を記述すると、正規表現に一致したかどうかの評価が得られる

コマンドライン

コマンドライン引数の取り込み

コマンドライン引数は、$1, $2, ... に格納されます。
コマンドライン引数が幾つ渡されたかを確認するには、$# を参照します。

if [[ $# != 2 ]]; then
    echo Usage: $0 <src> <dst>
    exit 1
fi

cp -p $1 $2

コンソール出力

表示制御

スピナーのような表示

コンソールのある箇所で、くるくると一定周期で文字を変えて、スピナー表示をします。

function spinner() {
    local spinner_chars="/-\|" 
    for (( i=0; i<${#spinner_chars}; i++ )); do
        sleep 1
        echo -en "${spinner_chars:$i:1}" "\r" 
    done
}
  • spinner_charsに、一定周期で表示させたい文字を羅列
  • spinner_charsに定義した文字数を ${#spinner_chars} で取得し、for文で文字数だけループ
  • 周期を1秒とする(sleep 1)、小数点指定もできるので、もっと早い周期で回したいときは、sleep 0.5のように指定
  • echo -en で、エスケープ文字を有効に(\nや\rなど)、-nで改行なしとし、
  • 表示する文字を変数から切り出し。${変数名:オフセット:長さ}の書式で指定、ループ変数でオフセットを変化し毎回文字を変更

このspinnerは、1周で終わるので、適宜繰り返し呼び出します。

権限

実行権限

root権限で実行されているかを判定

  if [[ ${EUID}:-${UID}} != 0 ]]; then
    echo "root accessibility is required for running this script" 
  fi

書き方

コマンドスクリプト

コマンドラインから実行するスクリプトの記載例。主にGoogle Shell Style Guideに準拠。

  • ファイル名は、スネークケース(小文字+アンダースコア)
  • インデントは、空白2文字(TABは使用しない)
  • 桁数は1行80文字 *

sample_command.sh

#!/bin/bash
# ファイルコメント
# スクリプトの概要を説明
シェバンは、bashを明示的に指定
set -eu -o pipefail
エラー耐性の向上
-e: 実行したコマンドがエラーの場合中断
-u:未定義変数を使うとエラー
-o pipefail: パイプの全てのコマンドを確認(-eはパイプの最後のコマンドのみ確認)
# 定数定義
readonly SOME_PATH='/data/something'
スクリプト全体の定数はreadonlyを付けて全て大文字のスネークケース(screaming snake case)
# グローバル変数定義
total_count=0
スクリプト全体の変数は小文字のスネークケース
##############################
# count files in data directory
# Globals:
#   SOME_PATH
#   total_count
# Arguments:
#   None
##############################
function count() {
  local num=$(find $SOME_PATH -type f | wc -l)
  total_count=$total_count + $num
}
関数名は小文字のスネークケース
関数コメントは、先頭に処理概要、Globals:に使用するグローバル変数、Arguments:に引数の有無とある場合はそれらの説明を記述。
標準/エラー出力を行う場合はOutputs:に記述、リターン値がある場合Returns:に記述。
関数ローカルな変数は、localをつけて小文字スネークケース

参考文献

ブログ

bashスクリプトのベストプラクティスを調査した


7日前に更新