fileutil_traverse(3tcl)

πŸ“¦
version 2.0 Β· 2 versions
, in debian trixie, ubuntu questing, ubuntu resolute, ubuntu stonking

πŸ’– fileutil_traverse β€” the file utilities manual, bestie edition ✨

okay so this is the fileutil_traverse(3tcl) page from the file utilities corner of the docs, and it's literally all about iterative directory traversal πŸ¦‹. come sit with me, we're gonna walk through the whole thing together, no skips, no skimming πŸ’…


🌸 NAME 🌸

fileutil_traverse β€” iterative directory traversal. that's it, that's the whole vibe. we go through directories, iteratively, one path at a time. periodt πŸ‘‘


πŸŽ€ SYNOPSIS πŸŽ€

package require Tcl 8.5 9

package require fileutil::traverse ?0.7?

package require fileutil

package require control

::fileutil::traverse ?objectName? path ?option value...?

$traverser command ?arg arg ...?

$traverser files

$traverser foreach filevar script

$traverser next filevar

yes, you need Tcl 8.5 9, and you need fileutil::traverse (version 0.7 is the optional bit), plus fileutil and control. get all of those and you're so good 🫢


✨ DESCRIPTION ✨

so this package hands you objects for programmable traversal of directory hierarchies. it's giving "i want to walk a whole tree and do stuff to every single thing in it." the main command the package exports is this one:

::fileutil::traverse ?objectName? path ?option value...?

  • it creates a brand new traversal object, and that object gets a global Tcl command attached to it whose name is objectName. you use that command to do all the operations on the traverser. slay.
  • if you pass the string %AUTO% as the objectName, the package will generate a unique name for you all by itself. iconic, no naming stress 😌
  • for the options it recognizes, go peek at the OPTIONS section below. heads up though: all of those options can only be set when you create the traversal object. changing them afterwards? not possible. attempt it and errors get thrown at you. fr fr, no takebacks πŸ’€
  • the object command has this general form:
$traverser command ?arg arg ...?

the command plus its arguments decide exactly what the object does.

and these are the commands you can throw at a traversal object:

πŸ’œ $traverser files

this is the most high-level method the traversal objects give you. invoke it and it hands back a list containing the names of all the files and directories that match the traverser's current configuration. chef's kiss.

πŸ’œ $traverser foreach filevar script

the high-level files method above is actually built on top of this mid-level one. invoke it and it finds every file and directory matching the current configuration, then executes the script for each path. the path currently under consideration gets stored in the variable named by filevar. both the variable and the script live and get executed in the context of the caller of the method. and in the files method, the script is just quietly saving the found paths into the list it's gonna return. sneaky, but in a cute way πŸ™ˆ

πŸ’œ $traverser next filevar

this is the lowest possible interface to the traverser β€” the core that every higher method is built on. invoke it and it returns a boolean: True if it found a path matching the current configuration, False if it didn't. if it did find one, that path gets stored in the variable named by filevar, in the context of the caller.

the foreach method is literally just calling this in a loop until it comes back False. this method is exposed so you can also incrementally traverse a directory hierarchy in an event-based manner. main character energy for the event loop πŸ’…

okay important note bestie: the traverser does follow symbolic links β€” except when following one would make it enter a link-cycle. in other words, it takes care not to lose itself in infinite loops when it bumps into circular link structures. and note that even links which are not followed will still show up in the result. 🌈

πŸ‘‘ OPTIONS πŸ‘‘

one bullet per option, all three of them, here we go:

  • -prefilter command_prefix β€” this callback runs for directories. its result decides whether the traverser recurses into that directory or not. default is to always recurse into all directories. the callback gets invoked with a single argument β€” the absolute path of the directory β€” and has to return a boolean value: True when the directory passes the filter, False if not. vibe check for directories πŸšͺ
  • -filter command_prefix β€” this callback runs for all paths. its result decides whether the current path is a valid result and gets returned by next. default is to accept all paths as valid. the callback gets invoked with a single argument β€” the absolute path to check β€” and has to return a boolean value: True when the path passes the filter, False if not. ✨
  • -errorcmd command_prefix β€” this callback runs for all paths the traverser has trouble with. like being unable to change into them, get their status, etc. default is to ignore any such problems. the callback gets invoked with two arguments: the absolute path for which the error occurred, and the error message. errors thrown by the filter callbacks get handled through this callback too. but errors thrown by the error callback itself are not caught and ignored β€” they're allowed to pass to the caller, i.e. whoever invoked the next. any other results from the callback are ignored. 😭

πŸ’€ WARNINGS AND INCOMPATIBILITIES πŸ’€

0.4.4 β€” in this version the traverser's broken system for handling symlinks got replaced with one that works correctly and properly enumerates all the legal non-cyclic paths under a base directory.

while that's correct, it also means certain pathological directory hierarchies with cross-linked sym-links will now take about O(n**2) time to enumerate, whereas the original broken code managed O(3tcl) due to its brokenness. yes bestie, that's what it says, i'm not touching it 😭

a concrete example and extreme case is the /sys hierarchy under Linux, where some hundred devices exist under both /sys/devices and /sys/class with the two sub-hierarchies linking to the other, generating millions of legal paths to enumerate. the structure, reduced to three devices, roughly looks like:

/sys/class/tty/tty0 --> ../../dev/tty0
/sys/class/tty/tty1 --> ../../dev/tty1
/sys/class/tty/tty2 --> ../../dev/tty1

/sys/dev/tty0/bus
/sys/dev/tty0/subsystem --> ../../class/tty
/sys/dev/tty1/bus
/sys/dev/tty1/subsystem --> ../../class/tty
/sys/dev/tty2/bus
/sys/dev/tty2/subsystem --> ../../class/tty

when you have to handle such a pathological hierarchy, it's recommended to use the -prefilter option to stop the traverser from following symbolic links. like so:

package require fileutil::traverse

proc NoLinks {fileName} {
    if {[string equal [file type $fileName] link]} {
        return 0
    }
    return 1
}

fileutil::traverse T /sys/devices -prefilter NoLinks
T foreach p {
    puts $p
}
T destroy
the way that little NoLinks proc just says "nope, not a link, not today" and saves you from an infinite-ish crawl... we love a protective bestie 🫢

πŸ¦‹ BUGS, IDEAS, FEEDBACK πŸ¦‹

this document, and the package it describes, will <highkey>undoubtedly</highkey> contain bugs and other problems. please report such in the category fileutil of the Tcllib Trackers at http://core.tcl.tk/tcllib/reportlist. please also report any ideas for enhancements you may have for either the package and/or the documentation. we want the glow-up, ngl πŸ’–

when proposing code changes, please provide unified diffs, i.e. the output of diff -u.

note further that attachments are strongly preferred over inlined patches. attachments can be made by going to the Edit form of the ticket immediately after its creation, and then using the left-most button in the secondary navigation bar. iykyk πŸŽ€


🌟 KEYWORDS 🌟

directory traversal, traversal


🌈 CATEGORY 🌈

Programming tools


okay bestie that's the whole page, every single option and warning and example accounted for, nothing dropped, nothing invented πŸ’… and for the cute sign-off: this one comes to you from tcllib, version 0.7, page fileutil_traverse(3tcl). now go traverse something responsibly, and remember β€” prefilter your symlinks πŸ§šβœ¨πŸ’œ

πŸ“¦ where this page comes from