2012/06/21

シェルスクリプトでの排他処理

業務系のシステムでシェルスクリプトを使用している場合など、 多重起動の防止などで排他制御が必要な場合が多々ある。
その様な場合に役に立つ(と思われる) シンボリックリンクを利用したシェルスクリプトでの排他制御方法。
ただし、あくまでもシェルスクリプトによる処理なので、 厳密な意味での排他制御にはなり得ないために タイミングによってはどうしても多重起動してしまう危険性がある事は 考慮しておく必要がある。

この処理では自分自身のプロセス ID をロックファイルとして シンボリックリンクして利用しているので、 排他制御を実施したプロセス ID が簡単に確認できる様にするとともに、 何らかの理由で不正にロックファイルが残ってしまった場合に プロセス ID を確認する事で簡単にリカバリ可能としている。
なお、プロセス確認のために /proc を参照しているので、 Linux など /proc が存在する システムのみで利用可能となっているが、 ps (1) などを利用してプロセスを特定すれば /proc が無いシステムでも利用可能だろう。

  1# ロックファイル
  2lockfile=${TMP:-/tmp}/${0##*/}
  3
  4# 多重起動防止のために symbolic link を作成する
  5# link が作成できたら trap を設定し終了時に link を自動削除する
  6# synbolic link が存在しても /proc/${PID} がない場合は
  7# ロックファイルが不正に残っているので削除して処理を続行する
  8while true
  9do
 10    if ln -s $$ ${lockfile} 2> /dev/null
 11    then
 12        # ロック取得できた
 13        break
 14    else
 15        # ロックファイルが既に存在している場合
 16        # ロックファイルから作成元の PID を取得する
 17        if [ -d /proc/`ls -l ${lockfile} | sed 's!.* !!g'` ]
 18        then
 19            # プロセスが存在する場合
 20            echo "${0##*/}: exist another instance" 1>&2
 21            exit 1
 22        else
 23            # プロセスが存在しない場合はロックファイルを削除してリトライ
 24            rm -f ${lockfile}
 25        fi
 26    fi
 27done
 28
 29# 終わる時は必ず lockfile を削除する
 30trap 'rm -f ${lockfile}; exit' 0 1 2 3 11 15
 31
 32# 実際の処理
 33    :
    

2012/06/20

コーディングスタイル ネタ編

twitter で話題になったので shell スクリプトにしてみた。
一応 posix 準拠の shell で動作すると思うんだけど、 $((…)) を使ったビット演算は果たして posix 準拠なのか微妙
あくまでもネタという事で笑って見て貰えれば嬉しい。

  1#!/bin/sh
  2# All rights reserved, copyright (C) 2012, Mitzyuki IMAIZUMI
  3#
  4
  5#
  6# 10進数を2進数に変換して表示
  7#
  8printb()
  9{
 10
 11    local   i str num
 12    
 13    i=0
 14    num=${1}
 15
 16    while [ $i -lt 32 ]
 17    do
 18        str="$((num & 1))${str}"
 19        num=$((num >> 1))
 20        i=$((i + 1))
 21    done
 22
 23    echo "${str}  (${1})"
 24
 25}
 26
 27#
 28# usage メッセージ表示
 29#
 30usage()
 31{
 32
 33    echo "Usage: ${1} [-v][-h|-o] num [... num]" 1>&2
 34    
 35    return 255
 36
 37}
 38
 39#
 40# ヘッダ表示
 41#
 42header()
 43{
 44
 45    local   i
 46
 47    i=0
 48
 49    while [ ${i} -lt 4 ]
 50    do
 51        printf "       *"
 52        i=$((i + 1))
 53    done
 54
 55    echo
 56
 57}
 58
 59#
 60# メイン処理
 61#
 62
 63# オプション解析
 64if args=`getopt voh $*`
 65then
 66    set -- ${args}
 67    for i
 68    do
 69        case "${i}" in
 70            -v )
 71                verbose=true;;
 72            -o )
 73                prefix="0";;
 74            -h )
 75                prefix="0x";;
 76            --  )
 77                shift
 78                break;;
 79        esac
 80        shift
 81    done
 82else
 83    usage ${0##/}
 84    exit
 85fi
 86
 87# ヘッダ出力
 88${verbose:-false} && header
 89
 90# 2進数変換
 91for i
 92do
 93    printb `printf %d ${prefix}${i}`                # 変換出力
 94done
    
どうだ、某会長(謎

ちなみに bc (1) を利用すると簡単に実現できちゃいますが、 プログラミング自体を楽しむという事で…

$ echo "ibase=10; obase=2; 10" | bc
1010
$ echo "ibase=16; obase=2; 20" | bc
100000
    

2012/06/19

コーディングスタイル

ちょっと間が空いてしまったが、 自分のコーディングスタイルを晒すために 簡単なプログラムを書いてみた。
プログラムは与えられた数値を2進数にして表示するだけの 簡単なプログラムなのであまり参考にはならないと思うが、 コーディングスタイルで取り上げた要素がほぼ網羅されていると思う。
条件演算子は無理矢理使ったので良い子は真似しない様に

  1/*
  2 * All rights reserved, copyright (C) 2012, Mitzyuki IMAIZUMI
  3 *
  4 * $Id: printb.c,v 1.1 2012/06/18 10:57:54 mitz Exp $
  5 */
  6#include    <stdio.h>
  7#include    <stdlib.h>
  8#include    <string.h>
  9#include    <unistd.h>
 10
 11/*
 12 * 10進数を2進数変換に変換して表示
 13 */
 14void    printb(int  num)
 15{
 16
 17    int     x = num;
 18    char    str[sizeof(int)*8+1],
 19            *ptr;
 20
 21    memset(str, '\0', sizeof(str));
 22
 23    for(ptr=str+sizeof(str)-2; ptr>=str; x>>=1)
 24        *ptr-- = '0' + (x & 1);
 25
 26    printf("%s  (%d)\n", str, num);
 27
 28}
 29
 30/*
 31 * usage メッセージ表示
 32 */
 33int     usage(const char    *myname)
 34{
 35
 36    fprintf(stderr, "Usage: %s [-v][-h|-o] num [num ...]\n", myname);
 37
 38    return(255);
 39
 40}
 41
 42/*
 43 * ヘッダ表示
 44 */
 45void    header(void)
 46{
 47
 48    size_t  i;
 49
 50    for(i=0; i<sizeof(int); i++)
 51        printf("       *");
 52    printf("\n");
 53
 54}
 55
 56/*
 57 * メイン処理
 58 */
 59int     main(int argc, char **argv)
 60{
 61
 62    int     verbose = 0,
 63            base = 0;
 64    char    ch,
 65            *myname;
 66
 67    /* basename $0 取得 */
 68    myname = (myname = strrchr(*argv, '/')) ? myname++ : *argv;
 69
 70    /* オプション解析 */
 71    while((ch = getopt(argc, argv, "voh")) != EOF)
 72        switch(ch){
 73            case 'v':
 74                verbose = 1;
 75                break;
 76
 77            case 'o':
 78                base = 8;
 79                break;
 80
 81            case 'h':
 82                base = 16;
 83                break;
 84
 85            default:
 86                exit(usage(myname));
 87        }
 88
 89    argc -= optind;
 90    argv += optind;
 91
 92    /* ヘッダ出力 */
 93    if(verbose)
 94        header();
 95
 96    /* 2進数変換 */
 97    while(*argv)
 98        printb(strtol(*argv++, NULL, base));
 99
100    exit(0);
101
102}
    

2012/06/18

posix shell で標準入力同士の diff (1) を実現する方法

コマンド command1 の出力結果と コマンド command2 の出力結果を diff (1) で比較したい場合、 一番手軽なのはそれぞれの出力を一時ファイルに出力して比較する方法である。

$ command1 > ${TMP:-/tmp}/output1
$ command2 > ${TMP:-/tmp}/output2
$ diff ${TMP:-/tmp}/output1 ${TMP:-/tmp}/output2
    :
$ rm ${TMP:-/tmp}/output1 ${TMP:-/tmp}/output2
    
しかし、この方法では一時ファイルを作成するので 処理効率が悪く一時ファイルの削除など後処理をする必要がある。
例えば bash (1) の場合は以下の様にする事で簡単に比較できる。
$ diff <(command1) <(command2)
    :
    
これは bash (1) 独自の機能なのだが、 posix 準拠のシェルでも以下の様に記述すると比較可能である。
$ command1 | ( command2 | diff /dev/fd/3 -) 3<&0
    :
    
ちょっとだけ理解しづらい記述であるが、 command1 の結果は 3<&0 の記述に従って ファイルディスクリプタ 3 へ出力される。 diff の第1引数は /dev/fd/3 が指定されているので、 結果として command1 の出力を受け取ることができ、 command2 の結果は直接標準出力を経由して diff の 第2引数へと渡されるので結果として command1 の出力と command2 の出力が比較できる。

2012/06/06

コーディングスタイル

自分のコーディングスタイル晒しの続き。

空白
linux コーディング規約とは若干異なっている。
基本的に余計な空白はなるべく入れない様にしているので、 if、switch、case、for、 do、while の後ろに空白は入れない。 それ以外は linux コーディング規約とほぼ同様だと思う。
2 項演算子や3 項演算子の演算子の前後には空白を入れるが、 例外的に for 文の中では演算子の前後に空白を入れていない。
    /* 2 項演算子 */
    i = j * 2;

    /* 3 項演算子 */
    x = x < y ? x : y;

    /* if 文 */
    if(condition)
        do_condition();

    /* for 文 */
    for(i=0; i<max; i++)
        do_loop();
            

名前の選択
これも linux コーディング規約に賛成。
例えばループのインデックスに利用するローカル変数は i や j で十分だと思う。
基本的にローカル変数は単純な名前で十分意味が通じると思うし、 変数名を工夫して可読性を高める必要があるのであれば、 そもそも関数のサイズやロジックを見直す必要があると思う。
それから、変数名に長めの英単語や英単語の組み合わせを使う場合に 特に我々が気をつけたいのはスペルミスだと思う。 他人に見られる可能性があるならせめて正しいスペルにするべき。 変な名前の変数だと逆に意味不明になりかねないし、 そもそも見る方も見られる方も恥ずかしい。
ハンガリアン記法に反対している部分も大賛成で、 コンパイラ言語ではコンパイラが正しく判断してくれる情報を わざわざ変数名に付けても得られる事は殆どないと思う。

typedef
linux コーディング規約とは若干異なっていて、 基本的には typedef で実態を隠蔽する方法を多用する。
fopen (3) や fclose (3) の様に パラメタを全て隠蔽した上で閉じたインタフェイスを提供した方が 使い方や記述がすっきりするのは勿論の事、 保守性も可読性も向上すると思っているので、 基本的には typedef したインタフェイス設計をする事が多い。
当然 typedef した場合はメンバーを完全に隠蔽するので、 例えば構造体を typedef した場合に、 構造体のメンバーに直接アクセスする様な事は絶対にしない。

関数
これも linux コーディング規約に賛成。
そもそも数学的な関数とは 入力値に対して一意な値を決定する規則の事なので、 プログラミングにおける関数も同様に、 単純に単一の事だけを実行して値を返すべきだと思う。
そのためにも関数の大きさはできれば 10 ~ 20 行程度、 長くても 50 行以内にはまとめたいと思う。

goto 文
これも linux コーディング規約に賛成。
何が何でも goto を嫌う人もいるが、 場合や使い方によってはとても便利だし可読性も高くなる。
linux コーディング規約では終了処理の共通化が上げられているが、 他にも多重ループの内側から全てのループを抜ける際などにも便利。
    while(condition1){
        while(condition2){
            if(anyconditions){
                do_something();
                break;
            }
            else if(otherconditions){
                do_anything();
                goto END;
            }
        }
    }
END:
    next_statement;
            
たとえば上記の処理を goto 使わないで実現すると、 変なフラグ変数を利用して内側のループ終了で フラグを判定する処理などが必要になるので却って煩雑になる。 なお、別解としてループ全体を関数とする方法もあり、 処理によっては関数化の方がより一層エレガントな場合もある。

最初に c に触れた時に参考にしたのは『初めての C』だったが、 この書籍はずぶの素人への入門としてはそれほど悪くなかった様に思う。 その後、『プログラミング言語 C』(いわゆる K & R) で 基本的なコーディングスタイルなどをほぼ独習した形なので、 それなりに独自のスタイルでコーディングしているのだと改めて思う。 独自のスタイルな事の言い訳ではないのだが、 自分のスタイルで統一した書き方のソースであれば、 多少独特な書き方でもそれほど見づらくはないと思う。
特にコーディングに自由度が高い c の場合、 絶対的に正しいスタイルなど存在しないと思うので、 常識的な範囲でそれぞれが独自のスタイルを作れば良いのではないだろうか。

追記

本文中での 3 項演算子 は一般名称として ternary operator の意味で 2 項演算子(binary operator) との対比として使用している。
c では条件演算子(conditional operator) `?:' が 唯一の 3項演算子なので 条件演算子 `?:' の事を 3 項演算子と呼ぶ人も多くいる様だが、 厳密には意味が異なるので要注意。

2012/06/05

コーディングスタイル

世の中にはプログラマの数だけコーディングスタイルがあると思うけど、 Linux カーネルのコーディング規約 と対比しながら、自分のコーディングスタイルを晒してみる。
勿論これはスクラッチで自分が自由に作成出来る場合のスタイルであって、 プロジェクトなどによりコーディング規約が定まっている場合や 他人が作成したソースを修正する場合は基本的にはそのスタイルに合わせる。

インデント
linux コーディング規約とは完全に意見を異にするが、 基本的にタブは 4 文字にしている。
通常の 80 カラムのディスプレイで見たときに タブが 8 文字だと間延びして見えてしまう事と、 ネストが 3 段以上になると格段に見づらくなる事が主な理由。
勿論、「3 段以上のネストは悪い」という意見は 原則的に賛成だが、 そうは言っても実際はもっと深くネストする事が 避けられない場面も多々ある。

case 文
linux コーディング規約とは若干異なっていて、 switch と case はインデントさせている。
深い理由はないのだが何となく見やすいと思う。
    swich(suffix){
        case 'G':
        case 'g':
            mem <<= 30;
            break;

        case 'M':
        case 'm':
            mem <<= 20;
            break;

        case 'K':
        case 'k':
            mem <<= 10;
            /* fall through */

        default:
            break;
    }
            

1行に複数の文を書かない
これは linux コーディング規約に賛成。
特に if に続くセンテンスは必ず改行して書いて欲しい所だ。
    if(condition)
        do_this;
    do_something_everytime;
            

インデントはタブを利用する
これも linux コーディング規約に賛成。

まともなエディタを利用する
何を「まとも」とするかで宗教戦争が勃発しちゃいそうだが、 例えば 1 行を 80 カラム以上で横にスクロールさせながら表示する エディタは使って欲しくないと思う。 そういうエディタを使っていると往々にして 1 行が 80 カラム以上の見づらいソースになりがちだから。

行の長さは 80 カラムが限界で強く推奨される
これも linux コーディング規約におおむね賛成。
ただし分割した 2 行目以降を 「かなり短く、かなり右におく」 事はしない。
基本的には 1 行目の先頭からタブ 1 個から 2 個分 右にインデントした位置にしている。
    if(condition)
        printf(KERN_WARNING "Warning this is a log printk with "
                "3 parameters a: %u b: %u c: %u\n",
                a, b, c);
    else
        next_statement;
            

ブレースの位置
これも linux コーディング規約におおむね賛成。
if、switch、for、while、 do においてはブロックの開始行の行末に開きブレース、 ブロックの最終行の行頭に閉じブレースというスタイルで書いている。 関数定義のブレースについての書き方も同様に賛成で、 開きブレースは次の行の行頭に書いている。
ただし else 節を開始する開きブレースは 昔から次の行の行頭に置くクセがあるので今でもそのまま書いている。
    /* 関数定義 */
    int     function(int i)
    {

        body;

    }

    /* for 文 */
    for(i=0; i<max; i++){
        do_condition_1;
        do_condition_2;
    }

    /* while 文 */
    while(condition){
        do_condition_1;
        do_condition_2;
    }

    /* do ~ while 文 */
    do{
        do_condition_1;
        do_condition_2;
    }while(condition);

    /* if ~ then ~ else 文 */
    if(condition == 1){
        do_condition_1_1;
        do_condition_1_2:
    }
    else if(condition == 2){
        do_condition_2_1;
        do_condition_2_2:
    }
    else{
        do_condition_other_1;
        do_condition_other_2:
    }
            

必要のないブレースを使わない
これも linux コーディング規約に賛成。
特に if の後ろには単文でも必ずブレースを使う人がいるが、 ソースが煩雑になるので不要なブレースは省略している。
    if(condition)
        this();
    else
        otherwise();
            

長くなってしまったので今回はここまで。
(多分)続く…

2012/05/26

gdb の意外に便利な使い方

開発環境には必ずインストールされている gdb (1) コマンド。
本来の機能はプログラムのシンボリックデバッガなのだが、 若干意外とも言える便利な使い方がある。

それはプログラマなら重宝する 16 進数電卓としての用法。

$ gdb -q
(gdb) p/x 10                     ← 10 を 16進数で表示
$1 = 0xa
(gdb) p/x 20                     ← 20 を 16進数で表示
$2 = 0x14
(gdb) p/x $1 + $2                ← 10 + 20 を 16進数で表示
$3 = 0x1e
(gdb) p $2 - $1 / 3              ← 20 - (10 / 3) を 10進数で表示
$4 = 17
(gdb) p ($2 - $1) / 3            ← (20 - 10) / 3 を 10進数で表示
$5 = 3
(gdb) q
    
例にある様に一度利用した値は変数 $n に保存されるので、 計算結果を後から参照するのも便利だし、 四則演算の優先順位もちゃんと解決してくれるので電卓としても結構使える。
ちなみに入力データは Unix/C の原則に従って、 0 が先行する値は 8 進数、 0x が先行する値は 16 進数として認識される。
ちょっとした便利機能の紹介でした。

2012/04/02

ご近所さん

我が家の近所には同じ年頃の子供を持つ家庭が偶然多く暮らしているので、 非常に楽しく素敵なご近所つきあいをさせて頂いている。

ご近所さんのうちの1家族は震災の随分前に福島から引っ越してきたのだが、 同じ年の子供がいたのでとっても仲良くさせて頂いていた。 いつも仲が良いとっても素敵な家族で子供達も凄く素直で優しい良い子達だった。 その家族が仕事の都合で先日引っ越してしまったのだが、 引っ越した先が京都だという事を訊いて何とも言えない気分になった。

横浜を経由しているとは言え福島から来た家族がいじめられたりしないだろうか? 今思うとその家族が乗っている自動車のナンバーは福島ナンバーだったんだけど、 特に子供達がいじめや仲間はずれになっていないだろうか。 がれき受け入れの記事を読んで人ごとながら胸が痛くなる思いで一杯だ。

京都に限らず福島からの人々が虐められ迫害されているという記事や ニュースを見る度に本当に心が痛くなる。
彼ら・彼女らこそが一番の被害者なのに、 一番傷付き疲れ果て様々な迷惑や不便を味わっているのに、 何故そんな仕打ちを受けなければならないのだろうか。

「自分たちの子供のため」などと言って思考停止していないで、 足りないなら足りないなりにその頭で考えて欲しい。
本当に自分の子供の事を考えているのであれば、 親として自分の子供に見せるべきはどういう姿なのか、を。
よく言われているが子供は親の背中を見て育つのだ。
理由も根拠もなく他者を差別し迫害し虐げる親のその姿をね。

2012/02/22

C 中級者が意外と陥りやすいワナ

ある程度プログラミングにも慣れてきて 言語仕様もそこそこ理解した中級者にとって 意外と陥りやすくセキュリティホールの温床になりがちなワナ。
勿論オンラインマニュアルにも仕様として明記してあるのだが、 直感とは反する仕様のなので特に中級者にこれらのミスが多い気がする。

  1. strncpy(3) は ``\0'' 終端してくれない場合がある
  2. strcpy(3) はバッファオーバーフローの危険性があるから strncpy(3) を利用する様によく言われるが、 ここにワナが潜んでいる。

    strncpy(dst, src, len);

    とした時に 文字列 src の長さが len バイト未満の場合は dst は ``\0'' で終端される。
    終端どころか dst の残り領域は何故か 全て ``\0'' が詰められるという 無駄とも思われる謎仕様。
    ところが文字列 src の長さが len バイト以上の場合、 dst に len バイトだけコピーするという memmove(3) 同様の動作となってしまい、 dst が ``\0'' で終端されない。
    文字列操作の関数なのにも関わらず得られた結果が 文字列として扱えないという不思議な仕様なのだ。
    snprintf(3) などの様に len - 1 バイトを dst にコピーして ``\0'' で終端される事を期待していると 痛い目に遭ってしまう。

    文字列操作関数なので、 殆んどの場合 ``\0'' 終端が必要になると思うので 以下の様な処理が代替になる。

     strncpy(dst, src, sizeof(dst) - 1);
     *(dst + sizeof(dst) - 1) = '\0';
     			

  3. strncat(3) のサイズ指定はコピー先のサイズを指定するのではない
  4. strcat(3) も同様にバッファオーバーフローの危険性があるので strncat(3) を利用する様によく言われるが、 ここにもワナが潜んでいる。

    strncat(dst, src, len);

    とした時の len はコピーする src の長さであって、 前述の strncpy(3) や snprintf(3)の様に dst のサイズ指定ではない ここで dst のサイズのつもりで len を指定すると バッファオーバーフローの原因になってしまう。

    殆んどの場合は dst の最大値が重要になると思うので 以下の処理が代替になる。

    strncat(dst, src, sizeof(dst) - strlen(src) - 1);
    			

  5. snprintf(3) は領域が重なってはいけない
  6. 前述した通りに使いやすいとは言いづらい strncpy(3) や strncat(3) の代用として、 コピー先のサイズ指定が直感的な snprintf(3) を利用する場合も多いと思うが、 strncat(dst, src, len) の置き換えとして

    snprintf(dst, sizeof(dst), "%s%s", dst, src);

    とすると正しく動作しない(結果は処理系依存になる)。
    ANSI X3.159-1989 (ANSI C) や ISO/IEC 9899:1999 (ISO C99) 、 IEEE Std 1003.1-1988 (POSIX.1) 等をざっと探したけど 関連する記述は見つける事はできなかったが、 snprintf(3) は重なった領域では正しく動作しない様だ。

以上、C によるプログラミングに慣れた頃に陥りがちなワナなので 注意が必要だと思う。

2012/02/10

Happy Birthday My Dearest !

Image: img_0429.jpg

Happy! Happy!

You were perfect the day I met you, but you just get better every year.


Copyright © 2008-2020 Mitzyuki IMAIZUMI. All rights reserved.