glob
暫く前から通ってる運動教室。同じメニューの繰りかえしだったのだけど、少しバリエーションが増えた。
大きな筋肉を大きく動かしましょう。それが、効率的にスリムになる方法です。脳味噌コンピューターも大飯喰らいなので、同時に働かせるとより効果があります。
スクワットの変形で、力士がやってる股割りスクワットをしましょう。膝を傷めないように、曲げる方向は、足の向きと必ず一致させてください。
腰を落とした時、先生が言う言葉をreveseして、大きな声で答えてください。答えられるまで、ずっとその姿勢を保ってね。
はいそれでは、でんわ。。。。各自考える。わんで と言えたら、姿勢を戻してね。逆語が出てこなかったら、タイムアウトする20秒ぐらいまで、ずっと腰を落としたまま。
3文字ぐらいなら、簡単だけど、はい次は、おにぎり、いろはにほ、とか、段々と字数が増えていく。
(define (rwl l) (if (null? l) '() (append (rwl (cdr l)) (list (car l))))) (define (rw s) (list->string (rwl (string->list s))))
オイラーは、思わず、こんなコードを思い出しちゃったぞ。
gosh> (rw "hello") "olleh"
お前の脳味噌は、再帰に耐えられるのか? stack depth 3ぐらいで、stack溢れが生じるんじゃないか。
脳味噌をフル回転させると、大量の血流が必要。それって、血圧上昇を招くんじゃなかろうか? 脳内でプッツンしないか心配。ボケーと生きてる方が幸せかな?
再帰なんて使わないで、脳内にメモ用紙を用意。そこに言葉を書き出す(イメージ)。後は、それを逆に読み上げるだけ、何も頭を捻る事はないぞ。
pythonのglob
glob.pyを見てとやかく言ってる人をみかけず。世の中、使う人ばかりなのかな?
magic_check = re.compile('[*?[]') def has_magic(s): return magic_check.search(s) is not None def glob1(dirname, pattern): : if pattern[0] != '.': names = filter(lambda x: x[0] != '.', names) return fnmatch.filter(names, pattern)
肝は、このあたりかな? ようわからん!
glob on unix v6
/etc/globにバイナリーがあって、ソースがusr/source/s1/glob.cに置いてあった。 下記はその冒頭部分。DownLoad V6 sources
/* global command -- glob params "*" in params matches r.e ".*" "?" in params matches r.e. "." "[...]" in params matches character class "[...a-z...]" in params matches a through z. perform command with argument list constructed as follows: if param does not contain "*", "[", or "?", use it as is if it does, find all files in current directory which match the param, sort them, and use them prepend the command name with "/bin" or "/usr/bin" as required. */
現代のccでコンパイルできるかと思ったら、エラーがボロボロと出てきた。まさか8進数で書いてあるのが気にいらなかったんでは無いよね。
ソースをざっと見した所、amatchが肝になる部分だ。再帰呼び出しでマッチングを進めている。なるほどね。
もう少し簡便なものは有ったなと思いを馳せていると、浮んできたよ。
正規表現
globって、単純に言うと、shell用のファイル名(or Dir名)に対するパターンマッチングだよね。 それって、心臓部は正規表現。巷に溢れていますなあ。
grep,awk,sed,python,ruby,perl ... きりがない。どれかの奴を見ればいいんだろうけど、 複雑怪奇なやつばかりだろうね。
昔の本で、ビューテフィルコードってのを思いだした。各界の著名人にコードの断片を提示してもらって、感嘆するって趣向の本。オイラーが持ってるのは、訳者のサイン入りのやつ。 引っ張り出してみたら、ぴったりの章があった。
プログラミング作法の本を書いた時、ロブ・パイクが作ったコードをカーニハンさんが賞賛してた。
原本に当る。
Practice of Programming (Downloads Grep program from Chapter 9)
正規表現の仕様は次の通り
c 文字 c そのもの ^ 行頭 $ 末尾 . 任意の一文字 * 0回以上の繰り返し
妥当な仕様だと思うぞ。perl軍団のこれでもか、これでもかと新発明するのは、誰が使うんやと思ってしまう。オイラーはすっかり、複雑な奴は書かなくなったからね。
matchstarをlongバージョンに置き換えると、最長マッチになる。
C語のポインターの扱いと再帰を、ほれぼれするぐらい上手に使っている。再帰は決して階乗を書くためだけにあるんじゃないよと、カーニハンさんが言ってる。オイラーには、耳に痛い指摘だな。
じっくり鑑賞するんだぞ。
考古学
上であげた unix v6 のglobを現代に蘇えらそう。懐古趣味です。amatchの使い方を調べる目的なんで、ちょいとmainを追加して、エラーにならない様に微調整したもの。
/* matcher -- ported from unix v6 glob */ #include <stdio.h> #include <stdlib.h> int amatch(char* , char*); int umatch(char *s, char *p) { if(*p==0) return(1); while(*s) if (amatch(s++,p)) return(1); return(0); } int amatch(char *as, char *ap) { char *s, *p; int scc, c, cc, ok, lc; s = as; p = ap; if ( (scc = *s++) ) if ((scc = scc & 0177) == 0) scc = 0200; switch (c = *p++) { case '[': ok = 0; lc = 077777; while ( (cc = *p++) ) { if (cc==']') { if (ok) return(amatch(s, p)); else return(0); } else if (cc=='-') { if (lc<=scc && scc<=(c = *p++)) ok++; } else if (scc == (lc=cc)) ok++; } return(0); default: if (c!=scc) return(0); case '?': if (scc) return(amatch(s, p)); return(0); case '*': return(umatch(--s, p)); case '\0': return(!scc); } } int main(int argc, char *argv[]) { if ( argc != 3 ){ printf("Usage: %s pat name\n", argv[0]); exit(1); } if (amatch(argv[2], argv[1])) { printf("%s\n", argv[2]); } }
使用例
ob6$ for f in * > do > ./a.out 's*' $f > done sh.1 sh.h shf.c shf.h syn.c
パターンは、shellの展開から逃れる為、シングルクォートで保護してます。例ではkshのソースツリー内で、頭がsで始まるファイルを列挙。
ob6$ for f in * > do > ./a.out '[st]*.h' $f > done sh.h shf.h table.h tree.h tty.h
こちはら、頭がsかtで始まり、尻尾がhで終るファイル。非常に緩い正規表現だけど、ファイルやディレクトリィーを指定するには、十分な仕様だと思うぞ。
本当は、glob内で、dir内を散歩して、要求に合致するのを見つけるんだけど、複雑になるんで、それはやっていない。
lib glob
glob(3)の例題をやってみる。
#include <glob.h> #include <unistd.h> int main() { glob_t g; g.gl_offs = 2; glob("*.c", GLOB_DOOFFS, NULL, &g); glob("*.h", GLOB_DOOFFS | GLOB_APPEND, NULL, &g); g.gl_pathv[0] = "ls"; g.gl_pathv[1] = "-l"; execvp("ls", g.gl_pathv); }
取り敢えず走ってみよう。
Breakpoint 1, main () at z.c:7 7 g.gl_offs = 2; (gdb) n 8 glob("*.c", GLOB_DOOFFS, NULL, &g); (gdb) 9 glob("*.h", GLOB_DOOFFS | GLOB_APPEND, NULL, &g); (gdb) set print pretty (gdb) p g $1 = { gl_pathc = 28, gl_matchc = 28, gl_offs = 2, gl_flags = 258, gl_pathv = 0x53d7c000, gl_statv = 0x0, gl_errfunc = 0x0, gl_closedir = 0x88932ac <_libc_atexit+28>, gl_readdir = 0x1608e510 <__fini>, gl_opendir = 0x0, gl_lstat = 0x0, gl_stat = 0x3608ffd0 }
構造体を確認
(gdb) n 10 g.gl_pathv[0] = "ls"; (gdb) p g $2 = { gl_pathc = 39, gl_matchc = 11, gl_offs = 2, gl_flags = 259, gl_pathv = 0x77f5ef00, gl_statv = 0x0, gl_errfunc = 0x0, gl_closedir = 0x88932ac <_libc_atexit+28>, gl_readdir = 0x1608e510 <__fini>, gl_opendir = 0x0, gl_lstat = 0x0, gl_stat = 0x3608ffd0 }
ちょっと収録数が増えた。確かにglobは動作してんな。
Breakpoint 2, glob (pattern=0x37f59324 "*.c", flags=2, errfunc=0x0, pglob=0xcf7f9eb0) at /usr/src/lib/libc/gen/glob.c:173 173 struct glob_lim limit = { 0, 0, 0 };
昔のunixだと、/etc/globに置いてあったのが、今ではライブラリィーの一員。ソースの場所も分かったので、旅してこい。
globは有名人なので、glob(3)の他にも、別の章に説明がある。
glob(7)
ob64$ man -s7 glob GLOB(7) Miscellaneous Information Manual GLOB(7) NAME glob - shell-style pattern matching : [!..] Like [..], except it matches any character not inside the brackets. \ Matches the character following it verbatim. This is useful to quote the special characters `?', `*', `[', and `\' such that they lose their special meaning. For example, the pattern "\\\*\[x]\?" matches the string "\*[x]?". : HISTORY In early versions of UNIX, the shell did not do pattern expansion itself. A dedicated program, /etc/glob, was used to perform the expansion and pass the results to a command. In Version 7 AT&T UNIX, with the introduction of the Bourne shell, this functionality was incorporated into the shell itself.
現代のglob正規表現は、昔よりも拡張されてる。これがshellに取り込まれたのは、v7の時代とな。
関連情報で、fnmatch(3)なんてのも紹介されてた。これって、オイラーが考古学で修復したやつじゃん。ああ、怖いregexもあるぞ。芋蔓式に次々に出てくるなあ。
case golang's glob
golang方面は、どうなってる? ぐぐったら、色々と出てきた。
package main import "fmt" import "path/filepath" import "syscall" func main() { files, _ := filepath.Glob("./*.[ch]") for _, f := range files { printPathAndSize(f) } } func printPathAndSize(path string) { var s syscall.Stat_t syscall.Stat(path, &s) fmt.Print(path) fmt.Print(": ") fmt.Print(s.Size) fmt.Println(" bytes") }
filepathのパッケージを調べたら、これでもかこれでもかって具合に色々な関数が用意されてたぞ。さすがだな。
ソフトウェア作法
手元に、こんな本がある。購入したのは1987年11月って書いてあった。家にある一番古い本になるかな。引っ越し時の処分を免れてきたんだから、思い入れがあるんだな。その割には再読もしてなかった。
内容は、Unixのtool類(wc,cp,grep,sed等)を、作ってみせる本。どう考えて、どう設計し、コーディングしテストしたかってのが丁寧に解説してある。
使ってる言語は、ratforって言う特殊なやつ。それをfortran語に変換してから、コンパイルするらしい。
fortranは再帰が使えないので、著者はボヤキつつ、それを回避するコードを提示してた。ちょっとまどろっこしい気もするが、まあ気にしないでおこう。
通読してみるかな。
家で一番古い本は、プログラミング言語C だった。1986年5月に購入。コンパイラーなんて高嶺の花で、ひたすら畳上の水練で、こういう本を読んでたんだな。
今は恵まれ過ぎているよ。時代の流れに感謝しよう。