2014/02/06
OSX の pkgutil コマンド
OSX で .pkg 型式のインストーラを実行する時に
このパッケージは、ソフトウェアをインストールできるかどうかを判断するプログラムを実行します。
多くの場合は問題なくインストールできるのだが、 稀に
より新しいバージョンのソフトウェアがインストールされているのでインストールできません
的なメッセージでインストーラが終了してしまう場合がある。この様な場合、大抵は /Applications フォルダ等に存在する 該当のアプリケーションを削除するだけで 新しいアプリケーションのインストールが可能となるのだが、 ごくごく稀に削除しただけではインストーラが同じメッセージを表示してしまい インストール出来ない場合がある。
そもそも OSX の場合、アンインストーラが附属する一部のアプリケーション以外は
ファインダなどで /Applications フォルダの
アプリケーションアイコンをみ箱にドラッグするなどして削除すれば
アンインストールできるのだが、
それだけだと設定情報やインストール情報などがシステムに残ってしまう可能性がある。
ところが OSX の標準型式に準拠したインストーラからインストールされたソフトウェアは
pkgutil (1) コマンドを利用する事で
アプリケーションや設定情報などを完全に消去でき、
殆どの場合は上記インストーラによるチェックも問題なく通過できる様になる。
pkgutil (1) コマンドの基本的な利用方法
- 現在インストールされているパッケージ名の一覧を表示する。
$ pkgutil --pkgs
$ pkgutil --files パッケージ名
実行する場合は管理者権限が必要なので sudo (8) などで 管理者権限を取得する必要がある。
# pkgutil --unlink パッケージ名
実行する場合は管理者権限が必要なので sudo (8) などで 管理者権限を取得する必要がある。
# pkgutil --forget パッケージ名
pkgutil を実行する事でパッケージのインストール情報を完全に削除できるので、 アプリケーションを完全に削除したい場合は便利。
2014/02/01
ショルダーバッグ
ちょっと遅めのクリスマスプレゼント 兼 すごく遅めの誕生日プレゼントとして
ショルダーバッグを貰った。
シボ感のある上質な革を使用したシンプルで上品なショルダーバッグで、
デザインも容量も使い勝手も質感も全てが気に入っている。
たまたま遊びに行った羽田空港の国際線ターミナルのショップで見かけて、
お互いに一目で気に入った 輪怐・LIN-KUのショルダーバッグ。
普段ちょっと出かける時などに財布や携帯電話、サングラスケースなど
ちょっとしたモノを入れて気軽に持ち運べるバッグが欲しかったので、
まさにうってつけの素敵なバッグと出会えて非常に重宝している。
なかなか品質の良い皮革を使用しているとの事なので、
ちゃんと手入れをすればこの先何十年も愛用できると思う。
良い感じに使いこまれた革の表情を想像すると今から楽しみだ。
2014/01/16
爪切り
長年愛用していた爪切りの刃が欠けてしまったので、
複数の知人から絶賛されていた木屋の爪切りを購入した。
そもそも木屋というのは創業1792年の包丁などで有名な老舗らしく、
その老舗が丹精こめて作ったという爪切りの逸品らしい。
一般的に売られている爪切りはステンレス製の刃を採用しているが、
こちらの製品の刃は何と鋼製でまるで包丁の様な仕上げ。
小型ながら持った感じもずっしりと重くて、
控えめながら「道具」としての主張も忘れていない感じ。
爪を切る度にしっかりした手応えのある重さを感じる事ができ、
その度に「良い道具を使っている」という自己満足に浸れる(笑)。
今や 100円均一でも爪切りは買えるのだが、 たかだか数千円で爪を切る度にちょっとだけ良い気分になれるのは ある意味で安上がりな幸せじゃないかと思う。
2013/11/23
Exif 情報から GPS の緯度/経度情報を削除する
例によって こちら のブログをネタに
他人の褌で相撲を取る記事です。
元記事では Exif 情報を操作する時に、
バイナリを直接操作するのではなく
od (1) を利用してバイナリをテキスト型式に変換し、
テキスト操作した後で再度バイナリ型式に戻すという
正統的な手法でのアプローチをしています。
しかし dd (1) を利用すると
シェルスクリプトからバイナリを操作できるので、
バイナリ型式の画像を直接操作してしまうというアプローチを取ってみます。
画像ファイルに格納されている Exif 情報を簡単に解析して
GPS 情報を格納している領域 (GPSInfoIFD) から
緯度/経度情報だけをゼロに置換する処理となっています。
流石にシェル芸では無理なのでシェルスクリプトです。
1#!/bin/sh 2 3# 4# Endian を考慮して 4 バイト数字を取得する 5# 一度 od(1) で 16 進数に変換しバイト並びを修正した後に 10 進数に変換する 6# 7# $1: ファイル 8# $2: オフセット 9# 10get4Byte() 11{ 12 13 if [ ${endian} = "MM" ] 14 then 15 printf "%d" `dd if="${1}" bs=1 skip=${2} count=4 2> /dev/null | od -x | 16 sed -n "s/^0000000 *\(..\)\(..\)[ ]*\(..\)\(..\)/0x\2\1\4\3/p"` 17 else 18 printf "%d" `dd if="${1}" bs=1 skip=${2} count=4 2> /dev/null | od -x | 19 sed -n "s/^0000000 *\(..\)\(..\)[ ]*\(..\)\(..\)/0x\1\2\3\4/p"` 20 fi 21 22 23} 24 25# 26# Endian を考慮して 2 バイト数字を取得する 27# 一度 od(1) で 16 進数に変換しバイト並びを修正した後に 10 進数に変換する 28# 29# $1: ファイル 30# $2: オフセット 31# 32get2Byte() 33{ 34 35 if [ ${endian} = "MM" ] 36 then 37 printf "%d" `dd if="${1}" bs=1 skip=${2} count=2 2> /dev/null | od -x | 38 sed -n "s/^0000000 *\(..\)\(..\)/0x\2\1/p"` 39 else 40 printf "%d" `dd if="${1}" bs=1 skip=${2} count=2 2> /dev/null | od -x | 41 sed -n "s/^0000000 *\(..\)\(..\)/0x\1\2/p"` 42 fi 43 44} 45 46# 47# 任意の長さの文字列を取得する 48# 49# $1: ファイル 50# $2: オフセット 51# $3: 文字列長 52# 53getString() 54{ 55 56 dd if="${1}" bs=1 skip=${2} count=${3} 2> /dev/null 57 58} 59 60# 61# GPSInfoIFD の解析を行い緯度/経度情報をクリアする 62# 63# $1: ファイル 64# $2: GPSInfoIFD のタグ数 65# $3: GPSInfoIFD のオフセット位置 66# 67gps() 68{ 69 70 local i j offset tag val 71 72 i=0 73 74 while [ ${i} -lt ${2} ] 75 do 76 # GPSInfoIFD のタグ情報 77 offset=`expr ${3} + 14 + \( ${i} \* 12 \)` 78 tag=`get2Byte "${1}" ${offset}` 79 # タグ種類 80 # 2: 緯度 81 # 4: 経度 82 if [ ${tag} -eq 2 -o ${tag} -eq 4 ] 83 then 84 # データの数とデータのオフセットを取得 85 # 緯度/経度情報は値のタイプに RATIONAL(8バイト長) が指定されている 86 num=`get4Byte "${1}" \`expr ${offset} + 4\`` 87 val=`get4Byte "${1}" \`expr ${offset} + 8\`` 88 89 # 情報を削除するためにオフセットから 8 * データの数をゼロクリア 90 dd if=/dev/zero of="${1}" bs=1 conv=notrunc \ 91 seek=`expr ${val} + 12` count=`expr ${num} \* 8` 2> /dev/null 92 fi 93 i=`expr $i + 1` 94 done 95 96} 97 98# 99# 0th IFD データの解析処理 100# 101# $1: ファイル 102# $2: 0th IFD のタグ数 103# 104ifd() 105{ 106 107 local i offset count position 108 109 i=0 110 111 while [ ${i} -lt ${2} ] 112 do 113 # 0th IFD のタグ情報 114 offset=`expr 22 + \( ${i} \* 12 \)` 115 # GPSInfoIFDPointer の場合はオフセットとタグ数を取得 116 if [ `get2Byte "${1}" ${offset}` -eq 34853 ] 117 then 118 position=`get4Byte "${1}" \`expr ${offset} + 8\`` 119 count=`get2Byte "${1}" \`expr ${position} + 12\`` 120 121 gps "${1}" ${count} ${position} 122 fi 123 i=`expr $i + 1` 124 done 125 126} 127 128# 処理開始 129if [ "`getString "${1}" 6 4`" = "Exif" ] 130then 131 endian=`getString "${1}" 12 2` 132 133 ifd "${1}" `get2Byte "${1}" 20` 134fi
GPS 情報が削除されており緯度/経度がそれぞれゼロになっています。
2013/11/11
grep -o
少々前の話ではあるが、某ブログ にて
grep -o について言及する記事が出ていた。
非常に便利そうな機能ではあるのだが、
POSIX (IEEE Std 1003.1, 2013 Edition) 準拠ではなく独自拡張の機能の様なので
POSIX の範囲での実現方法を考えてみた。
例えば
$ grep -o 'href="[^"]*"'
は
$ sed -n 's/.*\(href="[^"]*"\).*/\1/p'
で代用可能なので、
$ cat << EOF | sed -n 's/.*\(href="[^"]*"\).*/\1/p' <!DOCTYPE html> <html> <body> <a href="http://aho.aho" target="_blank">あほ</a> <img src="aho.jpg" alt="aho">エロい画像</a> </body> </html> EOF href="http://aho.aho"という結果が得られる。
素晴らしい! POSIX 準拠なコマンドで POSIX 非準拠をぶち破ったぞ!
論破! 論破! 論破!
ただし grep (1) の -E オプションは POSIX に準拠しているが、 sed (1) の -E オプションは POSIX に定義されていないので、
$ grep -o -E '(src|href)="[^"]*"'
には対応できない。FreeBSD の sed (1) など -E オプションが利用できれば
$ sed -n -E 's/.*((src|href)="[^"]*").*/\1/p'
とする事で対応できるのだが、
$ cat << EOF | sed -n -E 's/.*((src|href)="[^"]*").*/\1/p' <!DOCTYPE html> <html> <body> <a href="http://aho.aho" target="_blank">あほ</a> <img src="aho.jpg" alt="aho">エロい画像</a> </body> </html> EOF href="http://aho.aho" src="aho.jpg"POSIX の範疇を逸脱した時点で grep -o を使えば良いとなってしまう。
残念っ!
2013/11/23 追記
頑張る必要は全くありません(笑)普段は GNU の便利なオプションも使っていますし、 POSIX 準拠していないコマンドやオプションも使ってます。
純粋に楽しいからやってるだけです。
2013/10/30
2013/10/02
国別フィルタの自動生成ツール
bsdhack.org ドメインは FreeBSD でサーバを構築しているのだが、
特定の国からのアタックや迷惑メイルが非常に多いので
ipfw (8) によるフィルタリングを利用して
指定した国からのパケットを拒否する設定を自動生成するツールを作成した。
APNIC から IPアドレスの割当リストを取得して、
IPアドレスを CIDR 型式に修正した上で
ipfw (8) のコマンドラインパラメタを自動生成している。
スクリプトは FreeBSD の ipfw (8) 向けだが、
出力部分を適宜修正する事で Linux の iptables (8) 向けの
設定も出力可能だと思う。
1#!/usr/bin/perl 2use Socket; 3 4# IP アドレス割当リスト取得 URL 5$url = "http://ftp.apnic.net/stats/apnic/delegated-apnic-latest"; 6 7# FreeBSD の ipfw (8) のルール番号 8if(@ARGV < 1){ 9 $rule = 2000; 10} 11else{ 12 $rule = $ARGV[0]; 13} 14 15# 拒否対象の国コード 16@country = ('KR', 'CN'); 17 18foreach $i (@country){ 19 $country{$i} = 1; 20} 21 22if(open(IN, "fetch -q -o - $url|")){ 23 # 拒否対象の国の場合は IP アドレスを保存 24 while(<IN>){ 25 if(/^apnic\|(..)\|ipv4\|(\d+.\d+.\d+.\d)\|(\d+)/){ 26 if($country{$1}){ 27 $table{inet_aton($2)} = $3; 28 } 29 } 30 } 31 close(IN); 32 33 # 取得した IP 割当リストから開始アドレスと個数を取得 34 foreach $net (sort keys %table){ 35 $addr = unpack('N', $net); 36 $num = $table{$net}; 37 while($num == $num[0] && ($addr ^ $addr[0]) == $num){ 38 shift @addr; 39 shift @num; 40 $addr &= ~$num; 41 $num <<= 1; 42 } 43 unshift(@addr, $addr); 44 unshift(@num, $num); 45 } 46 47 # IP アドレスと個数を CIDR 型式に変換して出力 48 while (@addr){ 49 for($num = pop(@num), $mask = 32; $num > 1; $num >>= 1, $mask--){} 50 $filt = inet_ntoa(pack('N', pop(@addr))) . "/$mask"; 51 # FreeBSD の ipfw(8) 向け出力 52 print "\${fwcmd} add $rule deny ip from $filt to any in recv \${wan}\n"; 53 # Linux の iptables(8) 向け出力 54 # print "\${iptables} -A INPUT -s $filt -j DROP\n"; 55 $rule++; 56 } 57} 58 590;
FreeBSD の場合は WAN 側のインタフェイス名が '${wan}' 変数に、 iptables コマンドの実行ファイル名が '${ipfw}' 変数に それぞれ格納されている事を前提としたスクリプトになっているので、 利用する際は適宜変更しながら使って下さい。
2013/09/18
main 関数の書き方
こちらの記事に触発されて、 C で同じお題のプログラム (echo (1) のサブセット)を書いてみた。
main には殆ど処理を記述せずに
パラメタ解析や割り込みなどの初期設定のみをしてから、
実際のメイン処理(この場合は echo)を実行するというスタンスは同じです。
ただし、今回の例で言えば getopt(3) を利用して
オプション解析を済ませた(実際に echo 出力する)パラメタ群のループは
main 側で行う方が好きかな。
1#include <stdio.h> 2#include <unistd.h> 3 4void echo(const char *p, int nonl) 5{ 6 7 /* 8 * ここは以下の様にすると printf(3)、putchar(3) の呼び出しを 9 * printf(3) の呼び出しのみに変更できソースもスッキリする。 10 * 1 文字の出力を '%s' で変換する無駄が微妙に気になるのだが、 11 * どちらかと言えば改行を出力する事の方が多いと思うので 12 * putchar(3) の呼び出しがない方が良いのか 13 14 printf("%s%s", p, nonl ? "" : "\n"); 15 16 */ 17 18 printf("%s", p); 19 if(!nonl) 20 putchar('\n'); 21 22} 23 24int main(int argc, char *argv[]) 25{ 26 27 int i, 28 nonl = 0; 29 30 while((i = getopt(argc, argv, "n")) != EOF){ 31 switch(i){ 32 case 'n': 33 nonl = 1; 34 break; 35 36 default: 37 break; 38 } 39 } 40 41 for(i=optind; i<argc; i++) 42 echo(argv[i], nonl); 43 44 exit(0); 45 46}
2013/08/02
シェルスクリプトでループ
シェルスクリプトでループ処理をする場合、
最も多用するのは while だと思う。
良く見かけるのは次の様な書き方だろう。
: while true do 処理 if 条件 then break fi done :勿論この書き方でも問題なく動作するが、 条件がコマンドの戻り値の場合は以下の方が簡潔に記述できる。
コマンドの戻り値が 0 (正常終了の場合) ループを続けたい場合。
: while コマンド do 処理 done :コマンドの戻り値が 0 以外(正常終了以外の場合) ループを続けるのであれば while の替わりに until が利用できる。
例えば再起動中のリモートサーバに ssh (1) 接続したい場合、 以下の方法で繰り返し接続を試行する事ができる。
until ssh -o 'ConnectTimeout 5' リモートサーバ do sleep 10 donessh (1) が成功するとリモートサーバから exit した時点で until の条件が真になるのでループを抜ける事ができる。
2013/07/31
MySQL データベースを Amazon S3 にバックアップするスクリプト
自宅サーバで運用している MySQL データベースのデータを Amazon S3 にバックアップするためのスクリプトを作成したので公開。
今回作成したスクリプトでは、
ローカルディレクトリに YYYYMMDD 型式で 7 世代分のデータをフルダンプし、
毎週日曜日の深夜に Amason S3 に当日分のバックアップを転送している。
mysqldump で取得したバックアップデータに VIEW 情報が含まれていると
データベースのレストア時にエラーが発生する可能性がある様なので、
mysqldump 時に VIEW 定義は除外してダンプを取得し
VIEW 情報のみを別途ダンプする様にしている。
Amason S3 への転送は S3 tools が公開している
s3cmd を利用しているので、
s3cmd と python のインストールは必須。
当然 Amazon S3 も利用できる様にしておく必要がある。
以下のスクリプトは MySQL の接続用パスワードや
Amazon S3 のアクセスキーなどを平文で保存しているので、
ファイルのアクセス権限には十分に注意が必要である。
バックアップは root 権限で実行する様にし
ファイルの権限は 500 に設定しておく事が望ましい。
1#!/bin/sh 2 3# MySQL 用の設定 4database="データベース名" 5user="-uユーザ -pパスワード" 6 7# Amazon S3 用の設定 8backet="backet 名" 9PASSPHRASE="パスフレーズ" 10AWS_ACCESS_KEY_ID="アクセスキーID" 11AWS_SECRET_ACCESS_KEY="SECRET アクセスキー" 12 13# ローカルにバックアップする世代数 14max=7 15# Amazon S3 に転送する曜日指定 (0: 日曜 … 6: 土曜) 16dayofweek=0 17 18# コマンド定義 19ls="/bin/ls -1" 20wc="/usr/bin/wc -l" 21rm="/bin/rm -r" 22head="/usr/bin/head -1" 23mkdir="/bin/mkdir -p" 24 25# ファイルとディレクトリ定義 26logfile="/var/log/`basename ${0}`.log" # ログファイル名 27backupdir="/var/db/backup" # バックアップ先ディレクトリ 28target="`date '+%Y/%m/%d'`" # Amason S3 でバックアップを格納するディレクトリ名 29targetdir="${backupdir}/`date '+%Y%m%d'`" # ローカルでバックアップを格納するディレクトリ名 30backupfile="${targetdir}/all_dump.sql" # バックアップファイル名 31createview="${targetdir}/create_view.sql" 32createtable="${targetdir}/create_table.sql" 33 34# MySQL コマンドの定義 35mysql="/usr/bin/mysql ${socket} ${user} -B -N -s -r " 36mysqldump="/usr/bin/mysqldump ${user} 37 -R --hex-blob --single-transaction" 38sql="select TABLE_NAME from information_schema.TABLES 39 where TABLE_SCHEMA = '${database}' and TABLE_TYPE = 'VIEW';" 40 41# s3cmd の定義 42s3cmd="/usr/bin/s3cmd put -rr" 43export PASSPHRASE AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY 44 45# ロギング開始 46umask 700 47exec > ${logfile} 2>&1 48set -x 49 50# ローカルディレクトリの準備 51# 最大世代数以上の場合は一番古い世代を削除する 52test `${ls} ${backupdir} | ${wc}` -ge ${max} && 53 ${rm} ${backupdir}/`${ls} ${backupdir} | ${head}` 54${mkdir} ${targetdir} 55 56echo "DB backup: start: `date`" 57 58# ダンプから除外するために VIEW を取得し create view 文をダンプする 59for i in `${mysql} ${database} -e "${sql}"` 60do 61 view="${view} --ignore-table=${database}.${i}" 62 ${mysql} ${database} -e "show create view ${i}" 63done > ${createview} 64 65# 全データダンプ 66${mysqldump} ${view} ${database} > ${backupfile} 67 68# create table 文のダンプ 69${mysqldump} --no-data ${view} ${database} > ${createtable} 70 71# 指定された曜日の場合は Amazon S3 に転送する 72# 以下の 1行をコメントとすると 毎日 Amazon S3 に転送する 73test `date '+%w'` -eq ${dayofweek} && 74 ${s3cmd} ${targetdir}/ s3://${backet}/${target}/ 75 76echo "DB backup: end: `date`"