bash

(更新:2017/09)bashカラーバリエーション表示スクリプト
(作成:2013/12)

Bourne Shellの生まれ変わり(Born-again)ということで、Bourne-Again SHellの略。Bourne ShellはUNIX Version 7から使われているデフォルトシェルで、大抵のUN*X OSにも入っている。んでbashはってーと、GNUライセンスなもんでLinuxの標準搭載シェルとして有名だったりするわけですな。
因みにcshはBSD系らしい。bashとcshの良いトコ取り+αなzshなんてのもあるとか。

んで、今やスクリプト書くにしたってPerlだのPHPだのPythonだのRubyだの(Rubyはやった事無いケド)あるのに何でバッチ処理なのさ、というと。

  • bashは(最低でも sh は)OSを入れればデフォルトで入ってる
  • コマンド処理が一番簡単に書けるスクリプト
  • 超枯れてて超シンプル。

使われているものには大抵使われているだけの意味があるって事ですな。

インフラ業務なんかでは最新・最速よりも安定を優先する事が多く、例えばバックアップ処理なんかはOSにバンドルされる堅いアプリ・パッケージを併用してオンライン化・リモート化・高可用化を自動化付きで実現する。
そんなの1からプログラミングしても手間だしバグ混在の恐れ有りとかありえないわけで。かといってコマンド実行がメインなのにコマンド実行を組み難い言語で作るのも愚策なわけで。

しかしバッチ処理スクリプトと侮るなかれ。OS付属や他のコマンド型パッケージと組み合わせると、そこんじょらのスクリプト言語顔負けの仕事をしてくれる。これこそ万能型言語なんじゃなかろうか。

bash Tips

bashカラーバリエーション表示スクリプト

久々に色々真面目に調べて作ってみた。
Linuxのカラー対応コンソールで使う配色用エスケープシーケンスと、その見栄えを出力するスクリプト。エスケープシーケンスの最大文字数など考慮する必要があるため若干ハードコーディングしてるけど、208文字×50文字くらいのコンソールで表示崩れなく見れた。こんな感じ。
bashカラーバリエーション表示スクリプト(長いので折り畳み)
#!/bin/bash

MAX_STR=26

## ${1}: エラーメッセージ
## ${2}: エラー戻値
proc_err() {
    echo ${1}
    exit ${2}
}

