2011/04/20

MocBook Air

Image: RIMG0208.JPG

MocBook Air late 2010

以前白ポリカーボネート製の MacBook の名刺入れをお願いしたのだが、 MacBook Air を購入したのでそちらの分もお願いしていた物が届いた。

Image: RIMG0210.JPG

MacBook Air と MocBook Air

以前届いた MocBook と並べると最高に可愛くて素敵である。 MocBook のデスクトップは娘の誕生日でお願いしたので、 MocBook Air のデスクトップ画像は奥さんの誕生日にしてみた。 キーボードは勿論英語キーボードの画像に変更して頂けた。
MacBook Air は普段から持ち歩いているので MocBook Air 名刺入れも届いてからは毎日持ち歩いているのだが、 仕事柄人に会う機会が少ないので名刺交換も稀なので 自慢する機会が少ないのがちょっと悲しい(そこか)。

Image: RIMG0219.JPG

MocBook と MocBook Air

この様な素晴らしいアイテムを手作りで作成して頂けて非常に感謝している。
最近購入した MacBook Pro の分も是非欲しいなと思う今日この頃 (デスクトップは自分の誕生日にしよう♪)

残念ながら諸処の事情で当面新規の受付は中止しているとの事なので、 作者の方のお名前や Web ペイジの URL は伏せさせて頂く。

2011/04/19

Mac で簡易バックアップ

Mac は rsync (1) が標準で搭載されていて 簡単に差分転送する事ができるので、 簡易的なバックアップが手軽に実施できて便利である。

以下のコードはリモートサーバのディスクを samba 形式で mount (8) し、 rsync (1) で同期するためのスクリプト。
バックアップ先のディスクを samba 形式でマウントするので、 バックアップ先が Windows パソコンでも動作する。
Finder の共有から一度バックアップ先のディスクにアクセスして パスワードをキーチェーンに記憶させると、 以降はパスワードの問い合わせがなくなるので便利だろう。
バックアップ先が unix 系のサーバの場合であれば、 mount (8) しないで rsync (1) に 直接リモートサーバを指定しても良い。 その場合 -e オプションを指定して ssh (1) 経由で転送する事が望ましい。

  1#!/bin/sh
  2
  3# バックアップ対象(= バックアップ先ディレクトリ)
  4target="Pictures Music"
  5# バックアップ先サーバ
  6Server="<u>リモートサーバ</u>"
  7# バックアップ元ディレクトリを格納しておき eval(1) で展開する
  8Pictures="Pictures"
  9Music="Music/iTunes/iTunes Music"
 10
 11# "-f" オプションを指定しない場合は実際には転送しない
 12test "$1" = "-f" || dry=n
 13
 14for i in ${target}
 15do
 16    src="${HOME}/`eval echo '$'${i}`"
 17    dst="/Volumes/${i}"
 18    
 19    mkdir -p ${dst}
 20    mount -t smbfs //${Server}/${i} ${dst}
 21    rsync -avz${dry} --delete --exclude '._*' --exclude .DS_Store "${src}/" "${dst}"
 22    umount ${dst}
 23done
    

2011/04/14

貢ぐ

今まで購入してきた Apple 製品をつらつらと思い出してみる。
iPod 以外の Apple 製品を購入する様になったのは本当にこの数年なのだが、 今ではすっかり Windows を駆逐して Apple 一色になっている。

iPod

iPod Shuffle 第1世代 1GB
iPod nano 第1世代 4GB
iPod nano 第2世代 8GB
iPod nano 第4世代 16GB

Macintosh

Mac mini
Mid 2007 Intel® Core™ 2 Duo T5750 2.0 GHz 4 GB へ換装 200 GB / 7200 rpm へ換装
MacBook 13″ Early 2008 Intel® Core™ 2 Duo T8300 2.4 GHz 4 GB 500 GB / 7200 rpm へ換装
MacBook Air 11″ Late 2010 Intel® Core™ 2 Duo L9400 1.86 GHz 4 GB 128 GB SSD
MacBook Pro 15″ Earyl 2011 Intel® Core™ i7 2720QM 2.2 GHz 8 GB 512 GB SSD
MacBook Air 11″ Mid 2013 Intel® Core™ i7 4650U 1.7 GHz 8 GB 512 GB SSD

