【问题标题】:TCL: Recursively search subdirectories to source all .tcl filesTCL:递归搜索子目录以获取所有 .tcl 文件
【发布时间】:2009-01-09 19:34:24
【问题描述】:

我有一个主 TCL proc,它在其他文件夹和后续子目录中获取大量其他 tcl proc。例如,在主进程中它有:

source $basepath/folderA/1A.tcl
source $basepath/folderA/2A.tcl
source $basepath/folderA/3A.tcl
source $basepath/folderB/1B.tcl
source $basepath/folderB/2B.tcl
source $basepath/folderB/3B.tcl

当我总是知道我将在文件夹 A 和文件夹 B 中获取所有内容时,这样做似乎有点愚蠢。是否有一个功能(或简单的方法)可以让我只获取整个文件夹中的所有 .tcl 文件?

【问题讨论】:

    标签: tcl


    【解决方案1】:

    根据 ramanman 的回复,这里有一个使用内置 TCL 文件命令解决问题的例程,它递归地沿着目录树向下工作。

    # findFiles
    # basedir - the directory to start looking in
    # pattern - A pattern, as defined by the glob command, that the files must match
    proc findFiles { basedir pattern } {
    
        # Fix the directory name, this ensures the directory name is in the
        # native format for the platform and contains a final directory seperator
        set basedir [string trimright [file join [file normalize $basedir] { }]]
        set fileList {}
    
        # Look in the current directory for matching files, -type {f r}
        # means ony readable normal files are looked at, -nocomplain stops
        # an error being thrown if the returned list is empty
        foreach fileName [glob -nocomplain -type {f r} -path $basedir $pattern] {
            lappend fileList $fileName
        }
    
        # Now look for any sub direcories in the current directory
        foreach dirName [glob -nocomplain -type {d  r} -path $basedir *] {
            # Recusively call the routine on the sub directory and append any
            # new files to the results
            set subDirList [findFiles $dirName $pattern]
            if { [llength $subDirList] > 0 } {
                foreach subDirFile $subDirList {
                    lappend fileList $subDirFile
                }
            }
        }
        return $fileList
     }
    

    【讨论】:

    • 谢谢杰克逊。我想我们现在可以平息这一切了!
    • 如果你有一个创建循环的符号链接,你会得到“嵌套计算太多(无限循环?)”错误。
    • sudo apt-get install tcllib tcl tk
    【解决方案2】:

    在船上使用 tcllib 变得微不足道:

    package require fileutil
    foreach file [fileutil::findByPattern $basepath *.tcl] {
        source $file
    }
    

    【讨论】:

      【解决方案3】:

      也许更独立于平台并使用内置命令而不是管道到进程:

      foreach script [glob [file join $basepath folderA *.tcl]] {
        source $script
      }
      

      对文件夹 B 重复。

      如果您有更严格的选择标准,并且不担心在任何其他平台上运行,使用 find 可能会更灵活。

      【讨论】:

      • 我唯一注意到的是,如果没有文件匹配,这将返回错误,但我承认我没有检查其他答案的作用。
      • 使用 glob 命令上的 -nocomplain 选项来阻止它抛出并在生成空列表时出错。
      【解决方案4】:

      这是一种方法:

      set includes [open "|find $basedir -name \*.tcl -print" r]
      
      while { [gets $includes include] >= 0 } {
        source $include
      }
      
      close $includes
      

      【讨论】:

        【解决方案5】:

        基于之前的回答,此版本处理由符号链接创建的循环,并在此过程中消除了由于符号链接而导致的重复文件。

        # findFiles
        # basedir - the directory to start looking in
        # pattern - A pattern, as defined by the glob command, that the files must match
        proc findFiles {directory pattern} {
        
            # Fix the directory name, this ensures the directory name is in the
            # native format for the platform and contains a final directory seperator
            set directory [string trimright [file join [file normalize $directory] { }]]
        
            # Starting with the passed in directory, do a breadth first search for
            # subdirectories. Avoid cycles by normalizing all file paths and checking
            # for duplicates at each level.
        
            set directories [list]
            set parents $directory
            while {[llength $parents] > 0} {
        
                # Find all the children at the current level
                set children [list]
                foreach parent $parents {
                    set children [concat $children [glob -nocomplain -type {d r} -path $parent *]]
                }
        
                # Normalize the children
                set length [llength $children]
                for {set i 0} {$i < $length} {incr i} {
                    lset children $i [string trimright [file join [file normalize [lindex $children $i]] { }]]
                }
        
                # Make the list of children unique
                set children [lsort -unique $children]
        
                # Find the children that are not duplicates, use them for the next level
                set parents [list]
                foreach child $children {
                    if {[lsearch -sorted $directories $child] == -1} {
                        lappend parents $child
                    }
                }
        
                # Append the next level directories to the complete list
                set directories [lsort -unique [concat $directories $parents]]
            }
        
            # Get all the files in the passed in directory and all its subdirectories
            set result [list]
            foreach directory $directories {
                set result [concat $result [glob -nocomplain -type {f r} -path $directory -- $pattern]]
            }
        
            # Normalize the filenames
            set length [llength $result]
            for {set i 0} {$i < $length} {incr i} {
                lset result $i [file normalize [lindex $result $i]]
            }
        
            # Return only unique filenames
            return [lsort -unique $result]
        }
        

        【讨论】:

          【解决方案6】:

          与 schlenk 相同的想法:

          package require Tclx
          for_recursive_glob scriptName $basepath *.tcl {
              source $scriptName
          }
          

          如果你只想要文件夹 A 和文件夹 B 而不是 $basepath 下的其他文件夹:

          package require Tclx
          for_recursive_glob scriptName [list $basepath/folderA $basepath/folderB] *.tcl {
              source $scriptName
          }
          

          【讨论】:

            【解决方案7】:

            Joseph Bui 的回答效果很好,只是它跳过了初始文件夹中的文件。

            改变:

            设置目录[列表]
            到:
            设置目录 [list $directory]

            修复

            【讨论】: