プロジェクト

全般

プロフィール

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
    =~ で正規表現のマッチを記述できます。

コマンドの終了ステータスを判定

if command; then ... のように、角括弧なしに記載できます。
パイプの使用も可能です。if cmd1 | cmd 2; then ...
失敗(0以外)を真とする場合は、if ! cmd1; then ... と記述します。

case文

関数

関数の定義と呼び出し

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

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

foo arg1

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

関数の戻り値

標準出力を受け取る方法と、コマンド実行結果として値を受け取る方法があります。

  • 関数内で標準出力した文字列を戻り値として受け取ることができます。
  • 数値(0-255)を関数の戻り値として返却することができます。return文で値を返却、関数呼び出し側は、コマンド実行結果と同じく$? で値を取得します。
標準出力
関数の定義 関数の呼び出し
function foo () {
    echo 10
}

result=$(foo)
コマンド実行結果
関数の定義 関数の呼び出し
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$ ]];

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

データ構造

連想配列

連想配列の宣言

書式
declare -A 配列名

使い方

declare -A maxDate
maxDate[January]=31
maxDate[February]=28

コマンドライン

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

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

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

cp -p $1 $2

コンソール出力

標準出力と標準エラー出力

通常のメッセージは標準出力を使用し、エラーメッセージは標準エラー出力を使用します。

エラー出力の例

echo "Error in processing" >&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文字

関数を定義してモジュール性のあるコードを記述します。
スクリプトの使い方(コマンドラインインタフェース)を説明するusage関数を定義し、ヘルプまたは誤ったコマンドラインオプションが指定されたときに使い方を表示します。
main関数を定義し、スクリプトを実行したときに最初に呼ばれる関数とします。
変数はグローバルとローカル(関数内)変数を意識し、なるべく不変とします。

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
スクリプト全体の変数は小文字のスネークケース
##############################
# print usage of command and exit
# Arguments:
#   None
# Outputs:
#   Writes usage to stdout
##############################
function usage() {
  cat << EOF
  Usage: $0 [target]
  target  specify target to count
EOF
  exit 1
}
関数名は小文字のスネークケース。
コマンドの使い方を標準出力する関数。複数行に渡る出力なのでヒアドキュメントを使うのが定番。
##############################
# count files in data directory
# Globals:
#   SOME_PATH
# Arguments:
#   None
##############################
function count() {
  local num=$(find $SOME_PATH -type f | wc -l)
  total_count=$total_count + $num
}

関数コメントは、先頭に処理概要、Globals:に使用するグローバル変数、Arguments:に引数の有無とある場合はそれらの説明を記述。
標準/エラー出力を行う場合はOutputs:に記述、リターン値がある場合Returns:に記述。
関数ローカルな変数は、localをつけて小文字スネークケース
##############################
# main function in this script
# Globals:
#   total_count
# Arguments:
#   command-line arguments
# Outputs:
#   Write total to stdout
##############################
function main() {
  if [[ "$#" -lt 1 ]]; then
    count
  else
    count_with "$@" 
  fi
  echo "Total is $total_count" 
}

main "$@" 
main関数を定義、コマンドラインを引数に取る。スクリプトが実行されるとmain関数を呼び出す。

問題解決

T.B.D.

参考文献

ブログ

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


7日前に更新