Software

Mac OS X 10.6 Snow Leopard 2009 5 License Pack

Miscellaneous

Migty Mouse 2007
Wireless Keyboard 2007 3 AAA cell
Time Capsule Earyl 2010 1TB
Magic Mouse 2011
Magic Trackpad 2011
AirMac TimeCapsule 2018 2TB

2011/04/10

入園式

娘の幼稚園の入園式が無事に終了した。
今年は偶然入園式が日曜日だったので参加できたのだが、 娘の成長する姿というのは見ていて微笑ましさと、誇らしさ、 そして若干の寂しさが共存している不思議な感情になる。
「幼稚園の入園式は泣き声があちこちで聞こえて来るので 感動に浸っている余裕はない」 と聞いていたのだが、 確かにあちこちから子供達の泣き声が響いてきた。 一番大きな声で泣いていたのが自分の娘だったのが…微妙な感じではあるが(笑)

ご多分にもれず入園式会場の看板の前で家族で記念撮影などしてみる。
暖かな春の日差しの中で制服を着て少し誇らしげだけど、 幾分緊張もしているわが娘の姿がとても愛おしい。

2011/03/01

Facebook に届いたメッセージ

Facebook に怪しさ満点なメッセージが届きました。

Hello Imaizumi,

I have been in search of a foreigner with this last name "Imaizumi", that will be faithful to me, so when I saw your name I was pushed to contact you and see how best we can assist each other in this business. I am Mr. Bernard Awotwi, a Banker here in GHANA. I believe it is the wish of God for me to come across you name now. I am having an important business discussion I wish to share with you which I believe will interest you because, it is in connection with your last name and you are going to benefit from it. I write you this proposal in good faith, believing that I can trust you with the information I am about to reveal to you.


Let me know your mind on this and please do treat this information as TOP SECRET and DO NOT respond to this on facebook for same security reason. I have more to write you about the details once I receive your urgent response strictly through my personal Email address: xxx@hotmail.com

Regards,
Bernard Awotwi

そんなに私の名字が『信用できる』んでしょうか(笑)。 というかガーナの『銀行員』な方が赤の他人(しかも彼|彼女から見たら外国人)を 名字だけで信用しちゃって良いんでしょうかね(笑)。 さらには神のご意思とか(笑)。
しかも FaceBook での返信をせずに『セキュリティ上の理由から』 hotmail にメイルしてって(笑)。
怪しすぎてワクワクしちゃいますが、 これってひと昔〜ふた昔も前に流行った「ブラックドル」でしょうかね。
暫くは突っ込み所満載な内容に笑わせて貰ったので、 この楽しさを是非共有したいのと骨董品的に懐かしいのとで blog にしちゃいました。

2011/02/28

重複メイル削除ツール

友人の awk (1) の勉強支援の第 3 段。 以前 Software のペイジで公開したスクリプトの中身。

1 メール 1 ファイルの形式(mh 形式)で格納されたメイル本文のうち Message-Id: ヘッダが重複するものを抽出して削除するツールで、 sort (1) や uniq (1) だけを利用しても実装可能だが、 可読性と拡張性、実装の手間等を考えて簡単に awk (1) で実装した。
Message-Id: ヘッダから取得したメッセージ ID をインデックスとして ファイル名を連想配列に格納しており、 既にメッセージ ID が連想配列のインデックスとして存在している場合は メッセージ ID が重複したものとしてファイル名を標準出力に出力するだけの 非常に簡単な内容のスクリプトだ。

  1#!/bin/sh
  2#
  3#   All rights reserved, copyright (c) 2009, Mitzyuki IMAIZUMI
  4#   $Id: DupmailMac,v 1.1 2009/09/30 09:25:09 mitz Exp $
  5#
  6
  7exec 2> /dev/null
  8
  9# Mac のメールボックスのデフォルトロケーション
 10basedir="${HOME}/Library/Mail"
 11
 12find ${basedir} -name "*.mbox" -a -type d |
 13while read folder
 14do
 15    if [ -d "${folder}/Messages" ]
 16    then
 17        awk '{
 18            # 大文字小文字を区別しないために全部小文字に変換
 19            if(tolower($1) ~ /^message-id:/){
 20                # 入力行を `:' で分割する (line[2]: message-id)
 21                split($0, line, ":");
 22                # message-id から余計なスペース、 <、> を削除
 23                gsub("[<> ]", "", line[2]);
 24
 25                if(message[line[2]] != "")
 26                    # message-id に対応するファイルがある場合はファイル名を表示
 27                    print FILENAME;
 28                else
 29                    # message-id に対応するファイルがない場合はファイル名を格納
 30                    message[line[2]] = FILENAME;
 31            }
 32        }' ${folder}/Messages/* |
 33        # ファイルの削除
 34        sort | uniq | xargs rm
 35    fi
 36done
    

