2012/07/27

シェルスクリプトで便利な小技

シェルスクリプトを作成するときに知っておくと便利な小技集。

exec > ファイル
以降の標準出力を全て ファイル に出力するので この設定をしておくと簡単な実行ログが取得出来る。
同様に exec 2> ファイル とすると 標準エラー出力が全て ファイル に出力されるので エラーログが取得できる。
当然 exec > ファイル 2>&1 とすれば 標準出力も標準エラー出力も取得できる。
ファイル/dev/null を指定すれば スクリプト実行中の出力は全て抑止されるので、 cron (8) から実行される場合などでは便利な場合もある。
  1#!/bin/sh
  2
  3exec 2> ${TMP:-/tmp}/myname.log
  4    :
            
set -e
スクリプト実行時に制御文以外でエラーが発生した場合に スクリプトを終了させる。
スクリプト中で実行すべきコマンドを typo した場合などで、 以降の処理が実行されると困る場合などに特に役立つ。
  1#!/bin/sh
  2    :
  3echo "call myfunc ..."
  4mtfunc                                          # 関数名を typo している
  5echo "end myfunc ..."
  6    :
            
この様なコードの場合 set -e されていると、 mtfunc がエラーになった時点でスクリプトが エラー終了するので以降の処理が実行されない。
当然 ifwhile&&|| などでコマンドの結果が評価される場合は エラーにはならない。
set -u
スクリプト中で値が設定されていない変数を参照した場合に エラーメッセージを表示してスクリプトを終了させる。
シェル変数や環境変数を typo した場合など、 変数に値が設定されていない事で発生する問題が回避できる。
  1#!/bin/sh
  2tempdir=/var/tmp/
  3mynam=myname
  4    :
  5rm -rf ${tmpdir}/${myname}                      # 変数名を typo している
  6    :
            
この様なコードの場合、 変数名 ${tempdir}${mynam} を それぞれ typo しているので、 結果として rm -rf / に展開されてしまうが、 set -u されていると実行が防げる。
set -n
こちらはどちらかと言うとスクリプトの開発時に有効な設定で、 コマンドは実行されないのでスクリプトの文法チェックに最適。
set -x
こちらも開発時に有効な設定で、 コマンドが実行される前にコマンドを stderr に出力する。
上の exec と組み合わせると 詳細な実行ログが取得できるのでデバッグに便利
  1#!/bin/sh
  2
  3exec 2> ${TMP:-/tmp}/debug.log
  4set -x
  5    :
            

2012/07/23

英辞郎の辞書をコマンドラインから参照してみる

英単語の意味を調べるのに man(1) を使ってしまう人に贈る1行 という記事を拝見して、 自分の FreeBSD マシンで英辞郎の辞書を参照している方法を記事にしてみる。
要は英辞郎の辞書を EPWING 型式に変換して ndt サーバ経由で ndtpc コマンドから検索しているだけで、 検索コマンド自体は 1 行だが多少準備が必要となる。

英辞郎、和英辞郎、略辞郎、音辞郎の辞書を変換する。
英辞郎の辞書を EPWING 型式に変換するのは ports を利用すれば 何の問題もなく可能だ (若干ディスク容量が必要だが、 今となっては問題とならない容量だと思う)。
作業は英辞郎の CD-ROM をマウントした状態で実施すれば良いのだが、 CD-ROM は I/O が遅いのでローカルのファイルシステムに 辞書ファイルをコピーして実行しても良い。
ローカルにファイルをコピーした場合は DICT_PATH で 英辞郎の辞書ファイルのパスを指定すれば良い。
# cd /usr/ports/japanese/eijiro-fpw
# make DICT_PATH=辞書ファイルのパス install clean
# cd /usr/ports/japanese/waeijiro-fpw
# make DICT_PATH=辞書ファイルのパス install clean
# cd /usr/ports/japanese/ryakujiro-fpw
# make DICT_PATH=辞書ファイルのパス install clean
# cd /usr/ports/japanese/otojiro-fpw
# make DICT_PATH=辞書ファイルのパス install clean
            
この変換作業は結構時間が必要である。

ndtpd をインストールする
ndtpd は EB や EPWING などの辞書型式に対応している辞書サーバで、 FreeBSD では ebnetd という ports から導入可能である。
ports から導入すると ndtpd 以外に ebnetd、ebhttpd が インストールされるが今回は ndtpd のみ利用する。
# cd /usr/ports/japanese/ebnetd
# make install clean
# vi /usr/local/etc/ebnetd.conf
# cat << EOF >> /etc/rc.conf
> ndtpd_enable="YES"
> EOF
# chown nobody:nobody /var/run/ebnetd
# /usr/local/etc/rc.d/ebnetd.sh start
            
ebnetd.conf はコメントを参照にすれば簡単に設定できる。 今回はローカルサーバの自分しか利用しないので、 接続ホストや接続数を制限した。
### Port number `ndtpd' binds.
ndtp-port       ndtp

### Owner of the server process.
user            nobody

