2011/11/26
shell での while ループにおける問題
シェルスクリプトでコマンドの出力をループ処理する場合は
パイプ | を使用するのが一般的な手法だと思うが、
パイプ | を使うとループが別なプロセスとして実行されるので
ループ内部で設定したシェル変数がループ外部で参照できないという問題があり、
(存在するとすれば) shell script 業界では割と FAQ 的な問題だ。
何が問題かと言って posix で定義されていないから
シェルの実装に依存しているのが一番の問題だったりするのだろう。
この問題に対しては、シェルビルトインの exec (1) を利用して
ディスクリプタを複製して while ループの入力を標準入力にしてしまうのが
一番汎用的で柔軟性のある対応だと思うのだが、
シェルを愛する面々は
皆同じ様な苦労をしているのだ
と改めて認識した。
当初は一時ファイルを作成する方法を考えついたのだが、
自分的にはなるべく一時ファイルを使いたくなかったので
ファイルから入力する部分をバッククォートによるコマンド実行に置き換え等、
色々と試行錯誤した結果ヒアドキュメントを使う方法に落ち着いた。
# これでは正常に動作しなかった exec 3<&0 0<`${command}` while read line :
勿論一時ファイルを作成しても良いのだが、 ヒアドキュメントを利用する事で一時ファイルが不要になる分だけ 処理的に美しいかなと思う (完全に好みの問題)。
1#!/bin/sh 2# コマンドの出力をバッククォートしてヒアドキュメントとして使用する 3 4command="/path/to/command" 5count=0 6 7# exec 3<&0: 標準入力 (FD0) を FD3 に複製 8# exec 0<<EOF: ヒアドキュメントを標準入力として使用 9exec 3<&0 0<<EOF 10`${command}` 11EOF 12 13while read line 14do 15 : 16 count=`expr ${count} + 1` 17done 18 19# exec 0<&3: FD3 に複製した標準入力 (FD0) を復帰 20# exec 3<&-: FD3 をクローズ</i> 21exec 0<&3 3<&- 22 23echo ${count}
もう一点は子プロセスで親プロセスの変数値を変更する方法。
こちらはプロセスが別になってしまうとファイルを使うしかないので、
同じプロセス内で関数呼び出しとして対応する方法が汎用的ではないだろうか。
1#!/bin/sh 2# eval を利用して呼び元の変数を設定する 3 4default="デフォルト値" 5 6sub() 7{ 8 9 local _val 10 11 : 12 _val="設定したい値" 13 14 eval "${1}='${_val:-${default}}'" 15 16} 17 18value="元の値" 19 20sub value 21 22echo ${value}
ちなみに上のリンク元にある parent.sh の中の処理で
${tmp_file} の内容を for ループで eval しているが、
これはシェルビルトインの . もしくは source を利用して
. ${tmp_file} としても同一の結果が得られるので楽だと思う。
1#!/bin/sh 2 3g1="aa" 4g2="bb" 5 6tmp_file=`mktemp /tmp/ps.XXXXXX` 7export tmp_file 8 9./child.sh # child.sh で、上で設定した変数 g1, g2 の値を変更する 10 11# for v in `cat $tmp_file`; do 12# eval $v 13# done 14# ${tmp_file} の内容は name='value' 形式が保証されているので 15# ファイル自体を `.' コマンドで読み込むだけで 16# 行毎に eval を実行しなくてもシェル変数に代入された値が利用できる 17 18if [ -f ${tmp_file} ] 19then 20 . ${fmp_file} 21 rm -f ${tmp_file} 22fi 23 24echo $g1 #=> cc に変更された 25echo $g2 #=> dd に変更されたブログに直接コメントしたかったのだが、 はてなのアカウントが無いので申し訳ないがここで意見させて頂く。
2011/11/24
こどもの国
朝から好天に恵まれてぽかぽかな冬の休日を無駄にするのは勿体ないので、
かねてから気になっていた「こどもの国」に家族で出かけてみた。
行く前は「こどもの国」という名称から
『どうせ地方にありがちな子供向けのしょぼい遊園地の類だろう』等と
高をくくっていたのだが、実際に行ってみると大違い。
東京都と神奈川県にまたがる広大な敷地が、
自然の地形や環境上手に活かさて大人でも十分楽しめる遊園となっていて、
良い意味で期待が裏切られた。
あまりに広すぎるので一日では回りきれず
全体の半分程度を結構な駆け足で巡っただけなのだが、
それでも地面に自由に落書きできる広場や
本格的な牧場やポニーへの乗馬体験、
伊豆のサイクルスポーツセンターを彷彿とさせる様な
様々な変わり種自転車を集めた施設など盛りだくさんの内容だった。
しかも牧場では定番のソフトクリームや 首都圏で随一生産されるらしい牛乳も楽しめる。
2011/11/16
メモリリークチェッカー
c で daemon 動作するサーバを作っていると
malloc (3) などで動的に確保したメモリの free (3) 忘れによる
いわゆるメモリリークを防ぐ事が非常に重要になってくる。
この様なメモリリークを防止するために役立つ
様々なツールが世の中には沢山存在しており
それぞれ非常に有用ではあるのだが、
いざ利用しようと思うと割と面倒な作業が発生したり、
自分の求めている機能に対して明らかにオーバースペックだったりするので、
自分の用途に合わせて簡単に使える『俺様ツール』を作ってみた。
動作原理は簡単で malloc (3) や free (3) したアドレスを
ログに出力して、
後からログを解析してメモリの確保と解放が対になっていない部分を
抽出するだけである。
そのためにまずは元となるソースコードを修正して、
malloc (3) や free (3) を独自に作成した wrapper 関数に置換えて
確保、解放したアドレスをログに出力する機能を組込む必要があるのだが、
malloc (3) などはエラー処理を含めて共通化するために
あらかじめ wrapper 関数化されている場合が多いと思うので、
この部分はフォーマットを決めるだけで割りと簡単に追加できると思う。
今回は syslog (3) を利用して malloc (3)、strdup (3)、
free (3) で確保/解放するアドレスを出力した。
1/* 2 * malloc() の wrapper 3 */ 4void *mymalloc(size_t size) 5{ 6 7 void *p; 8 9 if(!(p = malloc(size))) 10 /* エラー処理 */; 11 syslog(LOG_DEBUG, "malloc: %p\n", p); 12 13 return(p); 14 15} 16 17/* 18 * strdup() の wrapper 19 */ 20char *mystrdup(const char *s) 21{ 22 23 char *p = NULL; 24 25 if(s){ 26 if((p = strdup(s))) 27 syslog(LOG_DEBUG, "strdup: %p\n", p); 28 else 29 /* エラー処理 */; 30 } 31 32 return(p); 33 34} 35 36/* 37 * free() の wrapper 38 */ 39void myfree(void *p) 40{ 41 42 if(p){ 43 free(p); 44 syslog(LOG_DEBUG, "free: %p\n", p); 45 } 46 47}
次に出力されたログの解析処理であるが、
確保したメモリのアドレスをキーとしたハッシュテーブルを利用したいので
入力行の解析やハッシュテーブルが簡単に利用できる言語として
今回はコマンドライン版の php (1) を利用してみた。
もちろん awk (1) を駆使したり perl (1) を利用しても問題ない。
1<?php 2 /* 3 * Copyright (c) 2011 Mitzyuki IMAIZUMI, All rights reserved. 4 * 5 * $Id: memcheck.php 1677 2011-10-12 06:58:03Z mitz $ 6 */ 7 8 /* 冗長指定 */ 9 if($argv[1] == "-v"){ 10 $verbose = 1; 11 array_shift($argv); 12 } 13 14 if($fp = fopen($argv[1], "r")){ 15 $line = 0; 16 while($buf = fgets($fp, 1024)){ 17 $buf = ltrim($buf); 18 $line++; 19 /* 20 * malloc か strdup か free を含む行の場合 21 * コマンドを配列 $p[0] に、アドレスを配列 $p[1] に格納する 22 */ 23 if(preg_match("/^(malloc|strdup|free):\s(.*)/", $buf, $p)) 24 /* 解放処理 */ 25 if($p[1] == "free") 26 /* 確保済みテーブルに解放アドレスが存在する場合 */ 27 if($alloc[$p[2]]){ 28 if($verbose) 29 printf("%s: % 8d -> % 8d\n", $p[2], $alloc[$p[2]], $line); 30 $alloc[$p[2]] = 0; 31 } 32 33 /* 確保済みテーブルに解放アドレスが存在しない場合 */ 34 else 35 printf("%s: unknown free at % 8d\n", $p[2], $line); 36 else 37 /* アドレスをキーとしたハッシュテーブルに行番号を格納 */ 38 $alloc[$p[2]] = $line; 39 40 } 41 fclose($fp); 42 43 foreach($alloc as $k => $v) 44 if($v) 45 printf("%s: NOT free (% 8d)\n", $k, $v); 46 } 47?>このスクリプトを実行する事により malloc(3) もしくは strdup(3) で確保したメモリが解放されていない場合、 もしくは確保していないメモリを解放した場合が簡単に発見可能である。
これらは自分が必要とする機能のみを簡単に実装したものであり、 例えば解放していない事を意図しているメモリまで警告されてしまう等、 汎用的に使える事を目指したツールでは勿論ない。 ただ、日頃発生しうる面倒な作業がちょっとの工夫で 多少なりとも楽になるだろう例の一つとして公開してみた次第である。
2011/11/28 追記
awk (1) 版の方が汎用的なので簡単に作成してみた。 処理内容は上記 php (1) 版と同じである。1#!/bin/sh 2 3if [ "${1}" = "-v" ] 4then 5 verbose=true 6 shift 7fi 8 9awk ' 10 /^(malloc|strdup)/{ 11 alloc[$4] = NR; 12 } 13 /^free/{ 14 if(alloc[$4]){ 15 if("'${verbose:-false}'" == "true") 16 printf("% 5d %s: % 5d\n", NR, $4, $alloc[$4]); 17 alloc[$4] = 0; 18 } 19 else 20 printf("% 5d %s: unknown free.\n", NR, $4); 21 } 22 END{ 23 for(i in alloc) 24 if(alloc[i]) 25 printf("% 5d %s: NOT free.\n", alloc[i], i); 26 } 27' ${1} | sort ${2}
2011/10/30
2011/10/18
電源関係の小物たち
いつも鞄に入れて持ち歩いている電源達関係の小物
- Apple 純正の AC アダプタ 普段から MacBookAir を持ち歩いているので、 Apple 純正の AC アダプタは必須 (IBM のバンドでまとめてあるのは愛嬌です)。
- USB 電源アダプタ 携帯電話や BlueTooth 機器など USB から充電できる機器が増えているので、 100V コンセントから USB の 5V を出力するアダプタも何かと便利。
- HyperMac 特許の関係で入手が不可になった HyperMac とそのケーブル。 これは MacBookAir からは通常の電源に見えるので、 純正のエアラインアダプタとは異なり充電もできる優れもの。 このおかげでお世辞にも長いとは言えない MacBookAir の駆動時間が 飛躍的に向上出来るのでモバイルには必須とも言えるアイテム。
- Griffin の USB Mini-Cableset。
Apple も特許が大切なのは判るんだけど、 こういう素晴らしい商品はライセンスするとか対応を考えて欲しいと思う。
2011/09/17
Mac OS X Lion on VMware Fusion 4
すっかり世間の話題からは乗り遅れた感があるが、
そろそろ Mac OS X Lion を使ってみたくなってきた。
しかし、現在使用している Snow Leopard から
操作性が大きく変更になったと噂で聞いているので、
自宅でメイン環境として利用している MacBook Pro や
職場で生活環境、かつ開発環境として利用している iMac 27″ に
Lion を導入する事は躊躇していた。
しかし、Mac OS X Lion をゲスト OS として正式にサポートした
VMware Fusion 4 が発売されたので、
まずは VMware Fusion 4 の仮想環境に Mac OS X Lion をインストールして
操作性などを検証してみる。
少なくとも現状では旧バージョンの VMware Fusion からの
優待アップグレードなどは存在しない様なので新規購入する必要がある様だ。
期間限定ではあるが新規購入でも 49.99 USD と格安なので アップグレード並の低価格ではあるが…。I have a previous version of Fusion (1.x, 2.x, or 3.x.). Am I eligible to purchase an upgrade to Fusion 4?
There is no upgrade option for VMware Fusion 4. You may purchase a full license from our VMware online store. Licenses cost $49.99 USD (for a limited time only).
VMware Fusion は日本では以前より act2 が正規代理店として発売しているが、
MacUpdate のクーポン
を利用して直接 VMware 社のサイトから USD の決済でダウンロード購入すると
10 USD ディスカウントとなり 39.99 USD で購入が可能となるので、
迷わず VMware 社サイトのオンラインストアにてダウンロード版を購入する。
ちなみに VMware Fusion は完全に国際化されているので、
日本で購入しなくてもインストーラからちゃんと日本語で表示される。
円高なので約 3,000 円程度で最新の仮想環境が入手できるのは素晴らしい。
Mac OS X Lion は AppStore からこちらもダウンロード販売で購入する。
こちらは円高の恩恵は受けられなかったのだが、
元々安価で提供されているのでそれほど気にならない。
VMware Fusion 4 を起動して新規仮想マシンアシスタントを起動したら、 インストールメディアの選択画面でダウンロード購入した Mac OS X Lion のインストーラを指定すれば簡単にインストールでき、 Mac OS X Lion の動作する検証用の仮想環境が構築できる。
肝心の Mac OS X Lion の操作性だが、
今は慣れていないのでまごつく事も多々あるのだが、
慣れれば良いのかな?というのが現状の感想。
もう暫く仮想環境で Lion の使い方に慣れたら
実マシンを Lion に移行してみようかとは思っている。
2011/09/09
Bowers & Wilkins C5
普段使いのヘッドフォンのケーブルが断線してしまったので、
発表されたばかりの Bowers & Wilkins 初のカナル型ヘッドフォン C5 を
試聴もしないまま購入する事にした。
購入時は日本未発売で円高の後押しもあったのでアメリカから個人輸入したのだが、
国内で購入するよりは随分と安く済んだ。
故障時の保証や言葉の問題など、
海外から直接個人輸入する事にはデメリットもあるので万人に勧められはしないが、
金額以外のメリットを享受できる場合もあるので
悪くない選択肢の一つだとはおもう。
新品の状態で聴いてみた印象は音場は結構広がりがある感じだが、
新品故の固さというかドンシャリ感が強い感じ。
とは言っても聴き疲れするタイプの音質ではないし、
今の段階でも上品な感じの低音がしっかり鳴っているので、
今後のエージングに期待してみる。
外観の特徴にもなっている筐体外側のマイクロ多孔質フィルターの効果なのか
装着した時の耳への圧迫感が随分軽減されていて、
カナル型なのにも関わらず長時間装着していても苦にならない快適さだと思う。
もう一つ特徴的な外観を形成しているセキュア・ループ・デザインは、
耳たぶの内側にあてる事で装着時のフィット感を高める造りとなっている。
簡単にサイズも調節可能だし見た目も特徴的なので悪くない。
ケーブルの途中に組み込まれたリモコンは
手持ちの iPod や MacBook Pro の iTunes を操作できるので意外と便利だ。
リモコンはボタンが 3 個なので
再生・停止とボリューム操作だけだと思っていたのだが、
ちゃんとマニュアルを読むと 2 回クリックで次の曲、
3 回クリックで前の曲へのスキップも可能だった。
ピンクノイズなどで暫くエージングした後も第一印象とそうは変わらない感じだが、 固めにドンシャリと響いていたカドが丸まったと思う。 上品に響く低音と抜けの良い高音、それから広い音場とあいまって とても優しく聴き疲れしない良質な音になっていると思う。
2011/08/12
Mac で Planex URS-03 (USB-Serial 変換アダプタ) を利用する
Planex URS-03 もメイカーは Mac 用のドライバを提供していないが、
利用しているチップセットのメイカー (Prolific Technology Inc. 社) の
Web サイト
から Mac 用のドライバをダウンロードしてインストールした上で、
ドライバの Info.plist ファイルに URS-03 の
Vendor ID と Product ID を登録する事で正しく利用できる様になる。
URS-03 の場合は /dev/tty.usbserial という
デバイスとして認識されるので、
このデバイスを利用して通信する事ができる様になる。
URS-03 の Vendor ID と Product ID はアダプタを接続した状態で、
以下のコマンドを実行して
# /usr/sbin/ioreg -l | lessX code がインストールされている場合は /Developer/Applications/Utilities/USB Prober.app を実行して
URS-03 の Vendor ID は 1367(10)、 Product ID は 8200(10) なので、 Info.plist の内容を以下の様に修正する。
1<?xml version="1.0" encoding="UTF-8"?> 2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3<plist version="1.0"> 4 <dict> 5 <key>CFBundleDevelopmentRegion</key> 6 <string>English</string> 7 <key>CFBundleExecutable</key> 8 <string>ProlificUsbSerial</string> 9 <key>CFBundleGetInfoString</key> 10 <string>ProlificUsbSerial v2.2.0, Copyright 2006 Prolific Technology Inc.</string> 11 <key>CFBundleIdentifier</key> 12 <string>com.prolific.driver.PL2303</string> 13 <key>CFBundleInfoDictionaryVersion</key> 14 <string>6.0</string> 15 <key>CFBundleName</key> 16 <string>Mac OS X Driver for Prolific USB-to-Serial Bridge Device</string> 17 <key>CFBundlePackageType</key> 18 <string>KEXT</string> 19 <key>CFBundleShortVersionString</key> 20 <string>2.2.0</string> 21 <key>CFBundleSignature</key> 22 <string>????</string> 23 <key>CFBundleVersion</key> 24 <string>2.0.0</string> 25 <key>IOKitPersonalities</key> 26 <dict> 27 <key>067B_2303</key> 28 <dict> 29 <key>CFBundleIdentifier</key> 30 <string>com.prolific.driver.PL2303</string> 31 <key>IOClass</key> 32 <string>com_prolific_driver_PL2303</string> 33 <key>IOProviderClass</key> 34 <string>IOUSBInterface</string> 35 <key>bConfigurationValue</key> 36 <integer>1</integer> 37 <key>bInterfaceNumber</key> 38 <integer>0</integer> 39 <key>idProduct</key> 40 <integer>8200</integer> <!-- 変更 --> 41 <key>idVendor</key> 42 <integer>1367</integer> <!-- 変更 --> 43 </dict> 44 </dict> 45 <key>OSBundleLibraries</key> 46 <dict> 47 <key>com.apple.iokit.IOSerialFamily</key> 48 <string>1.0.4</string> 49 <key>com.apple.iokit.IOUSBFamily</key> 50 <string>1.8</string> 51 <key>com.apple.kpi.iokit</key> 52 <string>10.0.0</string> 53 <key>com.apple.kpi.libkern</key> 54 <string>10.0.0</string> 55 <key>com.apple.kpi.mach</key> 56 <string>10.0.0</string> 57 </dict> 58 </dict> 59</plist>これで kextload (8) を実行してドライバをロードすると URS-03 が無事に利用可能となる。
ただし、本来サポート対象のものを無理矢理利用しようとしているので、 URS-03 を接続した際にカーネルモジュールを自動でロードできない。 そこで 起動時に任意のコマンドを実行する 機能で起動時にカーネルモジュールをロードする設定を追加すると URS-03 を接続するだけで利用可能になる。
2011/07/07
パイプコマンド中の終了ステータス取得
シェルスクリプトでは頻繁に複数のコマンドをパイプで連結して使用するが、
パイプの途中のコマンドの終了ステータスは通常では参照できない。
そこでリダイレクトを利用してパイプの途中のコマンドの
終了ステータスを取得する方法を考えてみる。
1exec 3>&1 2ret1=`{ { command1; echo $? 1>&3; } | command2; } 3>&1` 3ret2=$?exec を利用してあらかじめ FD3 を利用可能にしておき、 command1 の終了ステータスを FD3 に出力している。
command2 の実行後に FD3 の出力を FD1(標準出力) に変更しているので、 ret1 は標準出力から command1 の終了ステータスが取得できる。 command2 の終了ステータスはそのまま $? を参照して ret2 に格納される。
2011/07/04
awk (1) 小技
先日参加した勉強会で awk (1) 関連の発表があったので、 ちょっとした小技を紹介してみる。
- system() を利用する際の注意
-
awk の中で外部コマンドを実行する場合は
組み込み関数 system() が利用できるが、
この関数を利用して外部コマンドを実行すると
入出力 stream がオープンされるので、
ファイルディスクリプタが消費されてしまう。
従って awk 内で何度も system() を実行すると、 オープンできるディスクリプタの上限を超過してしまい、 awk の実行が中断されてしまう可能性がある。
その場合 close() を利用して system() がオープンした stream を閉じれば良いのだが、 awk の system() では stream を明示的に扱わない。 そこで system() で実行したコマンドを close() の引数で指定する事で stream を閉じる事ができる。1awk '{ 2 : 3 # コマンドを生成 4 command = sprintf("%s %s %s", command, arg1, arg2); 5 # コマンドを実行して結果を取得 6 buf = system(command); 7 # stream をclose 8 close(command); 9 : 10}'
- 関数内ローカルな変数の使用
-
nawk は function を利用する事で
内部関数を作成する事ができるが、
関数内でローカルな変数を明示的に定義する事ができない。
そこで関数宣言の仮引数部に変数を定義する事で、
関数内ローカルの変数として利用できる。
1awk '{ 2 : 3 # 関数 foo の定義 4 # num: ループ数 5 # 以下ローカル変数 6 # i: ループウンタ 7 function foo(num, i) 8 { 9 # i はローカル変数扱いなので呼出元の i は影響を受けない 10 for(i=0; i<1num; i++) 11 : 12 } 13 : 14 foo(num); 15 : 16 }'
但し関数呼び出し時と関数定義時に引数の数が等しくないので、 後に混乱しないためにコメントで明記するなどの処置は必要。