世代や職業によって変化する”Hello World”プログラム

GNUのジョーク集「Hello World!」という面白い記事があったので、少しだけ邦訳して、コメントを添えて掲載します。

中高生

[vb]
10 PRINT "HELLO WORLD"
20 END
[/vb]

まずは基本のBASICです。

大学生(初年度)

[perl]
program Hello(input, output)
begin
writeln(‘Hello World’)
end.
[/perl]

Pascalを覚えたようです。

大学生(卒業年)

[perl]
(defun hello
(print
(cons ‘Hello (list ‘World))))
[/perl]

Lispを使えるようになりました。

新人プログラマー

[cpp]
#include <stdio.h>

void main(void)
{
char *message[] = {"Hello ", "World"};
int i;
for(i = 0; i < 2; ++i)
printf("%s", message[i]);
printf("\n");
}
[/cpp]

Cを覚えました。ポインタも配列も使えます。

ベテランプログラマー

[cpp]
#include <iostream.h>
#include <string.h>
class string
{
private:
int size;
char *ptr;
public:
string() : size(0), ptr(new char(‘\0’)) {}
string(const string &s) : size(s.size)
{
ptr = new char[size + 1];
strcpy(ptr, s.ptr);
}
~string()
{
delete [] ptr;
}
friend ostream &operator <<(ostream &, const string &);
string &operator=(const char *);
};

ostream &operator<<(ostream &stream, const string &s)
{
return(stream << s.ptr);
}
string &string::operator=(const char *chrs)
{
if (this != &chrs)
{
delete [] ptr;
size = strlen(chrs);
ptr = new char[size + 1];
strcpy(ptr, chrs);
}
return(*this);
}
int main()
{
string str;
str = "Hello World";
cout << str << endl;
return(0);
}
[/cpp]

クラスを用いて、C++を使いこなせていますね。

システム管理者

[cpp]
#include <stdio.h>
#include <stdlib.h>
main()
{
char *tmp;
int i=0;
/* on y va bourin */
tmp=(char *)malloc(1024*sizeof(char));
while (tmp[i]="Hello Wolrd"[i++]);
/* Ooopps y’a une infusion ! */
i=(int)tmp[8];
tmp[8]=tmp[9];
tmp[9]=(char)i;
printf("%s\n",tmp);
}
[/cpp]

Cですが、コメントを見るとフランス人のようです。
まわりくどいことをやっている例でしょうか。

見習いハッカー

[perl]
#!/usr/local/bin/perl
$msg="Hello, world.\n";
if ($#ARGV >= 0) {
while(defined($arg=shift(@ARGV))) {
$outfilename = $arg;
open(FILE, ">" . $outfilename) || die "Can’t write $arg: $!\n";
print (FILE $msg);
close(FILE) || die "Can’t close $arg: $!\n";
}
} else {
print ($msg);
}
1;
[/perl]

書き易くて読みにくいPerlは、ハッカーの基本言語ですね。

上級ハッカー

[cpp]
#include <stdio.h>
#include <string.h>
#define S "Hello, World\n"
main(){exit(printf(S) == strlen(S) ? 0 : 1);}
[/cpp]

CでPerlのような読みにくいコードをガリガリ書けるようになり、
書いた本人も空気のように読めるようになってきます。

熟練ハッカー

[shell]
% cc -o a.out ~/src/misc/hw/hw.c
% a.out
Hello, world.
[/shell]

熟練になると、よく使うコードはコンパクトに持ち歩けるようにしておき、
必要な時にコンパイルして動かします。コーディングはしません。

グル・ハッカー

[shell]
% cat
Hello, world.
[/shell]

プログラミングの神様は、コーディングどころかコンパイルすらしません。
(catコマンドは引数を与えないと、標準入力に入ったものを標準出力に吐きます)

新人マネージャ

[vb]
10 PRINT "HELLO WORLD"
20 END
[/vb]

上の覚えてます? ;-)

中間管理職マネージャ

[shell]
% mail -s "Hello, world." bob@b12
Bob, could you please write me a program that prints "Hello, world."?
I need it by tomorrow.
^D
[/shell]

「明日までに必要なんだ!」

上級マネージャ

[shell]
% zmail jim
I need a "Hello, world." program by this afternoon.
[/shell]

「今日の午後、必要なんだ!」

経営責任者

[shell]
% letter
letter: Command not found.
% mail
To: ^X ^F ^C
% help mail
help: Command not found.
% damn!
!: Event unrecognized
% logout
[/shell]

「使い方が分からん、畜生!」

科学研究者

[vb]
PROGRAM HELLO
PRINT *, ‘Hello World’
END
[/vb]

科学分野で人気のFortran90です。

熟練科学研究者

[vb]
WRITE (6, 100)
100 FORMAT (1H ,11HHELLO WORLD)
CALL EXIT
END
[/vb]

さらに古いFORTRAN77です。

これらはジョークなので、本当にこれらの人が上のようなコーディングをしているわけではありませんが、スキルや考え方、他業種から見たイメージなどを上手く皮肉っていますね。

tumblrの画像を一括ダウンロードするスクリプト

tumblrの画像を一括ダウンロードするbash向けシェルスクリプトを書いてみました。

あまり効率的ではないですが、投稿を1ページずつ取得して、含まれる画像を抜き出しています。また、ダウンロードが完了したらZIPファイルに固めてくれます。

[bash]

!/usr/bin/env bash

#

simple picture downloader for tumblr

#

