« オブLOVE夜会第2弾「イマドキのJavaScript」に飛び込み参加 | メイン | 未踏な仲間たち新年会 »

#!/usr/bin/C でワンライナーを書く

Kazuho@Cybozu Labs: C - a pseudo-interpreter of the C programming language

 Perl や Ruby では、ワンライナーで処理が書けて便利です。でも、なぜか C では書くことができません。仕事上の都合で、小さな処理を C 言語で書く必要があったので、ワンライナーも書くことのできる C 言語のインタプリタ(?)を作ってみました。

面白そうなので、早速RPMパッケージを作ってみました。

Red Hat系Linuxならインストールは以下のコマンド一発です。

# rpm -ivh http://takesako.31tools.com/redhat/RPMS/noarch/C-0.01-0.noarch.rpm

こんなことができるようになります。

[1] C言語でワンライナー:

C -e 'printf("hello world\n");'

[2] shebangでスクリプト実行:

#!/usr/bin/C
printf("hello world\n");

処理させる内容によってはPerlの約100倍早く動作することもあるようです。

半分ジョークプログラムみたいですが、いや、これ、実は結構便利かも。。。

まだ tarball が揃っていないソフトウェアなので、specファイルはこんな感じになりました。

Summary: C - a peudo-interpreter of the C programming language
Name: C
Version: 0.01
Release: 0
License: GPL
Group: Development/Languages
Source0: http://labs.cybozu.co.jp/blog/kazuho/archives/C
Source1: C.pod
URL: http://labs.cybozu.co.jp/blog/kazuhoatwork/
Vendor: Cybozu Labs, Inc.
Requires: perl, gcc
AutoReqProv: no
BuildArch: noarch
BuildRoot: %{_tmppath}/%{name}-%{version}-root
%description
C (pronounced large-C) is a pseudo-interpreter of 
the C programming language. 
Without the need of compilation, developers can rapidly 
create scripts or write one-liners using the C programming 
language that runs at the native-code speed.
%prep
%build
%install
%{__rm} -rf $RPM_BUILD_ROOT
%{__mkdir} -p -m 0755 $RPM_BUILD_ROOT%{_bindir}
%{__install} -m 0755 %{SOURCE0} $RPM_BUILD_ROOT%{_bindir}/C
%{__mkdir} -p -m 0755 $RPM_BUILD_ROOT%{_mandir}/man1
pod2man -s 1 -c "%{vendor}" -r "%{name}-%{version}" %{SOURCE1} \
 > $RPM_BUILD_ROOT%{_mandir}/man1/%{name}.1 