## ${1}: 属性
## ${2}: 表示色
## ${3}: 背景色
## ${4}: 表示色(38;5の時、256色)
## ${5}: 背景色(48;5の時、256色)
print_color() {
    attr=${1}
    dcol=${2}
    bcol=${3}
    exdcol=${4}
    exbcol=${5}

    ## 属性Error処理
    if [ ${attr} -lt 0 -o ${attr} -gt 22 -o ${attr} -eq 9 ]; then
        proc_err "set attribule error: ${attr}" 1
    fi
    ## 表示色Error処理
    if [ ${dcol} -lt 30 -o ${dcol} -gt 39 ]; then
        proc_err "set display-color error: ${dcol}" 1
    fi
    ## 表示色(256色) Error処理
    if [ ${dcol} -eq 38 ]; then
        dcol="38;5"
        if [ ${exdcol} = "" -o ${exdcol} -lt 0 -o ${exdcol} -gt 255 ]; then
            proc_err "set extra-display-color error: ${dcol};${exdcol}" 1
        fi
        dcol="${dcol};${exdcol}"
    fi
    ## 背景色Error処理
    if [ ${bcol} -lt 40 -o ${bcol} -gt 49 ]; then
        proc_err "set background-color error: ${bcol}" 1
    fi
    ## 背景色(256色) Error処理
    if [ ${bcol} -eq 48 ]; then
        bcol="48;5"
        if [ ${exbcol} = "" -o ${exbcol} -lt 0 -o ${exbcol} -gt 255 ]; then
            proc_err "set extra-background-color error: ${bcol};${exbcol}" 1
        fi
        bcol="${bcol};${exbcol}"
    fi

    ## 整形
    str="\\\\e\\[${attr};${dcol};${bcol}"
    spc=""
    cnt=$((${MAX_STR} - ${#str} - 1))
    while [[ $cnt -gt 0 ]]; do
        spc="${spc} "
        cnt=$((${cnt}-1))
    done
    echo -en "\e[m "
    echo -en "\e[${attr};${dcol};${bcol}m${spc}${str}"
    echo -en "\e[m "
}

echo 'bash escape-sequences
attr: 0-8, 21 (see below)
disp: 30-37 (38:Extra) (39:Default)
back: 40-47 (48:Extra) (49:Default)
'

## 属性表示
fmt="%${MAX_STR}s%${MAX_STR}s%${MAX_STR}s%${MAX_STR}s%${MAX_STR}s"
echo 'Attributes:'
printf "${fmt}\n" "0:none " "1:strong " "2:short strong " "3:italic " "4:single underline "
echo "`print_color 0 39 49``print_color 1 39 49``print_color 2 39 49``print_color 3 39 49``print_color 4 39 49`"

printf "${fmt}\n" "5:slow brink" "6:rapid brink " "7:negative " "8:invisible " "21:double underline "
echo "`print_color 6 39 49``print_color 6 39 49``print_color 7 39 49``print_color 8 39 49``print_color 21 39 49`"

cnt=$((${MAX_STR} * 8))
while [[ ${cnt} -gt 0 ]]; do
    echo -n "-"
    cnt=$((${cnt} - 1))
done
echo

## 16色表示
echo '16 colors (8 palette):'
for i in {0..7}; do
    print_color 0 39 $((40 + ${i}))
done

cnt=$((${MAX_STR} * 8))
while [[ ${cnt} -gt 0 ]]; do
    echo -n "-"
    cnt=$((${cnt} - 1))
done
echo

## 256色表示
echo '256 colors:'
for i in {0..255}; do
    print_color 0 39 48 0 ${i}
done
echo ''

作るにあたっては以下のサイトを参考にしたんだけど、エスケープシーケンスの作法って決まってないのかな。

参考:
- シェル - echoで文字に色をつける その1 - Miuran Business Systems
書き方の作法に対する考え方あり。概ね同意。
- bashで16色表示するスクリプト用意しておくと便利 - Qiita
- bashで256色表示するスクリプト用意しておくと便利 - Qiita
スクリプトの参考に。

メール送信関数

sendmail

${MAIL}=root@localhost.localdomain
${BODY}=/var/tmp/mail_body.$
send_mail() {
    flg=${1}
    if [ "${2}" != "" ]; then
        echo >> ${BODY}
        echo "${2}" >> ${BODY}
    elif [ ! -e ${BODY} ]; then
        echo "Empty log messages." >> ${BODY}
        flg=1
    fi

    if [ ${flg} ]; then
        subject="Process complete"
    else
        subject="Process failed"
    fi
    cat ${BODY} | mail -s "${subject}" ${MAIL}
    /bin/rm ${BODY}
    exit ${flg}
}

長々と書いてるけど、キモは下から3行目の mail コマンド。 sendmail の設定が確り出来ていれば、このコマンドで簡単にメールが送れる。複数文を Body に貼りたい場合は、テキストファイルに落とし込んでパイプで流すのも楽だし、定型文なんかならヒアドキュメント使って echo で流しても良いかも。
で、これは何を長々と書いてるのかってーと、こんな使い方が出来る。

  • 処理が失敗した時は即座にエラーメールを出して終わる
  • 処理がうまくいっていれば、その間の処理内容をBodyに貼ってメール出して終わる
FILES="hoge fuga piyo"
DIR_SRC="/var/tmp/source"
DIR_DST="/var/tmp/destination"
for fname in ${FILES}; do
    /bin/cp ${DIR_SRC}/${fname} ${DIR_DST}/
    if [ x$? != x0 ]; then
        send_mail 1 "File \"${fname}\" copy failed."
    fi
    echo "File \"${fname}\" copy success."
done
send_mail 0

まあ見りゃ何となく想像付くだろうけれど、日時バックアップ処理の結果通知なんかに使える。

リモートホスト死活チェック関数

チャレンジコード無しのRSA認証鍵なんかを使ってNon passログイン出来る環境前提。あと相手先ホストがICMP Echo reply許可してくれる事前提。

chk_host() {
    /bin/ping -c 1 -i 5 ${1} > /dev/null 2>&1
    if [ x$? != x0 ]; then
        return 1
    fi
    return 0
}

これも大した事してないね。 ping 結果をコマンドライン戻り値で判断して死活判定してるだけ。
使い方は以下のような感じで。

flg=`chk_host localhost.localdomain`

リモート先ファイル存在チェック関数

これも条件は上の関数と同じ。

chk_remote_file() {
    if [ `ssh ${1} test -e ${2}; echo \$?` != "0" ]; then
        return 1
    fi
    return 0
}

キモはsshコマンド経由でのリモートコマンド実行結果を echo で返してあげる事。 ssh 経由のリモートコマンド実行後に直接コマンドライン戻値を参照しても、ssh接続が成功した/失敗した結果になってしまうのでご注意。

使い方は以下のような感じで。

flg=`chk_host localhost.localdomain /var/tmp/hoge`

bashで便利なLinuxコマンドTips

find

ファイル名を詳細検索できる。

以下は「ファイルのみ、本日0時を起点として365日前までのデータ以外を削除」みたいなことをする。

find . -type f -daystart ! -mtime +365 | xargs rm -f

以下はファイルサイズ上位10位を検索する。

find /var/www/html/ -type f | xargs du -k | sort -n | tail -10

sed

StreamEDitorの略でSED(セド)。まあそれは良しとして。
テキスト処理に特化したスクリプト言語だが、これ自体は至ってシンプルな機能しか持ち合わせていない。テキストファイルやパイプライン処理時の文字列フィルタツールとして結構便利に利用できる。
テキスト処理でもsedの場合、前述の通り入力 → sed → 出力、のように行単位でフィルタを掛けるのが得意なようで。つまり正規表現なんかでの文字列処理をコマンドラインで出来る感じ。

以下は文字列を置換する。 /hoge/fuga/piyo.txt/hage/fuga/piyo.txt となる。

FILE_PATH=/hoge/fuga/piyo.txt
path=`echo ${FILE_PATH} | /bin/sed s/hoge/hage/

以下は絶対パスのディレクトリを交換する。 /hoge/fuga/piyo.txt/fuga/hoge/piyo.txt となる。

FILE_PATH=/hoge/fuga/piyo.txt
path=`echo ${FILE_PATH} | /bin/sed s|/(.*)/(.*)/piyo\.txt|/\2/\1/piyo\.txt|`

以下は find と連携してディレクトリ内特定ファイルを再帰処理する。 mailto:hoge@hogehoge.jphttp://hogehoge.jp/cgi-bin/postmail.html に置換する。

for name in `find . -name "*.html"`; do
    sed s/'mailto:hoge@hogehoge.jp'/'http:\/\/hogehoge.jp\/cgi-bin\/postmail.html'/ $name > ./hoge
    mv ./hoge $name
done

awk

開発者のAlfred Aho、Peter Weinberger、Brian Kernighanの3人の頭文字を取ってAWK(オーク)。まあそれは良しとして。
テキスト処理に特化したスクリプト言語で、これ自体多機能で大規模なスクリプト処理も書けるらしい。とはいえ、広く一般に使われている言語の習得でイッパイイッパイなウチとしてはツールとして使う程度で満足だったり。
テキスト処理でも awk の場合、CSVなどの所謂DBチックなテキストをパースし処理するのが得意なようで。つまり特定のデリミタで区切られた文字列を要素要素に分解したりする場合、 awk で処理するとかなり楽チン。
一行スクリプトとしても利用可能なので、パイプ処理等と組み合わせてシェルスクリプトに組込むと凄く使える。

以下は特定文字で区切られた文字列から任意の位置の文字列を抜き出す。 piyo が得られる。

FILE_PATH=/hoge/fuga/piyo/foobar.txt
str=`echo ${FILE_PATH} | /bin/awk -F \/ '{print $4}'`

以下は絶対パスのファイルから絶対パスを抜き出す。 /hoge/fuga/piyo が得られる。

FILE_PATH=/hoge/fuga/piyo/foobar.txt
dir=`echo ${FILE_PATH} | /bin/awk -F \/ '{while(++i<NF)s=s$i"/";print substr(s,0,length(s));'}`

以下は絶対パスのファイルからファイル名を抜き出す。 foobar.txt が得られる。

FILE_PATH=/hoge/fuga/piyo/foobar.txt
file=`echo ${FILE_PATH} | /bin/awk -F \/ '{print $NF;}'`

以下はApacheのログを統合して日時順ソートする。
例えばロードバランサ複数台構成のApacheサーバからログを取得して、統合したのちソートしたいなんて要望。場合によりメモリ不足に陥るかも知れないけれど、もしコマンドラインでやるとすればこうなる。

$ cat web01_access_log web02_access_log web03_access_log | awk '{print $4" "$0}' | sort | cut -d " " -f 2- > web_access_log

やってる事は至極簡単。
まずは複数のApacheログを統合して全行 awk にパイプ。Apacheログのフォーマットから空白区切り4つ目にある日時情報を先頭にくっ付けて sort にパイプ。最後に先頭へくっ付けた日時情報を cut
ログフォーマットさえ知れていれば他にも使える。そんなにキモくないワンライナー。

wget

コマンドラインでWebサイトを取得できる。

以下はページ一括DL。まぁ何のためにとか野暮ったい事は問わない(w よく使うのはこんな感じ。

wget -nv -U 'Mozilla/4.0 (compatible; MSIE 5.0; Windows 95)' -A *.jpg -r -l 2 -D (サーバホスト名) -H (URI)

意味合的には、 -nv ログ出力は少なく、 -U 'Mozilla/4.0 (compatible; MSIE 5.0; Windows 95)' ダミーのユーザエージェントを答える、 -A *.jpg .jpg拡張子のみDL、 -r リンクを再帰的に辿る、 -l 2 辿る深さは2階層まで、 -D 該当ホスト名以外から取得しない、 -H 絶対アドレスリンクも辿る。

雑記

使い回せるバックアップスクリプト

データをどう確保するか

バックアップの基本は、サーバのデータのコピーを取ること。でも考えにゃならん要素は色々ある。

  • バックアップ場所
    • ローカルtoローカル
      一番簡単で、一番やっちゃいけないバックアップだと思う。
    • リモートtoローカル
      。。。え、自席環境に?これもやっちゃ駄目だろ常考。。。
    • リモートtoリモート(、ローカルtoリモート)
      まあ最低限これだわな。てかバックアップサーバにコピーするだろ常考。
  • リモート接続方法
    • nfs マウント
      やると解るが、相当ヤバい。却下。てか設定も面倒。
    • smb マウント
      Windowsバックアップならまあアリっちゃアリ。
    • ftp
      これも通信が平文だからなるたけ勘弁。
      そういえば関係無いけど、未だに世の中のWebスペースはFTPが多いのは何故だろう。
    • ssh
      UN*X間バックアップはこれを基本に考えた方が無難。暗号化される分処理に負担は掛かるが、それでも盗聴されるよかマシでしょう。
  • コピーコマンド
    • scp でファイル毎 or scp -r でディレクトリ単位
      これは状況に依る。例えば各サーバのログをバックアップしておいて、後でログ解析器に掛けるためにバックアップ先の整理もせにゃならん場合なんかは指定したファイルのみ対象にしてテクニカルな処理をする必要もある。
    • rsync でディレクトリ単位の同期を取る
      パッケージソフトのバックアップなんかはこちらでお手軽に同期取るのが良い。ファイルの整合性も保たれる。
  • オンラインバックアップ
    • LVM Snapshot
      Windowsの場合はVSS(Volume Shadowcopy System)に相当する。未使用のHDDボリュームに作業用物理エクステントを割り当てて、元ボリュームに更新が掛からない状態(スナップショット)にしてバックアップを実施する。
      利用者からはほぼ無停止(データ整合性を保つ為、スナップショット作成時に各種サービス停止を伴う。が、スナップショットは大抵数秒以下で作成できる為、サービス再起動程度の停止で済む)で整合性の取れたバックアップが可能。
      OS依存なのでOSとその構成に限定されるが、サービスに依存せずほぼ無停止のレベルで、しかも一番単純なファイルコピーによるバックアップで済ませることができる。
    • MySQL replication
      マスタサーバの更新をスレーブサーバにリアルタイムで同期する仕組み。リアルタイム性が重要なMySQLサーバのslave運用として有用。PostgreSQLにはまだ無いみたい。
    • lsyncd+rsyncd
      リアルタイム性が重要なストレージのslave運用として有用かつお手軽。
    • 商用ソフトウェアを利用
      うんまあ一番堅いけれども。

んでまぁ。この中で「MySQL replication」「lsyncd+rsyncd」はそもそもサービスなので省くとして、「商用ソフトウェアもを利用」も物に依りけりで省くとして。
これらの方式を利用したバックアップを「自動化したい」と考えるなら、一番容易いのはコマンドラインと親和性の高いシェルスクリプトで組む事だろうね、というお話。