#!/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
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 01 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 08 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

コメントは受け付けていません。