### Group of the server process.
group           nobody

### How many clients can be connected to the server at the same time.
max-clients     1

### Which hosts can or cannot connect to the server.
hosts           127.0.0.1

### Timeout seconds until the server disconnects an idle connection.
timeout         900

### Path to a working directory.
work-path       /var/run/ebnetd

### How many hit entries the server tries to find at a search.
max-hits        50

### The maximum size of text the server may send as a response to a client.
max-text-size   32768

### Syslog facility
syslog-facility local0

###
### Book entry
###
begin book
    ### Name of the book.
    name        eijiro

    ### Title of the book.
    title       eijiro

    ### Path to a top directory of the book.
    path        /usr/local/share/dict/eijiro-fpw

    ### How many clients can access the book at the same time.
    max-clients 1

    ### Which hosts can or cannot access to the book.
    hosts       127.0.0.1
end

begin book
    #
    # 以下 waeijiro の設定
    #
end

begin book
    # 
    # 以下 ryakujiro の設定
    #
end

begin book
    #
    # 以下 otojiro の設定
    #
end
            

作成された辞書を ebzip で圧縮する
EB ライブラリで使用されている圧縮方式で、 辞書のためのディスク容量を多少なりとも削減するために辞書を圧縮する。
# cd /usr/local/share/dict/eijiro-fpw
# ebzip --level 5
# cd /usr/local/share/dict/waeijiro-fpw
# ebzip --level 5
# cd /usr/local/share/dict/ryakujiro-fpw
# ebzip --level 5
# cd /usr/local/share/dict/otojiro-fpw
# ebzip --level 5
            

ndtpc をインストールする
ndtpc は perl で書かれた NDTP クライアントで、 こちら で公開されているので利用する。
今の所 FreeBSD の ports にはなっていない様なので、 ソースを取得してインストールする。
$ fetch http://www.tanu.org/~sakane/download/ndtpc-20050323a.tgz
$ tar -C /usr/local/src -zxvf ndtpc-20050323a.tgz
$ ./configure
# make install
            

これで以下のコマンドラインで辞書検索が可能になる。

$ ndtpc hello
DICT: 英辞郎
<4c33:560>hello
hello
【発音】helo'u 【変化】《複》hellos
[間投] こんにちは, あのう, やあ
<4c33:5f0>hello
Hello
[人名] エロ
DICT: 和英辞郎
DICT: 略語辞典
DICT: 音辞郎
    
ちなみに jvimkeywordprgndtpc を登録すると 単語の上で 'K' をタイプする事で辞書を検索できるので便利。

2012/07/12

久々の迷惑メイル

先ほど携帯に届いた迷惑メイル。
やりくちがあまりにも下衆なので修正なしで晒す。

From: info@wu30oa22rs81rr.info
To: XXXX@docomo.ne.jp
Date: 2012/ 7/12 18:30
Subject: ※レターバック※宛先不明により集中センター保管中※受け取り先ご確認をお願い致します

現在、お客様宛のレターメールをお預かりしております。
配送先のご確認が必要となっておりますのでお手数ではございますが、下記よりアクセスの上、配送確認をお願い致します。
本メール配送便に関しまして、お届け先不着の状態ですとお届けが出来ませんのでご注意下さいませ。
http://wu30oa22rs81rr.info/XXXXXXXXXXXXXXXXXXXXXX/1307240/sagawa/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
From アドレスや本文に書かれているの URL のドメイン部分が 怪しさ満点な wu30oa22rs81rr.info だったり、 URL の中に無理矢理入れた sagawa の文字だったり、 レターバック 等という低脳としか思えない間違いだったり etc. … 普通の人は引っかかる事はないと思いますが 注意喚起の意味をこめて公開します。
そもそもレターパック(バック?)を騙るなら sagawa じゃなくて japanpost だろうと思うのだが…、 というかレターバッグって郵便配達の方が持っている鞄か?。
一生懸命にそれっぽく作ろうとしている努力は認めなくもないが、 文章の端々からにじみ出す本当の意味での頭の悪さを思うと 人ごとながら同情を禁じ得ない。

最近のドコモの携帯はメイル表示画面から迷惑メイルの報告ができる様なので、 勿論通報しておいた。

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 による色づけは大嫌いなので使っていない
  • 表示するコードの種類が複数ある場合に使い方が煩雑
などの理由で使いづらく感じたので blosxom 用のソースコード/コマンドライン表示用プラグイン を作ってみた。

慣れない perl で適当に書いたのであまり自慢できる代物ではないのだが、 Bloxsom を利用している人の役に立てば嬉しいので公開してみる。 例によって動作に関しては全くの無保証だが、 もしも問題や改善案などがあれば是非連絡して欲しいと思う。

2012/06/25

太陽のたまご♪

すでに毎年恒例の家内行事となった完熟マンゴー。

Image: RIMG0617.JPG

太陽のたまご

今年も 4 個です。
素晴らしく甘く美味しい自然の恵みです。

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 の出力が比較できる。

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