%{__chmod} 0644 $RPM_BUILD_ROOT%{_mandir}/man?/*
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
%{_bindir}/C
%{_mandir}/man1/*
%changelog
* Fri Jan  6 2006 Cybozu Labs  0.01-0
- Initial package.

PODファイルから pod2man で man1 のファイルを生成しているのがミソです。

そういえば、たまにspecファイルの書き方/勉強の仕方を聞かれることがあるのですが、紹介しやすい参考文献ってあまりないですよね。他人の(ディストリビュータが)書いたspecファイルを参考にするのが早いけど、変な癖がつくし。お勧めできるものとしては、ちょっと古いですが、Maximum RPM13. Inside the Spec Fileあたりでしょうか。

■追記(2006-01-07)

Plamo Linux/TOWNS の nmaruichi さんから動作報告をいただきました。ありがとうございます。



RPMバイナリを Plamo Linux の rpm2tgz で変換することでインストールできました。

以下の100万回の単純なループで bash と比較すると 100 倍位になりました。
この /usr/bin/C って面白いですね。(変な比較をしてすいません)

bash-3.00# time C -e 'int counter;for(counter=0;counter<1000000;++counter) printf("%d\n", counter);' >/dev/null

real 0m0.614s
user 0m0.610s
sys 0m0.010s
bash-3.00# time for((counter=0;counter<1000000;++counter)) do printf "%d\n", $counter ; done >/dev/null

real 0m59.351s
user 0m57.790s
sys 0m0.770s

ちなにみ先にダウンロードしたのは、ソースだったんですが『中身を見て「これを元にビルドスクリプト」を書くのは、さらに意味が無いな…』って思ったので rpm2tgz にしたと言うのが、実際です。
まぁ、RPMバイナリが noarch だったんでなんとなくよめてました。


■追記(2006-01-09) /usr/bin/C をデバッガ(gdb)経由での使用

nmaruichiさんがブログのコメントにうまく書き込めなかったみたいなので原文を追記。

/usr/bin/C に実行ファイルを呼び出す段階で gdb 経由で呼び出すように修正し
たコマンド "C.gdb" をつくって試してみました。
# なお、コマンド内で indent を使用しているのは、コンパイル前に成形してお
いた方が分かりやすいかと思ったためです。
----
$ C.gdb -c'g' -e "`echo 'int
counter;for(counter=0;counter<10;++counter)printf("%d\n", counter)' |
indent -kr`"
GNU gdb 6.1.1
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i586-pc-linux"...Using host libthread_db
library "/lib/libthread_db.so.1".
(gdb) list
1 #include 
2 #include 
3
4
5 int main(int argc, char** argv)
6 {
7 #line 1
8 int counter;
9 for (counter = 0; counter < 10; ++counter)
10 printf("%d\n", counter);
(gdb) list
11 return 0;
12 }
(gdb) break 8
No line 8 in file "/tmp/filedCHG4R.c".
(gdb) run
Starting program: /tmp/filedCHG4R
0
1
2
3
4
5
6
7
8
9
Program exited normally.
----
上記のテスト結果では、ブレークポイントを設定しているにも
係わらず。機能していないようです。
それで、list 表示の中にある #line のプリプロセッサが良く
分からないので、省くために /usr/bin/C.gdb から取り除いて
みました。
修正済みのもので試すと。
----
$ C.gdb -c'g' -e "`echo 'int
counter;for(counter=0;counter<10;++counter)printf("%d\n", counter)' |
indent -kr`"
GNU gdb 6.1.1
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i586-pc-linux"...Using host libthread_db
library "/lib/libthread_db.so.1".
(gdb) list
1 #include 
2 #include 
3
4
5 int main(int argc, char** argv)
6 {
7 int counter;
8 for (counter = 0; counter < 10; ++counter)
9 printf("%d\n", counter);
10 return 0;
(gdb) list
11 }
(gdb) break 7
Breakpoint 1 at 0x8048338: file /tmp/file9CUgWl.c, line 7.
(gdb) run
Starting program: /tmp/file9CUgWl
Breakpoint 1, main (argc=1, argv=0xbffff834) at /tmp/file9CUgWl.c:8
8 for (counter = 0; counter < 10; ++counter)
(gdb) step
9 printf("%d\n", counter);
(gdb) step
0
8 for (counter = 0; counter < 10; ++counter)
----
この場合は、上記のようにブレークポイントも正常に動きました。
この「#line 1」の行は必要なのでしょうか?
この行があると無いとでは、gcc -E でプリプロセッサの処理を通したソース
コードにも違いが見られます。
なお、「#line 1」を省いた C.gdb にするための差分を以下のよう
にして
$ diff -u C{,.gdb} | gzip -c | uuencode -m C.gdb.diff.gz.uuencode
固めたものを載せておきます。
begin-base64 644 C.gdb.diff.gz.uuencode
H4sIAKEDwEMAA32RXW+CMBSGr9df8Qa3BOUjxfmBIozFi93vYldLDJaKTaAS
KItm2X9fQdRsS9YL2tPnHN5z3jqOg/XdmNKZQz2HzjCmS2++fJy69LJg0QWl
xLIsrN0s3d6yfdDxckKXk9mf7DiGMx3bM1j66yOOCQiKE+7rim0UP6rgHB4a
tdmJnCOEKkqZFOYwINagY/zI2YYVqWbXPA1/obqshFQ709Ct4aE27FvyMLhp
9iLGFbrM6LGQLG9SXrd40AdY1SoVB3cfvcsfd7nYdpd9cZwxtjmUqg66kRe+
PYflUaonP8+se0ORCGm2h6TKmA22T6rRqA0+hgSfxBnkQnJ4BO26OnQOK66a
SoJqua9OwvPOGt5Cb60GPwpl3j8hiuAPIXbQ51UYdSUEA7ReNYoTpz7Vihfm
1QEb8fPry1tr+AVdfL0hsJwnsinbd0EqOIxdomtTqMPlz1B7joxLXiVKg62Q
SXXSDvW9hCG84N82vwGKFyuXhQIAAA==
====
上記の部分を「cat > C.gdb.diff.gz.uuencode」などとして(ペーストすること
で)ファイルに保存して以下のコマンドで取り出すことが出来ます。
$ uudecode C.gdb.diff.gz.uuencode
$ gzip -cd C.gdb.diff.gz

トラックバック

この一覧は、次のエントリーを参照しています: #!/usr/bin/C でワンライナーを書く:

» 最狂プログラム from 404 Blog Not Found
最近自分がプログラマーでもあるという事を忘れつつあるので、ここらで一つ披露。 鈴木島男に学ぶ ウィルス撃退法 - にぽたん研究所ここで、私からこんな島男... [詳しくはこちら]

» C-0.02 from Kazuho@Cybozu Labs
 C-0.02 をリリースします。 ダウンロード URL: http://lab... [詳しくはこちら]

コメント

既に若干情報が古くなりつつありますけど

http://www.amazon.co.jp/exec/obidos/ASIN/4797324937

とか悪くないと思いますよ

#自分の書いた書籍は怖いので照会しない(うひひ

/usr/bin/C の起動時に設定ファイルとして ~/.Crc を読むようにするとか言うのはどうでしょうか?
ここに設定を書いておくと反映してくれるのであれば、手間が緩和される場合も出てくるかも知れません。
# 例えば、-L とか -I の長めのオプションを設定したい場合は、便利かも?
但し、それによって起動に時間がかかるようになるといけませんけど。
---
ちなみに私が言っていた「よめていた」は、「予想できた」と言う意味で使ったつもりでした。(適切な言葉ではありませんね > 私)
今回は、実際には rpm を読む必要は無かったので…。

nmaruichi さんへ:

ご利用いただき、ありがとうございます m(__)m

デバッグ機能を本体に追加させていただきました。行数の処理も改善を図ったつもりですが、まだいまいちかもしれません。
設定ファイルを読む機能については、設定ファイルではなく、環境変数で対応しようと思います。
関連して、C-0.02 ではソースコード内に #option -cWall -lm のように書けるようになったので、だいぶ実用的になったと思います。
よろしくお願いします。

まぁ、~/.Crc って言うのは、半分冗談です。

でも私自身は、「もし設定したい内容が肥大化したときに ~/.Crc って言う方法はいいのでは?」とは思っています。

ただ、この機能をつけたために重くなるのでは本末転倒なので、「export CRC=".Crc" みたいに環境変数で設定されているときに設定ファイルを読み込むと言う手法はどうかな?」と思っています。
…とはいえ今は、まだ必要ないと思います。
---
なお、使用するデバッガを環境変数で設定できた方がいいかも知れません。

それから、この「/usr/bin/C」ってコマンドのアイデアは、私個人的にも刺激的なもので、以前に私のプログラムについて takesako さんに提案された「案」を実現してみたくなるような面白いものでした。
この様なプログラムを見せてもらえるなんて、ありがとうございます。