2011/02/25

awk を利用した構文解析ツール

友人の awk (1) の勉強支援の第 2 段。 今回はちょっと複雑な処理なので追うのが大変かも?
このスクリプトは随分以前(1996年頃)に作成したのだが、 プログラムによって微妙に異る複数の設定ファイルの中身を 解析するために作成したそこそこ汎用の構文解析機だ。 本来は perl (1) などで記述したかったのだが、 よんどころない事情で awk (1) により実装した。

以下に示す構造の状態遷移テーブルで状態(status)とキーワード(token)を定義し、 それぞれの status の時に出現する入力データ中の token により 定義されていてば外部コマンドを実行して次の status への遷移を繰り返す。

状態遷移デーブルは 1カラム目が '#' の行、タブ、 スペースのみの行は無視する。
`%syntax' で始まる行が token の定義となり、 次の行以降が status の時に出現する token 毎の定義で、 実行するコマンドと遷移する status、 もしくはシンタックスエラー(error)を記述する。
予約された状態値として初期状態を示す `start'と エラー状態を示す `error' が定義されており、 エラー状態では標準エラー出力にメッセージを出力後終了する。
オプションとして開始時に 1 度だけ実行される初期処理コマンドを `%start' で始まる行に、 終了時に 1 度だけ実行される終了処理コマンドを `%end' で始まる行にそれぞれ定義できる。

%syntax token1 token2 ... *
start status1:command1 error ... status2
status1 status3:command3 error ... statusN:commandN

:
statusN error error ... start
%start command parm ....
%end command parm ....

