2012/06/27
shebang
スクリプトの先頭行に書かれている #!/bin/sh の事を
shebang (シェバング) と呼ぶ。
古くはプログラムが実行された際にローダがファイルの先頭 2 バイトを参照して
ファイルの形式を調査した事に由来しているらしく、
例えば FreeBSD や Linux の最近の実行可能ファイルであれば
7f45(16) になっている
(その後 4c46(16) と続いているので、
ファイルをダンプすると ELF と読める)。
ファイル先頭のこの値の事を magic number とも呼び、
初期の file (1) コマンドはこの magic number を読み取って
ファイルの種類を報告していた (今の file (1) も基本は
magic number を利用している)。
shebang は通常はスクリプトを実行するインタプリタのパスを記述するのだが、 実は実行可能なプログラムであればインタプリタ以外も記述できるので、 あるプログラムの設定ファイルの先頭にプログラム名自身を書いておくと その設定でプログラムが実行できて実は便利である。
例えば sshd を /usr/local/etc/sshd/mysshd.conf を設定ファイルとして起動したい場合、通常はこの様に起動する。
# /usr/sbin/sshd -f /usr/local/etc/sshd/mysshd.conf
ところが設定ファイル /usr/local/etc/sshd/mysshd.conf の
先頭の shebang に #!/usr/sbin/sshd -f を追加して
/usr/local/etc/sshd/mysshd.conf に実行権限を与えると、
設定ファイルを実行する事で sshd が起動できるので便利だ。
# cat /usr/local/etc/sshd/mysshd.conf Port 20022 Protocol 2 : # ex -s !$ <<- EOF ex -s /usr/local/etc/sshd/mysshd.conf <<- EOF > 0i > #!/usr/sbin/sshd -f > . > w! > EOF # head !$ head /usr/local/etc/sshd/mysshd.conf #!/usr/sbin/sshd -f Port 20022 Protocol 2 : # chmod 755 !$ chmod 755 /usr/local/etc/sshd/mysshd.conf # !$ /usr/local/etc/sshd/mysshd.conf # netstat -anf inet | egrep '^Active|^Proto|\.20022' Active Internet connections (including servers) Proto Recv-Q Send-Q Local Address Foreign Address (state) tcp4 0 0 *.20022 *.* LISTEN実行例で利用している "!$" は GNU bash (1) の コマンドライン履歴機能で直前に実行したコマンドの最後の引数に展開される。
GNU bash (1) には他にも 色々な履歴の参照機能 があるので、 覚えておくとコマンドの入力が格段に便利になる。
2012/06/26
Bloxsom プラグイン
先日のコーディング晒しでソースコードを表示する際、
当初は <pre> タグにファイルの中身を貼り付けたり、
直接ソースコードを記述した後で、
手作業でタブの展開や特殊キャラクタの置換の作業をしていたのだが、
あまりにも面倒に感じたのと、
折角ソースコードを表示するならせめて行番号は表示したいので
結局プラグインをでっち上げた。
似たような機能では
SyntaxHighlighter
が有名だが、
- JavaScript を利用している
- 普段から Syntax による色づけは大嫌いなので使っていない
- 表示するコードの種類が複数ある場合に使い方が煩雑
慣れない perl で適当に書いたのであまり自慢できる代物ではないのだが、 Bloxsom を利用している人の役に立てば嬉しいので公開してみる。 例によって動作に関しては全くの無保証だが、 もしも問題や改善案などがあれば是非連絡して欲しいと思う。
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();
長くなってしまったので今回はここまで。
(多分)続く…