Gauche:ディレクトリを再帰的に処理

Gauche:ディレクトリを再帰的に処理

よく、ディレクトリを再帰的になめてごちゃごちゃやりたいことがある。 簡単なものならfind使ってone linerで済ませるんだが、 ちょっとややこしい処理だとスクリプトにしたくなる。 そういう時に書いた使い捨てスクリプトを貯めておくページ。

ソースファイルの行数カウント

まあ、処理自体は find . -name '*.scm' | xargs wc -l で済むことなんですが、 良く出てきそうなパターンなんで書いときます。 port-foldの中でごにょごにょすれば、コメントは飛ばすとか色々できますし。

#!/usr/bin/gosh
(use gauche.parseopt)
(use file.util)
(use srfi-13)

(define matcher #/\.scm$/)            ;default

(define (main args)
  (let* ((arg (parse-options (cdr args) (("c" () (set! matcher #/\.[ch]$/)))))
         (total
          (directory-fold (if (null? arg) "." (car arg))
                          (lambda (path knil)
                            (if (matcher path)
                                (let1 lines
                                    (with-input-from-file path
                                      (lambda ()
                                        (port-fold (lambda (line count)
                                                     (+ count 1))
                                                   0
                                                   read-line)))
                                  (format #t "~6d  ~a\n" lines path)
                                  (+ lines knil))
                                knil))
                          0)))
    (format #t "~6d  total\n" total))
  0)

directory-for-each

directory版for-eachです。幅優先で木を処理します。 末端にだけ興味が有る場合はdirectory-foldを使うほうが良いでしょう。 fold系関数がピンと来ない人(私)にもお勧めです。--hira(2004/04/11 20:01:52 PDT)

#!/usr/bin/gosh
(use file.util) ;directory-list
(define (directory-for-each proc . base)
  (define (loop cwd)
    (define (down dir)
      (if (file-is-directory? dir) (loop dir)))
    (let1 ls (directory-list cwd :add-path? #t :children? #t)
      (for-each proc ls)
      (for-each down ls)))
  (for-each loop base))

(define (main args)
  (apply directory-for-each
    (lambda (path)
      (print "p:"               path )
      (print "d:" (sys-dirname  path))
      (print "b:" (sys-basename path))
      (print "t:" (file-type    path)))
    (cdr args)))

directory-traverse

(2005/10/24 02:07:44 PDT) directory-for-eachを深さ優先にして、 unixのfind(1)やRubyのFind.findっぽくしたものです。 procが#fを返すとそのサブディレクトリを探査から外します(RubyのFind.pruneに相当)。

(use file.util) ; directory-list, file-type
(define (make-selector . opts)
  (lambda (e)
    (and (file-exists? e)
         (eq? (apply file-type e opts) 'directory)))))

(define (directory-traverse path proc . opts)
  (define direcotry? (apply make-selector opts))
  (let rec ((path path) (depth 0))
    (when (and (proc path depth) (direcotry? path))
      (for-each (cute rec <> (+ depth 1))
                (directory-list path :add-path? #t :children? #t)))))

使用例。カレントディレクトリ以下のファイルを段差付け表示。

(directory-traverse "."
  (lambda (path depth)
    (format #t "~a~a\n" (make-string depth) (sys-basename path))
    #t))

以下はpost orderの処理もできるようにしたバージョン。 post orderの処理を行うthunkをprocの2番目の返り値にする。

(define (directory-traverse path proc . opts)
  (define directory? (apply make-selector opts))
  (let rec ((path path)
            (depth 0))
    (receive (continuep after) (proc path depth)
      (when (and (directory? path) continuep)
        (for-each (cute rec <> (+ depth 1))
                  (directory-list path :add-path? #t :children? #t)))
      (when after (after)))))

使用例。

(directory-traverse "."
  (lambda (path depth)
    (values #t
            (cut format #t "~a~a\n" (make-string depth) (sys-basename path)))))

globを使った簡易版

一時リストを作ることを厭わなければ、

  (glob #`",|dir|/**/*")

でdir以下の(ディレクトリ含む)全てのファイルのパス名のリストが返ってくるので あとはそれをmapやfilterでごにょごにょする。 書き捨てスクリプトではこっちの方が楽。


Last modified : 2012/02/07 08:08:52 UTC