status 定義行ではその status に遷移した際に実行するコマンドを `:' に続けて記述でき、 コマンドの引数には以下の特殊文字が指定できる。 全ての特殊文字の置き換えを終了するとシェルを通してコマンドを実行する。
%
現在のトークンに置き換えられる
,
スペースに置換えられる
$0
直前のコマンドのリターン値に置き換えられる
$1$N
このコマンドの第 3 引数以降に置き換えられる

実際に使用した状態遷移テーブルの例を示す。 下で示す形式の設定ファイルの解析を実施するためのもので、 開始状態から入力データに応じて状態値を遷移させながら解析処理を実施する。

# 最初に実行されるコマンド
%start      ${path}/do.start

# 最後に実行されるコマンド
%end        ${path}/do.end $0

# token 定義
%syntax     {           }      ,           =          *

# 状態遷移テーブル
start       error       error  error       error      name:${path}/do.name,%,$0,$1,$2
name        keyword     error  error       error      error
keyword     error       start  error       error      continue:${path}do.keyword,%,$0,$1,$2
continue    error       error  error       equal      error
equal       error       error  error       error      next:${path}/do.val,%,$0,$1,$2
next        error       start  keyword     error      error
	
解析させた設定ファイルの形式。
名称1 {
	キーワード1 = 値1,
	キーワード2 = 値2,
	  :
	キーワードN = 値N
}

  :

名称M {
	キーワード1 = 値1,
	キーワード2 = 値2,
	  :
	キーワードN = 値N
}
	
この定義ファイルの解析を実行すると 以下の順にコマンドを実行する事と等価な処理が実施できる。
$ ${path}/do.start
$ ${path}/do.name 名称1 $? 引数1 引数2
$ ${path}/do.keyword キーワード1 $? 引数1 引数2
$ ${path}/do.val 値1 $? 引数1 引数2
$ ${path}/do.keyword キーワード2 $? 引数1 引数2
$ ${path}/do.val 値2 $? 引数1 引数2
    :
$ ${path}/do.keyword キーワードN $? 引数1 引数2
$ ${path}/do.val 値N $? 引数1 引数2
    :
$ ${path}/do.name 名称M $? 引数1 引数2
$ ${path}/do.keyword キーワード1 $? 引数1 引数2
$ ${path}/do.val 値1 $? 引数1 引数2
$ ${path}/do.keyword キーワード2 $? 引数1 引数2
$ ${path}/do.val 値2 $? 引数1 引数2
    :
$ ${path}/do.keyword キーワードN $? 引数1 引数2
$ ${path}/do.val 値N $? 引数1 引数2
$ ${path}/do.end $?
    

そしてスクリプト本体。 今見返すと冗長な記述などもあるが敢えてそのままにしておく。

  1#!/bin/sh
  2#
  3# Copyright (c) 1996 Mitzyuki IMAIZUMI, All rights reserved.
  4#
  5# $Id: parser,v 1.7 1996/02/01 19:33:18 mitz Exp $
  6#
  7# 名称 
  8#   parser - 状態遷移テーブルに基づいてシンタックスをチェック
  9#
 10# 構文
 11#   parser config input [引数…]
 12# 
 13# 引数
 14#   config
 15#       状態遷移テーブル
 16#   input
 17#       入力ファイル
 18#   引数…
 19#       各状態で実行するコマンドの引数
 20#
 21
 22# パラメタチェック
 23test $# -lt 2 -o ! -f $1 -o ! -f $2 && exit 255
 24
 25trap '' 1 2 3 5 9 13 15
 26
 27conf=${1}; file=${2}; shift 2
 28
 29for i
 30do
 31    parm="${parm},${i}"
 32    shift
 33done
 34
 35# %syntax 行から token を取得(最後の token は除外
 36token=`
 37    awk '{
 38        if($1 == "%syntax"){
 39            for(i=2; i<NF; i++)
 40                printf("%s", $i);
 41            exit
 42        }
 43    }' ${conf}`
 44
 45# 入力ファイルの token 前後にスペースを付加する
 46sed 's/['${token}']/ & /g' ${file}    |
 47
 48awk '
 49    #
 50    # 初期処理
 51    #   状態遷移テーブルのリード
 52    #
 53    BEGIN{
 54
 55        argc = split("'${parm}'", argv, ",");       # 引数を格納
 56        argv[1] = 0;
 57
 58        while(getline < "'${conf}'" > 0){
 59            if(/^#/ || /^[ \t]*$/)                  # コメント行/空行
 60                continue;
 61            if($1 == "%start"){                     # 初期処理定義行
 62                $1 = "";
 63                prolog = $0;
 64            }
 65            else if($1 == "%end"){                  # 終了処理定義行
 66                $1 = "";
 67                epilog = $0;
 68            }
 69            else if($1 == "%syntax")                # トークン定義行
 70                for(i=2; i<NF; i++)
 71                    item[i-1] = $i;
 72            else                                    # 状態遷移定義行
 73                for(i=2; i<NF; i++)
 74                    if(p = index($i, ":")){
 75                        data[$1 item[i-1]] = substr($i, 0, p-1);
 76                        command[$1 item[i-1]] = substr($i, p+1);
 77                    }
 78                    else
 79                        data[$1 item[i-1]] = $i;
 80        }
 81        if(prolog != "") 
 82            argv[1] = exec(prolog, "");
 83
 84        status = "start";
 85
 86    }
 87
 88    #
 89    # トークンチェック
 90    #
 91    function  isitem(item, token,       i)
 92    {
 93
 94        for(i in item)
 95            if(item[i] == token)
 96                return 1;
 97
 98        return 0;
 99
100    }
101
102    #
103    # コマンド実行
104    #
105    function  exec(command, token,      buf, i)
106    {
107
108        gsub("%", token, command);
109        for(i=0; i<argc; i++){
110            buf = sprintf("\\$%d", i);
111            gsub(buf, argv[i+1], command);
112        }
113        gsub(/,/, " ", command);
114
115        i = system(command);
116        close(command);
117
118        return i;
119
120    }
121
122    #
123    # メイン処理
124    #
125    {
126
127        # コメント行スキップ
128        if(/^#/) 
129            continue
130
131        for(i=1; i<NF; i++){
132            if(isitem(item, $i)){
133                format = command[status $i]
134                status = data[status $i]
135            }
136            else{
137                format = command[status]
138                status = data[status]
139            }
140            if(status == "error"){
141                printf("%s: %d: syntax error \"%s\"\n",
142                    "'${file}'", NR, $i) | "'cat' >2"
143                ret = 255
144                exit
145            }
146            else if(format != "")
147                if(format == "exit"){
148                    ret = argv[1]
149                    exit 
150                }
151                else
152                    argv[1] = exec(format, $i)
153        }
154
155    }
156
157    #
158    # 終了処理
159    #
160    END{
161        if(epilog != "")
162            exec(epilog, "")
163
164        exit ret
165    }
166'
    

2011/02/24

awk スクリプト

知り合いが awk (1) の勉強をしたいと言ってるので、 支援がてら過去に作成したスクリプトをさらしてみる。
このスクリプトは linux 上で稼働している あるプログラムが取得した共有メモリの残骸を掃除するスクリプトで、 ipcs (8) で取得した共有メモリ一覧のうち、 3 件連続しているキーが存在する場合 ipcrm (8) をコールして 共有メモリを削除する処理を行う。
短いスクリプトながら、外部コマンドの呼出し (system())、 コマンドからの値取得 (getline())、 そして関数内ローカルスコープの変数定義など awk (1)の持っている機能を結構活用しているので、 多少なりとも参考にして貰えるとうれしい。

  1#!/bin/sh
  2#
  3# Copyright (c) 2010 Mitzyuki IMAIZUMI, All rights reserved.
  4#
  5# $Id: shmrm,v 1.7 2010/05/08 09:03:15 mitz Exp $
  6#
  7# 共有メモリ削除処理
  8#
  9
 10LANG=C /usr/bin/ipcs -m | sort |
 11
 12awk '
 13    #
 14    # 初期処理
 15    #
 16    BEGIN{
 17
 18        max = 0;                        # 削除対象キーインデックス
 19        num = 0;                        # 削除対象共有メモリインデックス
 20        ipcrm = "/usr/bin/ipcrm";       # ipcrm コマンド
 21
 22    }
 23
 24    #
 25    # 16進数 -> 10進数変換
 26    #    cmd, dec はローカル変数として利用するために仮引数宣言とする
 27    #
 28    function    h2d(hex,            cmd, dec)
 29    {
 30
 31        # printf(1) を利用して 16進数を 10進数に変換
 32        cmd = sprintf("printf '%%d' %s", hex);
 33        # printf(1) の実行結果を getline で変数 dec に取得
 34        cmd | getline dec;
 35        close(cmd);
 36
 37        return dec;
 38
 39    }
 40
 41    #
 42    # 3連続するキーかのチェック
 43    #    start, i はローカル変数として利用するために仮引数宣言とする
 44    #
 45    function    cont3(num,          start, i)
 46    {
 47
 48        start = h2d(key[num]);
 49
 50        for(i=1; i<3; i++)
 51            if(h2d(key[num+i]) != start + i)
 52                return 0;
 53
 54        return 1;
 55
 56    }
 57
 58    #
 59    # コマンド実行
 60    #
 61    function    exec(key, arg)
 62    {
 63
 64        system(sprintf("%s -%s %s", ipcrm, arg, key));
 65
 66    }
 67
 68    #
 69    # 共用メモリ/セマフォの削除
 70    #
 71    function    shmrm(key, num)
 72    {
 73
 74        # 共有メモリを削除
 75        exec(key, "M");
 76
 77        # セマフォを削除
 78        if(num % 3 == 2)
 79            exec(key, "S");
 80
 81    }
 82
 83    #
 84    # メイン処理
 85    #
 86    {
 87
 88        # owner が root で権限が 666 かつ nattch が 0 の共用メモリを検索
 89        if($3 == "root" && $4 == "666" && $6 == 0)
 90            key[max++] = $1;
 91
 92    }
 93
 94    #
 95    # 終了処理(ある意味こっちがメイン)
 96    #
 97    END{
 98
 99        if("'$1'" == "-f"){
100            # 強制モード -- 3連続していないキーでも削除
101            for(n in key)
102                shmrm(key[n], 2);
103        }
104        else {
105            # 通常モード -- 3連続しているキーが削除対象なので抽出する
106            for(i=0; i<max; i++)
107                if(cont3(i))
108                    for(j=0; j<3; j++)
109                        target[num++] = key[i+j];
110
111            # 削除対象の共有メモリ/セマフォを削除
112            for(n in target)
113                shmrm(target[n], n);
114        }
115
116    }
117'
    

2011/02/17

ssh

ssh (1) コマンドは接続先のホスト情報をローカルに保存していて、 接続するたびに接続先が正しいホストかを確認する機能を持っている。 これはホスト名の詐称や DNS poisoning などにより 正しくない接続先に接続してしまうという事態を防止するためには 非常に便利で有用な機能ではあるが、 接続先のシステムが再インストールなどでホスト情報が変更になってしまった場合、 ssh で接続しようとすると

$ ssh ホスト名
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX.
Please contact your system administrator.
Add correct host key in /home/ユーザ/.ssh/known_hosts to get rid of this message.
Offending key in /home/ユーザ/.ssh/known_hosts:NN
RSA host key for ホスト名 has changed and you have requested strict checking.
Host key verification failed.
$
    
などと表示されて接続できなくなってしまう。
画面に表示された通り known_hosts ファイルの該当行を削除すれば 再度接続できるのだが、 これを自動で行うためのスクリプトを作成してみた。

  1#!/bin/sh
  2cat << EOF | ex -s ${HOME}/.ssh/known_hosts
  3`ssh $* 2>&1 | sed -n '/^Offending key/s/.*:\(.*\)/\1/p' | tr -d '\r'`d
  4wq
  5EOF
    
PATH の通ったディレクトリに sshfix の様な名前で保存し 実行権限を付与しておくと、 ssh で接続を試みて上記エラーが発生した際に実行する事で known_host の該当行を削除するので再度接続できる様になる。

$ ssh ホスト名
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!</i>
   :
$ sshfix !$
$ ssh !$
        
くれぐれも本当に正しい接続先か確認してから利用しなければならない。

2011/02/12

シェルスクリプト自身のパス取得

シェルスクリプトを作成していて、 たまにスクリプト自身のパスを取得したい場合がある。
以下の処理でシェルの種類に依存せず(とは言っても bourne shell 系のみだが) シェルスクリプト自身のパスを取得する事ができる筈(多分)。
PATH 環境変数に従って実行された場合は ${0} にスクリプトの絶対パスが格納されているので ${0}dirname (1) を取得すれば良いが、 相対パス指定で実行された場合は 1度 cd (1) した上で pwd (1) を実行してカレントディレクトリを取得する必要がある。
すべてのケースで "( cd ${0%/*} `dirname ${0}` && pwd )" しても同じ結果を得られるのだが、 サブシェルの実行や特に pwd (1) の実行はシステムに負荷をかけるので、 効率を考え ${0} の値を確認している。
expr (1) の実行も結構負荷がかかるからあまり意味ないかな…

  1#!/bin/sh
  2# ${0} の dirname を取得
  3# cwd=${0%/*}
  4cwd=`dirname ${0}`
  5
  6# ${0} が 相対パスの場合は cd して pwd を取得
  7expr ${0} : "/.*" > /dev/null || cwd=`(cd ${cwd} && pwd)`
  8    :
    

2011/03/17 追記

$ sh foo
	
この様に実行された場合 ${0} には foo が 格納されているので ${0%/*} では foo に展開されてしまい cd (1) がエラーとなる問題があったので、 dirname (1) を利用する様修正しました。


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