written by dyama (https://dyama.org/), Feb 1 2013

#

usage:

gettumblr http://example.tumblr.com/

function gettumblr()
{
url=$1

# fix the url
url=${url#http://} # this pattern is not regex!
url=${url%%/*}

# check the url and get tumblr user acount
if [[ $url =~ ^([a-z0-9]+)\.tumblr\.com$ ]]; then
    USER=${BASH_REMATCH[1]}
    echo OK, Target user name is $USER. &gt;&amp;2
else
    echo Error: $url is not tumblr URL. &gt;&amp;2
    return 1
fi

# working directory
wdir=&quot;/tmp/gettumblr$$&quot;
if [ ! -d $wdir ]; then
    mkdir $wdir
    if [ $? -ne 0 ]; then
        echo Error: Cannot make working directory at $wdir. &gt;&amp;2
        return 1
    fi
fi

pushd $wdir

for i in {1..999};
do
    echo Getting posted page $i ... &gt;&amp;2
    wget -O - -q &quot;http://$url/page/$i&quot; | grep -oE 'http[^&quot;]+post[^&quot;]+' \
        | grep $USER &gt; list
    [ $? -ne 0 ] &amp;&amp; break;
    echo -e '\tGetting included image ...' &gt;&amp;2
    grep -v frame list | wget -q -i - -O - | grep 'og:image' \
        | grep -v facebook | grep -oE 'http[^&quot;]+' | wget -i - -nv
done

file=$wdir/tumblr_$USER.zip
zip $file *

popd

mv $file .
rm -rf $wdir

echo Done. &gt;&amp;2

}

gettumblr "$*"
exit $?
[/bash]

関数 gettumblr() の冒頭部分のパターンによる変数保持値の編集や正規表現パターンマッチ構文は、bash依存です。また、grep の -o オプション(パターンに一致した行の、パターンに一致した部分だけを標準出力に印字する)も多用しています。このオプションは古めの grep にはなかったはずなので、注意が必要です。

ターゲットのtumblrユーザが存在しない場合、ファイルが削除できなかった場合など、例外処理はまったく書いていなかったり、一回で済むような処理を分けて書いていたりと、トライアンドエラーの跡が残ったままですので、もし流用する場合には適宜書き換えてください。

SONY VAIO Z シリーズに Ubuntu 12.10 を

総選挙があって、円安にシフトし初めた2012年12月にソニーのVAIO Zシリーズを購入しました。Ubuntu 12.10 をインストールして1ヶ月ほど使ってみたので、そろそろレビューを書いておきます。

なぜVAIOなのか

2012年の初めの頃まで、NECのLaVie LightにUbuntuを入れて普段使いしていたのですが、さすがにスペックがキツくなってきたのを理由に、マウスコンピューターのBTOラップトップを購入しました。Intel Core i5、メモリ 8GB とそこそこのスペックのマシンでお値段は4万円程度したのですが、手にした際に色々と問題があった為、半年ほどで弟に譲りました。

そこからの教訓か、「少々値段が高くなってもちゃんと使えるマシンを」という考えが出てきて、国内メーカーのBTOできるラップトップを視野に入れて検討していました。

先日、英語配列キーボードに脳内ドライバを切り替えたこともあって、BTO項目に最初から英語配列キーボードが選択できるものに搾ると、ソニーのVAIOか、アップルのMac Book Pro /Air くらいしか選択肢がありませんでした。他のメーカーももっと気合を入れて探せば、英語配列キーボードを選択できるのかもしれませんが、VAIOは初めてじゃないし、Zシリーズの評判もなかなか良かったので、VAIOに決めました。

VAIO Z シリーズ自体の素晴しさについては、他のレビューサイトをご覧ください。薄いし高性能だし、サクサク動いて快適ですよ!

日本の大手メーカーという不安

Linuxユーザーにとって、日本の大手メーカー製のコンピュータを購入する際に悩ましいのが、ハードウェアに独自の機能を盛り込んでしまう点です。個人的な意見ですが、日本の大手メーカーは市場に媚びすぎている感じがします。高性能な良いハードウェアを作る力は持っていますので、ユーザに媚びるとハードウェアは余計にガラパゴス化してしまいます。結果、Windowsでしか使えないようなハードウェアを平気で作ってしまいます。まあ勝てば官軍、Windowsで動けば商売的にはそれで良いんでしょうが、日本のコンピュータ産業はMicrosoftと心中でもする気なのかなあ、と一抹の不安もあります。

ただ、この不安も杞憂だった事をこのレビューで書けそうです。

カスタマイズ・オーダー

前述の不安もあって、ハードウェアをカスタマイズする際には、半ば賭けのような気分でした。私が行なったカスタマイズは次のとおりです。

  • CPUを選択できる中で最も高性能な Intel® Core™ i7-3612QM CPU @ 2.10GHz ハイパースレッド 8 コアに変更
  • メモリを選択できる中で最大の 8 GB に変更
  • キーボードを日本語配列から英語配列に変更し、バックライトも追加で装備
  • 内蔵ディスクは標準の SSD 64 GB × 2基、ハードウェア RAID 0 構成のまま。ストレージはNASに8TBほど持っているので、端末側にはそんなに必要ありませんでした。
  • ディスプレイを 1920×1080 フル HD に変更。
  • WiFiデバイスをMIMO3x3に変更、無線WANはなし。
  • Bluetooth搭載、指紋認証はなし、Power Dockや拡張バッテリーはなし。

 

OpenCASCADE でソリッドのブーリアン演算(とボリューム計算)を行う

OpenCASCADE でソリッドモデル同士のブーリアン演算とボリューム計算を行うサンプルコードを書いてみました。
コードは次のとおり。

#include <stdio.h>

#include <TopoDS.hxx>
#include <gp_Pnt.hxx>
#include <TopoDS_Solid.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepGProp.hxx>
#include <GProp_GProps.hxx>

int main(int c, char** v)
{
    // s1 というソリッドを作成
    gp_Pnt opos1(0.0, 0.0, 0.0);
    BRepPrimAPI_MakeBox box1(opos1, 10.0, 10.0, 10.0);
    TopoDS_Solid s1 = box1.Solid();

    // s2 というソリッドを作成
    gp_Pnt opos2(2.0, 3.0, 5.0);
    BRepPrimAPI_MakeBox box2(opos2, 10.0, 10.0, 10.0);
    TopoDS_Solid s2 = box2.Solid();

    // ブーリアン演算オブジェクトを s1 と s2 で作成
    BRepAlgoAPI_Common bo(s1, s2);

    // ブーリアン演算オブジェクトのオペレーションを指定
    // BOP_COMMON:  s1 と s2 の共通部分を得る
    // BOP_FUSE:    s1 と s2 を結合したものを得る
    // BOP_CUT:     s1 を s2 でカットしたものを得る
    // BOP_CUT21:   s1 で s1 をカットしたものを得る
    // BOP_SECTION: s1 と s2 のセクションを得る
    bo.SetOperation(BOP_CUT);

    // 計算の実行
    bo.Build();

    // エラーが発生していなかったら
    if (!bo.ErrorStatus()) {
        TopoDS_Shape S = bo.Shape();

        // GPropsオブジェクト(ボリュームや重心位置を得るツール)を作成
        GProp_GProps gprops;
        BRepGProp::VolumeProperties(S, gprops);
        Standard_Real vol = gprops.Mass(); // ボリュームを得る

        printf("volome: %f\n", vol);
    }

    return bo.ErrorStatus();
}

BRepAlgoAPI_Common クラスは、名前に Common の文字がありますが、上のとおり setOperation() で指定すれば、コモン演算(共通部分を得る)だけではなく、カットしたり結合したりすることもできます。

ビルド方法は次のとおり。

g++ -g -lTKernel -lTKMath -lTKBO -lTKTopAlgo -lTKPrim -L/usr/lib/opencas -I/usr/include/opencascade -o solid solid.cxx

ロブ・パイクの正規表現マッチャを拡張する

ブライアン・カーニハンとロブ・パイクによる The practice of programming (邦題:プログラミング作法) に掲載されている正規表現マッチャを拡張したいと思います。

※ O’Reilly から出版された Beautiful Code: Leading Programmers Explain How They Think (邦題: ビューティフル コード)にも掲載されており、該当部分の全文は公式サイトで読むことができます。

前提

元の正規表現マッチャは、以下の表現をサポートしています。

  • 任意文字指定子: .
  • くりかえし指定子: *
  • 位置指定子: ^$

まず、正規表現マッチャを評価する為に main() 関数を追加しました。

#include <stdio.h>

/* match: テキスト中の任意位置にある正規表現を探索 */
int match(char *regexp, char *text)
{
  if (regexp[0] == '^')
    return matchhere(regexp+1, text);
  do { /* 文字列が空の場合でも調べる必要あり */
    if (matchhere(regexp, text))
      return 1;
  } while (text++ != '\0');
  return 0;
}
/* matchhere: テキストの先頭位置にある正規表現のマッチ */
int matchhere(char *regexp, char *text)
{
  if (regexp[0] == '\0')
    return 1;
  if (regexp[1] == '')
    return matchstar(regexp[0], regexp+2, text);
  if (regexp[0] == '$' && regexp[1] == '\0')
    return text == '\0';
  if (text!='\0' && (regexp[0]=='.' || regexp[0]==text))
    return matchhere(regexp+1, text+1);
  return 0;
}
/* matchstar: 「c」型の正規表現をテキストの先頭位置からマッチ */
int matchstar(int c, char *regexp, char *text)
{
  do { /* 「」は「0回以上の繰り返し」であることに注意 */
    if (matchhere(regexp, text))
      return 1;
  } while (text != '\0' && (*text++ == c || c == '.'));
  return 0;
}

int main(int argc, char** argv)
{
  int r;
  if ( argc != 3 ) {
    printf("Too few or many arguments for regex.\n");
    return 2;
  }
  printf("%s\n", (r = match(argv[1], argv[2])) ? "Match" : "No match" );
  return !r;
}

これで、

gcc -o myregex myregex.c

すると、実行バイナリが作成され

./myregex regex-pattern source-string

とタイプする事により、挙動を確認することができます。引数は、シェルによる展開を避ける為、クォートで囲むことをオススメします。

拡張1: 「c+(直前のパターンが1回以上くりかえす)」表現を追加する。

上の37行目にあるコメント『「*」は「0回以上の繰り返し」であることに注意』が最大のヒントになります。「+」は1回以上のくりかえしですので、0回でもマッチする dowhile 文による検索ではなく、while 文で実現すれば良いはずです。

ということで、次の関数を追加しました。

/* matchcorss: 「c+」型の正規表現をテキストの先頭位置からマッチ */
int matchcross(int c, char *regexp, char *text)
{
  while (text != '\0' && (*text++ == c || c == '.')) {
    if (matchhere(regexp, text))
      return 1;
  }
  return 0;
}

1回もマッチすることがなければ、return 0をして検索を終了します。また、呼び出し元となる matchhere() 関数を次のように変更します。

/* matchhere: テキストの先頭位置にある正規表現のマッチ */
int matchhere(char *regexp, char *text)
{
  if (regexp[0] == '\0')
    return 1;
  if (regexp[1] == '')
    return matchstar(regexp[0], regexp+2, text);
  /* ここから */
  if (regexp[1] == '+')
    return matchcross(regexp[0], regexp+2, text);
  /* ここまで追加 */
  /* … */

matchstar() を呼び出しているところと同じように、regexp[1]+ だった場合、matchcross() を呼び出しています。

これを実行してみると

$ ./myregex '^co+l' 'cool'
Match

$ ./myregex '^c+l' 'cool'
No Match

ちゃんと動いているようです。

「c?(直前のパターンが0回もしくは1回)」表現を追加する。

直前のパターンが0回、もしくは1回マッチすればいいので、次のような関数を追加しました。

int matchquestion(int c, char regexp, char *text)
{
  if (text == c)
    text++;
  return matchhere(regexp, text, 0);
}

仮引数は、matchstar()matchcross() と同じです。ポインタ text が指している場所にある文字が c と同じなら、text をインクリメントします。 同じでない場合は、「0回マッチした」ということですので、ポインタ text はそのままで検索をすすめていきます。

呼び出し元となる matchhere() はこんな感じになりました。

/* matchhere: テキストの先頭位置にある正規表現のマッチ */
int matchhere(char *regexp, char *text)
{
  if (regexp[0] == '\0')
    return 1;
  if (regexp[1] == '')
    return matchstar(regexp[0], regexp+2, text);
  if (regexp[1] == '+')
    return matchcross(regexp[0], regexp+2, text);
  /* ここから */
  if (regexp[1] == '?')
    return matchquestion(regexp[0], regexp+2, text);
  /* ここまで追加 */
  /* … */

くりかえし指定子ですので、おんなじですね。

$ ./myregex 'colour' 'color'
No Match

$ ./myregex 'colou?r' 'color'
Match

$ ./myregex 'colou?r' 'colour'
Match

ちゃんと動いてます。

指定子のエスケープに対応する

正規表現では、文字 *. そのものを指定したい場合、それぞれ *. といったバックスラッシュを用いたエスケープを行います。

それに対応します。

考え方としては、エスケープ文字が指定子の前にあれば、その指定子を単なる文字として解釈するわけですが、まずは matchhere() を修正してみましょう。

int matchhere(char *regexp, char *text)
{
  if (regexp[0] == '\0')
    return 1;

  /* 直前がバックスラッシュじゃない場合のみ、指定子として処理する */
  if (regexp[0] != '\\') {
    if (regexp[1] == '*')
      return matchstar(regexp[0], regexp+2, text);
    if (regexp[1] == '+')
      return matchcross(regexp[0], regexp+2, text);
    if (regexp[1] == '?')
      return matchquestion(regexp[0], regexp+2, text);
  }
  else {
    /* バックスラッシュだった場合、regexp をインクリメントして検索を続ける。 */
    return matchhere(regexp+1, text);
  }
  /* ... */

これで、くりかえし指定子のエスケープができるようになりました。さて、問題は次の位置指定子と任意文字のマッチングの部分です。

if (regexp[0] == '$' && regexp[1] == '\0')
  return *text == '\0';

if (text!='\0' && (regexp[0]=='.' || regexp[0]==text))
  return matchhere(regexp+1, text+1);

くりかえし指定子の処理突入部の場合、「regexp[0]が直前のパターン、regexp[1]が指定子」というペアでしたが、これらの文では、regexp[0]が指定子であり、直前のパターンを判断する手立てがありません。

直前のパターンがエスケープ文字かどうか、つまり、matchehere() が呼び出された時点で、エスケープされているのかどうかを知ることができればいいわけです。

matchhere() を次のように変更しました。

/* int matchhere(char *regexp, char *text) */
int matchhere(char *regexp, char *text, int esc)
{
/* … */

第3引数の int esc は、直前でエスケープして呼び出された場合には 0 以外が入るようにします。既存の match()matchstar()matchcross()matchquestion() 内で呼んでいる matchhere() は、全て第3引数を 0 にして呼び出すように修正をします。

さらに、matchhere()

int matchhere(char *regexp, char *text, int esc)
{
  if (regexp[0] == '\0')
    return 1;

  if (regexp[0] != '\\') {
    if (regexp[1] == '*')
      return matchstar(regexp[0], regexp+2, text);
    if (regexp[1] == '+')
      return matchcross(regexp[0], regexp+2, text);
    if (regexp[1] == '?')
      return matchquestion(regexp[0], regexp+2, text);
  }
  else {
    return matchhere(regexp+1, text + esc, 1); /* エスケープされている事を教える */
  }

  if (!esc && regexp[0] == '$' && regexp[1] == '\n')
    return *text == '\n';

  if (*text!='\n' && ((!esc && regexp[0]=='.') || regexp[0]==*text))
    return matchhere(regexp+1, text+1, 0);

  return 0;
}

のように変更します。条件式のところどころに、esc を差し込んでいます。順に追っていってみます。

15行目の

return matchhere(regexp+1, text + esc, 1);

は、regexp[0]がエスケープ文字だった場合に、第3引数を 1 に設定して matchhere() を呼び出します。また、第2引数の text + esc は、「エスケープされていたら1文字進め、そうでなければ現在のポインタの指す位置から」という意味になります。エスケープされており、かつこの return 文のスコープに入ってきたという事は、パターン「\」が指定されたことになります。エスケープされておらず、この return 文が実行される場合は、「\」以外のパターン「」や「\w」などの場合であり、それぞれ、文字「」「w」として解釈されるようになります。

次に、18行目の

if (!esc && regexp[0] == '$' && regexp[1] == '\0')

ですが、ここは「エスケープされていない$があり、かつパターンの文末」を意味しています。エスケープされていたら、マッチしません。

最後の、21行目

if (text!='\0' && ((!esc && regexp[0]=='.') || regexp[0]==text))

では、パターン「.」を弾いています。

これを試してみると

$ ./myregex '.++.+=.+' '12+34=46′
Match

$ ./myregex '^\abc' '\abc'
No Match

$ ./myregex '^\abc' '\abc'
Match

しっかり動いているようです。

ここまでの機能を盛り込んだソースコード

2つのくりかえし指定子とエスケープを実装した後のコードです。

#include <stdio.h>

/* match: テキスト中の任意位置にある正規表現を探索 */
int match(char *regexp, char *text)
{
  if (regexp[0] == '^')
    return matchhere(regexp+1, text, 0);
  do { / 文字列が空の場合でも調べる必要あり /
    if (matchhere(regexp, text, 0))
      return 1;
  } while (text++ != '\0');
  return 0;
}
/* matchhere: テキストの先頭位置にある正規表現のマッチ */
int matchhere(char *regexp, char *text, int esc)
{
  if (regexp[0] == '\0')
    return 1;

  /* くりかえし */
  if (regexp[0] != '\\') {
    if (regexp[1] == '*')
      return matchstar(regexp[0], regexp+2, text);
    if (regexp[1] == '+')
      return matchcross(regexp[0], regexp+2, text);
    if (regexp[1] == '?')
      return matchquestion(regexp[0], regexp+2, text);
  }
  else {
    return matchhere(regexp+1, text + esc, 1);
  }

  /* 文末 */
  if (!esc && regexp[0] == '$' && regexp[1] == '\n')
    return *text == '\n';

  /* その他、任意文字 */
  if (*text!='\n' && ((!esc && regexp[0]=='.') || regexp[0]==*text))
    return matchhere(regexp+1, text+1, 0);

  return 0;

}
/* matchstar: 「c」型の正規表現をテキストの先頭位置からマッチ */
int matchstar(int c, char *regexp, char *text)
{
  do { /* 「」は「0回以上の繰り返し」であることに注意 */
    if (matchhere(regexp, text, 0))
      return 1;
  } while (text != '\0' && (text++ == c || c == '.'));
  return 0;
}
/* matchcorss: 「c+」型の正規表現をテキストの先頭位置からマッチ */
int matchcross(int c, char *regexp, char *text)
{
  while (text != '\0' && (text++ == c || c == '.')) {
    if (matchhere(regexp, text, 0))
      return 1;
  }
  return 0;
}
/* matchquestion: c? */
int matchquestion(int c, char *regexp, char *text)
{
  if (text == c)
    text++;
  return matchhere(regexp, text, 0);
}

int main(int argc, char** argv)
{
  int r;
  if ( argc != 3 ){
    printf("Too few or many arguments for regex.\n");
    return 2;
  }
  printf("%s\n", (r = match(argv[1], argv[2])) ? "Match" : "No match" );
  return !r;
}

次回は、範囲パターン([a-z])の実装と、パターンのコンパイルフェイズ・チェックフェイズの分離をやってみたいと思います。

PDFを連番画像ファイルを含むZIP圧縮ファイルに変換

安価な Android 端末が出回っています。
Amazon キンドルが発売されて、投げ売り状態だった楽天の Kobo が、新品でも3000円程度で流通するようになりました。
ようやく、電子書籍を読むというライフスタイルが日本にも定着するんでしょうか。今後が楽しみです。

さて、私が持っている Huawei の IDEOS のような低スペックな端末では、PDF の文書を読むのにもひと苦労です。
低スペックな環境では、ファイルサイズが小さく高圧縮&レンダリングにマシンパワーが必要なものより、ファイルサイズが大きく冗長でも負荷をかけずに表示できるものの方が重宝される場合があります。

特にほとんどが文字の場合、ページをめくるたびに何秒も待たされるより、少々画質が荒くても(もちろん、文字が読める程度に、です)サクサクとページをめくれる方が良い事もあります。

という事で、既存の PDF ファイルの各ページを連番の JPEG ファイルにして、ZIP 形式で圧縮するスクリプトを書いてみましょう。

1. まず基本的な事

ImageMagick の convert コマンドで、次のようにタイプすると PDF のページを JPEG ファイルとして保存することができます。

[bash]
convert src.pdf[1] dst1.jpg
[/bash]

鉤括弧の中は、ページ番号を指定します。0 ページ目から開始します。存在しないページを指定すると

[bash]
convert src.pdf[999] dst999.jpg

Requested FirstPage is greater than the number of pages in the file: 645
No pages will be processed (FirstPage > LastPage).
[/bash]

とエラーになります。

2. シェルのブラケット展開で繰りかえす

エラーが出るまで繰替えせばいいので、

[bash]
for n in {0..999}; do convert src.pdf[$n] dst$n.jpg || break; done
[/bash]

と書く事ができます。0から999ページまで指定して変換を試みて、失敗したら break で抜ける、という動作をします。

3. 圧縮ファイルにつっこむ

変換し終わったファイルから ZIP ファイルに追加していくには

[bash]
for n in {0..999}; do convert src.pdf[$n] dst$n.jpg || break; zip target.zip dst$n.jpg; done
[/bash]

という風に、そのまま zip コマンドに投げてやれば良いです。

[bash]
for n in {0..999}; do convert src.pdf[$n] dst$n.jpg || break; zip target.zip dst$n.jpg && rm dst$n.jpg; done
[/bash]

ZIP ファイルへの追加に成功した後、JPEG ファイルは不要ですので、削除しておきましょう。

4. 変換オプションを指定する

必須ではありませんが、convert のオプションを利用する事によって、画像サイズの指定、グレースケール化、フォントの指定など様々なことができます。詳しくは

[bash]convert –help[/bash]

をご覧ください。

5. スクリプト化

以上の事を踏まえて、スクリプトにしてみます。

[bash]

/!usr/bin/env bash

src=$1
dst=$2

opt=’-density 600 -resize 640 -font kochigothic’

dir=/tmp/$$

(test ! -d $dir && mkdir $dir) || exit 1
test ! -f $src && exit 1

tmp=basename $dst

for n in {0..999}
do
pict=printf '%04d.jpg' $n
convert $opt $src[$n] $dir/$pict 2>/dev/null || break
pushd $dir >/dev/null
zip $tmp $pict && rm $pict
popd >/dev/null
done

mv $dir/$tmp $dst
rmdir $dir
[/bash]

このスクリプトは、/tmp 以下に一時ディレクトリを作成し、そこに画像ファイルとZIPファイルを貯め込むようにしています。
実行は、以下のように使います。

[bash]
pdf2cbz src.pdf dst.zip
[/bash]

dst.zip の名前を自動生成するようにすれば、複数ファイルの一括処理や Nautilus などのファイラから右クリックで変換、など、さらに便利に使えそうですね。

OpenCASCADE で IGES ファイルを読み込む

OpenCASCADE で IGES ファイルを読み込むには、IGESControl_Reader クラスを用います。

[cpp]

include <stdio.h>

include <Standard_TypeDef.hxx> // Standard_Boolean

include <Standard_Macro.hxx> // Handle()

include <TopAbs_ShapeEnum.hxx> // ShapeType

include <TopoDS.hxx> // TopoDS::

include <TopoDS_Shape.hxx> // TopoDS_Shape

include <TopoDS_Face.hxx> // TopoDS_Face

include <IGESControl_Reader.hxx> // IGES Reader

int main(int c, char** v)
{
Standard_CString path = "sample28.igs";

// IGES制御リーダで読み込む
IGESControl_Reader reader;
if (reader.ReadFile(path) != IFSelect_RetDone) {
    // エラーが起きた
    reader.PrintCheckLoad(Standard_True, IFSelect_ItemsByEntity);
    return 1;
}

// 変換対象のルート数
printf(&quot;number of roots = %d\n&quot;, reader.NbRootsForTransfer());

// IGES内のルートを OCC で扱えるように変換
reader.TransferRoots();

// 得られたシェイプ数
printf(&quot;number of shapes = %d\n&quot;, reader.NbShapes());

// シェイプの取得
for (int i=1;i&lt;=reader.NbShapes(); i++) {
    TopoDS_Shape s = reader.Shape(i);
    if (s.ShapeType() == TopAbs_FACE) {
        TopoDS_Face f = TopoDS::Face(s);
        // 取得した Face で何かする
        printf(&quot;%04X\n&quot;, f.HashCode(0xFFFF));
    }
}

// 一つのシェイプにして返す場合
// TopoDS_Shape s = reader.OneShape();

return 0;

}
[/cpp]

次のようにビルドします。TKIGES にリンクするのを忘れないように注意してください。

[bash]
$ g++ -g -lTKernel -lTKMath -lTKBinL -lTKIGES -L/usr/lib/opencas -I/usr/include/opencascade -o occtest occtest.cxx
[/bash]

IGES関連のクラスについての解説は、$CASROOT/../doc/iges.pdf にドキュメンテーションが同梱されているので、そちらを参考にしてください。また、IGES ファイル自体の仕様(PDF)は、US Product Data Association で公開されています。

PFU Happy Hacking Keyboard Professional2 墨 英語配列モデル レビュー


PFU の「Happy Hacking Keyboard Professional2 墨 英語配列モデル」(以後、HHKBと記載します)を購入し、何週間か使ってみたのでレビューを書いておきます。

値段

価格.comで調べたところ、Amazon.co.jp が最安値だったため、そこで 18,634円で購入しました。HHKBは、Control キーが A キーの左側にある、いわゆる「UNIX配列」のキーボードです。ジャンパスイッチにより切り替えができるキーボードは他社製でもいくつか出されていますが、デフォルトでこの位置に Control キーが配置されている数少ないキーボードです。
一定の需要があるにも関わらず、市場の絶対数が少ないためか、数ある実用的なキーボードの中でも最高クラスのお値段で流通しています。価格.comの販売価格の推移を見てみると、平均価格は21,000円から24,000円の間で変化していることが分かります。

私は元々、同シリーズの廉価版「Happy Hacking Keyboard Lite2」を使っていたので、HHKB の Professional 版はある種の憧れでもあり、ここ2年ほど Amazon のカートに入れたり出したりしていました。そして先日、ようやく決心が着いて購入したところであります。

HHKB は耐久性がとても良いので、他のユーザの方もおっしゃってるように、10年以上使えるようです。10年で2万円弱…1年で2000円と考え、4,000~5,000円の安いキーボードを10年の間に何度も買い替える事を考慮すると、良い選択じゃないかと考えています。

プログラマーな自分は、1日の業務の大半はキーボードを叩いているわけですし、キーボードを叩くのが仕事と言っても過言ではありません。
車が趣味でホイールやシートに10万も20万もかけるという話でもないので、購入の決心が着きました。(車が趣味の方、ごめんなさい)

結論を言うと、キーボードとしては確かに割高ですが、得られる効果は大きなものだと感じています。

UNIX配列

thumb_pdkb400b_l

 

HHKB の特徴をまず挙げるとすると、やはりUNIX配列だと思います。IBM/PC 配列で言う CapsLock の位置に Control があります。しょっちゅう使うキーが左手の小指に最も近い位置に鎮座してくれていると、非常に効率的に作業ができます。UNIX 系のツールでは、特に Control キーを酷使して使いますが、IBM/PCの配列では C-y (Emacsのヤンクコマンド)など、いくら手が大きくても指がつってしまいそうです。あんまり正確な記憶ではありませんが、確か昔の Macintosh のキーボードもこの位置に Control キーがあったと思います。(Mac では Control よりも Command/Apple キーを多用する為、あまり真価を発揮できていないようですが…)

先述のとおり、これまでは HHKB Lite2 を使っていたのであまり違和感なく移行することができました。Lite2 との違いは、Fn キーが標準では右下の一つだけになっているところでしょうか(DIPスイッチで左のAltまたはSuperキーをFnキーに割り当てる事は可能です)。F1~F12 や PrntScr キーなどの特殊キーは Fn キーを使って押す必要があります。

英語配列

これまでずっと日本語配列に馴染んで来たのですが、今回初めて英語配列にしていました。英語配列を利用する利点は、よく言われるように「プログラミングに向いている」点にあるかと思います。ここから少しだけ掘り下げて、英語配列のメリットとデメリットについて触れてみたいと思います。

英語配列のメリット

キーが少なくて済む 60キーしかありません。キーボードがすっきりして、同じ面積でもキーを広く取れるメリットがあります。特にスペースキーの両サイドにある「変換」「無変換」キーがない為、誤タイプの危険性が低下します。

シンボル(記号)キーの位置が自然 ブラケット([]や{})が隣り合っている点やセミコロン&コロン、クオート&ダブルクオートがそれぞれ同一キーになっているなど、シンボルキーの位置がより自然になっています。また、数字キーを Shift キーを押しつつ入力するシンボルキーも日本語配列のように ASCII コード順にただ並べただけではありません。日本語配列では、数字の 0 キーには Shift キーを押した場合の記号は割り合てられていませんが、英語配列ではそのような無駄もなく、全てのキーに Shift キーを押した場合にシンボル(または大文字)が入力できるよう割り当てられています。さらに、日本語配列のキーボードには「¥」と「\」キーが独立して準備されており、どちらを押下しても結局はバックスラッシュ入力ですが、英語配列では「\」キーのみで、無駄がありません。また、日本語配列では Shift キーを押さなければタイプできない「」も Shift キーを押す事なく入力できます。代わりに「@」を入力する場合に Shift キーが必要になります。もし、シェルスクリプトや Perl を日々使う人であれば @ キーより キーの方が多用しているはずですので、Shift キーをタイプする手間が省けるのかもしれません。

エスケープキーの位置 「半角/全角」キーがないので、エスケープキーが Tab の直上、数字の 1 キーの左側に収まっています。私は Windows でも SKK(SKKFEP) を利用しているので、「半角/全角」キーは不要です。不要なキーがなくなった代わりに、エスケープキーがホームポジションに近づいてくれるおかげで、あまり手を動かす事なく、使用することができるようになります。(そもそも半角/全角キーって、「互換性を無視してまで、必要とされる機能毎にハードウェアを追加する」みたいで、あまりクールな方法じゃないですよね)

ちなみに、同じ東アジア圏でも、中国や韓国のキーボードは英語配列と同じシンボルキーの配置のようです。日本語配列は、コンピュータの創世記、マルチバイト言語圏の雄として発展を遂げた日本独自の進化の結果と言ってもいいでしょうね。

UNIXツールを利用するのに最適 vi であっても nethack であっても、英語配列のキーボードで操作する事をベースとしてキーバインドが設定されています。日本語配列でも慣れてしまえば、あまり不自由がないような気もしますが、UNIX ツールを利用するには、やっぱり英語配列の方がしっくりくるでしょう。

英語配列のデメリット

配列自体にはデメリットはあまりないのですが、いざ利用するとなったら、「それ以外の点」でデメリットが生じます。

世知辛い ラップトップPCを購入しようとしても、おのずと購入できるものが限られてきます。海外製の安いふにゃふにゃキーボードなどは、むしろ英語配列の場合が多いんですが、日本メーカーのちゃんとしたラップトップPCを買おうとしても英語配列を選択できるものが少ないのも現状です。また、英語配列を選択できたとしても、日本語配列モデルより割高になってしまったり、と、ある程度の覚悟が必要だと思います。

これらは HHKB 以外の全ての英語配列キーボードに言えることです。HHKB のような「一度使ったら(色んな意味で)手放せない!」マニアックなキーボードを購入検討されている方であれば、いっその事、英語配列にしてしまうのもいいかと思います。事実、私も日々幸せになれました。

カーソルキーがない

カーソルキーがありません。ないので入力ができないかと言うとそうではなく、Fn キーと組み合わせることによって入力することができます。私は Vim では hjkl 移動、bash や emacs では C-p C-n C-f C-b 移動を普段から利用しているので、これらのツールでは特に困りませんが、多くの Windows アプリケーションではカーソルキー操作を前提としています。また、Windows でなくても GTK や Qt ベースのアプリケーションでも、最近では大抵がカーソルキーを装備した環境を対象にしています。カーソルキーを多用するアプリケーションでは間違いなく効率が低下するので、利用目的をしっかりと考えた上で購入する必要があります。
逆説的な話ですが、これまで使用していた Lite2 にはカーソルキーがあった為、Vim 上でもついついカーソルキーを使ってしまって、hjkl 移動はあまりやっていませんでした。カーソルキーが撤去される事によって、今ではホームポジションから手を動かす事なく自由に移動する事ができます。たった1週間、不便な思いをして慣らしていくと、その後は永遠に良い使い勝手を得られる、と前向きな解釈をしています。
また、タイプするキーが増えるものの、カーソルキーが独立していないことによって、ホームポジションから右手を数センチだけ右にシフトしてあげるだけで、入力ができるというメリットもあったりします。

タイプ感

他のユーザの方もおっしゃっているように、打鍵感はちょっと高めの音で「コトコト」と言った感じです。
Lite2 では、プラスチック音がカチャカチャと非常にうるさかったので、それに比べたら落ちついた印象になっていると思います。PFU の直販でしか購入ができない Type-S や、東プレの REALFORCE といった静音を売りにしたキーボードに比べれば、打鍵音は大きいと思いますが、タイプしていて心地いい感じがします。
キーの押し込み具合については、Lite2 がしっかり押し込まなければならないのに対し、Professional では、ちょっと押しただけでも入力が入ります。Lite2 のつもりで、考えごとをしてキーボードに手を乗せているだけでもどうかすると入力してしまいますが、通常の手の重さのみがかかった場合は、入力されることはありません。

メンテナンス性

公式サイトで専用ケースや、交換用のキートップが販売されているので、長く使用することができると思います。構造的にキーボードの内部にほこりが入り込むのですが、キーも簡単に取り外せます。(専用のキー外しもあります)

その他

Lite2 では USB ケーブルと本体を切り離すことができず、収納や持ち運び時にケーブルが邪魔になっていましたが、Professional2 ではケーブルを完全に取り外すことができます。端子形状も mini USB ですので、純正以外の USB ケーブルを代用することができ、自宅と職場のデスクに USB ケーブルをそれぞれ準備しておくと、本体だけを持ち運んで使うことができます。これは地味に嬉しい点です。

Delete キーと BackSpace キーは、背面にある DIP スイッチで切り替えることができます。標準だと「Delete キーと Fn キーを押しながらの BackSpace キー」という構成ですが、ここだけは抵抗があったので「BackSpace キーと Fn キーを押しながらの Delete キー」に変更しました。

また、買い替えなどで手放す場合、オークションサイトに出すと中古でもそこそこなお値段で流通しているようです。コンピュータ機器であるのに、価値が下がりにくいのは良い傾向ですよね。

総評

良いです。どうせ長く使うものですので、もう少し早く決心して購入しておけば良かったと思っています。
ちょうど冬のボーナスもあるし、自宅用に 2 台目買おうか検討しています。

Debian GNU/Linux Squeeze の MOTD 周りのメモ

12月になったし、MOTD(5) をクリスマス仕様にでもしようかなと /etc/motd を修正しても上手くいかなかったのでメモしておきます。

Debian GNU/Linux の Squeeze サーバに対して SSH 接続すると、デフォルトの状態で「ターミナルログインとしての MOTD」と「PAM の MOTD」が二重に出力されてウザかったので、後者を無効にしていたら、いつの間にか MOTD が全く表示されなくなっていました。デフォルト挙動が変更されたのかな。

まずおさらい

Debian GNU/Linux Squeeze の MOTD は次のようになっています。

/etc/motd -> /var/run/motd
/etc/motd.tail

で、起動時に実行される /etc/init.d/bootmisc.sh にて

[bash]
uname -snrvm > /var/run/motd
[ -f /etc/motd.tail ] && cat /etc/motd.tail >> /var/run/motd
[/bash]

と毎度毎度、motd の中身を書き換えやがります。(/etc/motd または /var/run/motd に記載していた内容は、起動時毎にぶち消されてしまいます。)

永続的なメッセージは /etc/motd.tail に記載せよ、という意図でしょうね。それに従って、表示させたい motd テキストを /etc/motd.tail に記載しておきます。

PAM の設定

本来の motd 表示がどのプロセスで表示されているのかは知りませんが、少なくとも PAM による認証時、pam_motd.so を有効にしておくと motd を吐いてくれます。ちなみに PAM とは、Pluggable authentication module の略で、UNIX 系 OS の共通認証システムの API です。アプリケーションの機能が、一部、特権を必要とする処理を行いたい場合、PAM の API を叩くだけで認証周りの面倒を見てくれる便利な API ですね。SSHD の認証システムにもこの PAM が利用されています。

/etc/pam.d/sshd の次の行のコメントアウトを外しました。

[bash]

Print the message of the day upon successful login.

session    optional     pam_motd.so # [1]
[/bash]

また

[bash]

Print the status of the user’s mailbox upon successful login.

session    optional     pam_mail.so standard noenv # [1]
[/bash]

の部分は、コメントが書いてあるとおり、ログイン成功時にメールボックスのメール件数を表示するモジュールです。

2013-07-23 追記

Wheezy にアップグレードした時からか、いつの間に MOTD が二重表示されるようになりました。
今回は、/etc/ssh/sshd_config の次の部分を変更し、sshd をリスタートすれば二重表示されなくなりました。

[bash]
– PrintMotd yes
+ PrintMotd no
[/bash]

Wheezy では、確認してみたところ bootmisc.sh の起動時毎に上書きする処理や motd.tail がなくなっていますね。ほとんど意味がない上に、ユーザを混乱させるだけだったから廃止されたかな。

軽量・高速な C コンパイラ「Tiny C Compiler」を使う

Tiny C Compiler (tcc)は軽量かつ高速なコンパイル、リンクが可能な C コンパイラです。コンパイルが非常に高速なので、抜き出した小さなコードでのテストや、あるプロジェクトのモジュール単位でのトライアンドエラー開発に向いていると言われています。

また、クイックリーなトライアンドエラーでのコーディングだけが目的であれば C インタプリタでも良いのですが、tcc はちゃんと機械語コードを吐くコンパイラですので、生成したバイナリはもちろんネイティブで実行可能です。tcc が生成したデバッグシンボルを gdb で利用することも可能です。

Debian GNU/Linux で利用する

Squeeze では標準リポジトリに入っていました。下記のコマンドをタイプするだけでインストールが完了します。

$ sudo aptitude install tcc

サンプルとして次のソースをビルドしてみたいと思います。

#include <stdio.h>
#include <ncurses.h>
int main(int c, char** a)
{
    initscr();
    printf("tcc is running!\n");
    endwin();
    return 0;
}

ちゃんとビルドできるかどうかの確認のため、ncurses ライブラリを使ってみています。

$ time tcc -lncurses -g -Wall -O3 -o tcctest test.c
real 0m0.018s
user 0m0.020s
sys 0m0.000s

ビルドすると一瞬でプロンプトに戻りました。ソースが短いとは言え早い。

$ ./tcctest

実行すると、もちろん正常に動作します。※ initscr() と endwin() の間に printf() が書かれているので、実行時には何も印字されないのが正しい動作です。

同じオプションで gcc を実行してみます。

$ time gcc -lncurses -g -Wall -O3 -o gcctest test.c
real 0m0.161s
user 0m0.128s
sys 0m0.040s

対象ソースが短いのであまり精度は良くありませんが、それでも10倍近くの時間がかかりました。 ldd で見てみたリンク状態は同じ、

$ diff -u <(nm gcctest) <(nm tcctest)

も同じです。アセンブリを見たくて tcc -S test.c してみると -S オプションはサポートしていないと怒られました。tcc 自体にもアセンブラは搭載されていますが、objdump などで見た方がいいみたいですね。 また、-g する事により、gdb に食わせることもできました。

$ echo 'int main(){ printf("hello\n"); }' | tcc -run -

で、動的にコードを入力、コンパイル、リンク、実行の一連の流れを実行します。

Windows で利用する

Windows にはパッケージ管理システムが機能していないので、自分で公式ページよりダウンロードし、解凍する必要があります。 コンパイラ・リンカ本体 tcc.exe、モジュール定義ファイル(DEFファイル)生成コマンド tiny_impdef.exe、「ar」コマンドに置き換わるライブラリ作成コマンド tiny_libmaker.exe が同梱されています。また、基本的なヘッダーやライブラリ、Win32 上でのサンプルコードまで同梱されています。 使い方は Debian 上での操作とほとんど変わりませんが、Windows 独自のコーディング方法がありますので、それについては example/ のソースコードと doc/tcc-win32.txt を参考すればいいと思います。