daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

シェルスクリプトのテンプレート(bash)

今回は、シェルスクリプトのテンプレートを作りました。

いつもシェルスクリプトを作るときは、前に作ったコードをコピーして使うことが多いのですが、前のコードが見つからないときに困ることがあったので、ブログに書いておこうと思います。

それではやっていきます!

参考文献

はじめに

最初に、ここで紹介するシェルスクリプトの用途について書いておきます。

シェルスクリプトの文法は、昔から継承してることもあり、洗練されてませんし、とても扱いにくいです。

例えば、変数を定義するとき(例:PATH=~/bin)に、前後の空白が入ってはダメというのがありますが、とても不便な制限です。

たまに、すごい長いシェルスクリプトを見かけますが、とても効率が悪いと思いますし、出来れば読みたくありません。個人レベルで書くシェルスクリプトで、複雑なことがやりたいなら、そこは Python などで書いて、シェルスクリプトから呼び出すなど、他の言語で処理した方が効率がいいですし、見やすくてメンテナンスもしやすいです。

ただし、他の人に使ってもらうような(Linux で最初から入ってる)シェルスクリプトは別です。いろんな理由があって、そうなっていると思いますし、変化の多い言語を採用するとメンテナンスが大変そうですしね。シェルスクリプトは、昔から文法が変わらず、使い続けることが出来るという点が良いところだと思います。

ここで扱うシェルスクリプトでは、あくまで、他の言語の処理をいろんな方法で呼び出すための手段であり、なるべく複雑なことをしないものに留めておくものになっています。

それではやっていきます!

全文

まず、全文を示した上で、必要に応じて、部分的に説明を入れます。

#!/bin/bash

#set -x # シェルスクリプト内で実際に実行されたコマンドを表示する (変数が使用されている場合は、その変数が展開された状態で表示される)
#set -v # シェルスクリプト内でこれから実行されるオプション (変数が使用されている場合は、-x オプションとは異なり、変数名がそのまま表示される)
set -e # 実行したコマンドの戻り値が 0 ではないステータスで終了した場合、即座に終了する

DATE=`date '+%Y/%m/%d %H:%M:%S'`
echo ${DATE} "start"

if [ "$1" != "" ]; then
    OPE=$1
else
    OPE=LATEST
fi

if [ "$2" != "" ]; then
    FNAME=$2
else
    FNAME="not found"
fi

sub()
{
    echo $1
    echo $2
}

if [ "$OPE" = "2to10" ]; then
    for CNT in `seq 2 1 10`
    do
        echo $CNT
    done
    exit 1
elif [ "$OPE" = "sub" ]; then
    sub 1 2
elif [ "$OPE" = "count" ]; then
    # ファイルが存在することを確認してから処理を行う
    if [ -e "${FNAME}" ]; then
        echo File exists: ${FNAME}
        # ファイルの行数を出力する
        # wc -l ${FNAME} は、行数以外が出力されてしまう
        cat ${FNAME} | wc -l 
    fi
elif [ "OPE" = "sedsample" ]; then
    # sed 覚書
    # echo -e は改行を有効にする
    # sed の2つ目は空行削除
    echo -e "aaa,bbb,ccc\n \nddd,eee,fff" | sed -e 's/,//g' | sed -e '/^$/d'
elif [ "OPE" = "awksample" ]; then
    # awk 覚書
    # awk -F は区切り文字の変更
    # awk の NR は行番号(1始まり)、NF は列番号(1始まり
    echo -e "aaa,bbb,ccc\r\nddd,eee,fff" | awk -F, 'BEGIN {ll=0} {printf "%02d: %s %d\n", NR, $1, ll; ll+=1}'
else # LATEST
    # ファイルが存在することを確認してから1行ずつ読み出す
    if [ -e "${FNAME}" ]; then
        echo File exists: ${FNAME}
        while read LINE
        do
            echo $LINE
        done < ${FNAME}
    else
        echo "file is not found"
    fi
fi

DATE=`date '+%Y/%m/%d %H:%M:%S'`
echo ${DATE} "end"

シェバンとシェルスクリプトのオプション

まず、先頭のシェバンと、シェルスクリプトのオプションについてです。

#!/bin/bash

#set -x # シェルスクリプト内で実際に実行されたコマンドを表示する (変数が使用されている場合は、その変数が展開された状態で表示される)
#set -v # シェルスクリプト内でこれから実行されるオプション (変数が使用されている場合は、-x オプションとは異なり、変数名がそのまま表示される)
set -e # 実行したコマンドの戻り値が 0 ではないステータスで終了した場合、即座に終了する

bash を使っていますが、#!/bin/sh と sh を使う方法もあると思います。個人で使う、かつ、Ubuntu でしか使わないのであれば、わざわざ制限の多い sh を使う必要はないと思います。

シェルスクリプトのオプションは非常に重要です。

-eオプション

-e はコメントにも書いてるように、途中でエラーが発生したときに、処理を継続させずに終了させるオプションです。途中でエラーが起こったとしても、処理を継続させたいケースは、とても少ないと思います。

例えば、ディレクトリを作成するコマンドを書いたときに、既にディレクトリが存在する場合でも止まってほしくない、ということはあると思います。その場合は、mkdir -p と個別に対応すればエラーは出なくなりますので、-e を設定した場合でも問題ありません。

一方で、-e を設定しなくても、エラーが起こった場合のために、エラー処理の分岐を書けば、後続の処理を実行しないようにすることができます。

-e については、どちらが効率がいいかということだと思います。どのような処理をシェルスクリプトにさせたいかによると思いますが、私の場合は、ほぼ -e を入れています。

その他のオプション

-x も、常に有効にしていてもいいと思います。シェルスクリプトを実行したときに、何が実行されたか(どこまで実行できたか)が、分からないと困る場合は、とても多いと思います。

-v はデバッグ用で、非常に多くの情報が出力されるので、必要に応じて有効にすればいいと思います。

その他

よく使う簡単なものを入れておいた。

  • ループ処理
  • 関数呼び出し
  • 個数を数える(ここでは -l の line を使ってるが、バイト数やワード数などもある)
  • sedの使用例
  • awkの使用例
  • ファイルを1行ずつ読み込んで処理する

コメントを入れておいたので、説明は割愛します。

また、必要と思うものを見つけたら追記しようと思います。

おわりに

今回は、自分で使うシェルスクリプトのテンプレートを記事にしました。

もっと書いておきたいことはありそうですが、思いついたら追記しようと思います。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。