(sigil fs)
Filesystem Operations
Work with files and directories: read metadata, traverse trees, find files with glob patterns, and manage temporary files. Complements (sigil io) for reading/writing and (sigil path) for path manipulation.
(import (sigil fs))
;; Read a config file
(define config (read-file-string "settings.json"))
;; Find all Scheme files
(glob "src/**/*.sgl") ; => ("src/main.sgl" "src/lib/utils.sgl" ...)
;; Walk a directory tree
(directory-walk "project"
(lambda (path type)
(println "~a: ~a" type path)
#t)) ; Return #t to recurse into directoriesExports
realpathprocedureReturn the canonicalized absolute pathname.
Resolves all symbolic links, . and .. components, and returns an absolute path. Returns #f if the path doesn't exist.
(realpath ".") ; => "/home/user/project"
(realpath "../other") ; => "/home/user/other"
(realpath "/tmp/../tmp") ; => "/tmp"
(realpath "nonexistent") ; => #fpath-resolveprocedureResolve a path relative to a base directory.
If path is absolute, returns it unchanged. Otherwise joins it with the base directory.
(path-resolve "/home/user" "file.txt") ; => "/home/user/file.txt"
(path-resolve "/home/user" "/tmp/x") ; => "/tmp/x"
(path-resolve "." "src/main.sgl") ; => "./src/main.sgl"file-exists?procedureCheck if a file or directory exists.
(file-exists? "config.txt") ; => #t or #f
(file-exists? "nonexistent") ; => #ffile?procedureCheck if a path is a regular file.
Returns #f for directories, symlinks, and non-existent paths.
(file? "main.sgl") ; => #t
(file? "src") ; => #f (it's a directory)directory?procedureCheck if a path is a directory.
(directory? "src") ; => #t
(directory? "main.sgl") ; => #fdirectory-exists?variableCheck if a directory exists at the given path.
This is an alias for directory?, provided for naming symmetry with file-exists?.
(directory-exists? "src") ; => #t
(directory-exists? "main.sgl") ; => #f (not a directory)
(directory-exists? "no-such") ; => #f (doesn't exist)file-statprocedureGet detailed file metadata.
Returns a list containing file information, or #f if the file doesn't exist. Pass #f as second argument to not follow symlinks. Use the stat-* accessor functions to extract specific fields.
(define info (file-stat "myfile.txt"))
(stat-size info) ; => 1234
(stat-mtime info) ; => 1702847123 (Unix timestamp)
(stat-type info) ; => 'regular
;; Don't follow symlinks
(file-stat "link" #f)file-sizeprocedureGet file size in bytes.
Returns #f if file doesn't exist.
(file-size "data.bin") ; => 4096
(file-size "missing") ; => #ffile-mtimeprocedureGet file modification time as Unix timestamp.
Returns #f if file doesn't exist.
(file-mtime "file.txt") ; => 1702847123file-mtime-nsprocedureGet file modification time as nanoseconds since the Unix epoch.
On filesystems and targets that expose sub-second timestamps this is suitable for freshness comparisons between writes in the same second. Targets without that support return seconds converted to nanoseconds. Returns #f if the file doesn't exist.
(file-mtime-ns "file.txt") ; => 1702847123123456789file-stat-fingerprintprocedureGet what one stat says about whether a file's bytes may have changed: (dev inode size mtime-ns ctime-ns), from a single call.
For memoising a content hash: recompute it when any field differs. touch -d can reset mtime but not ctime, which every write moves. Follows symlinks. Returns #f if the file doesn't exist.
(file-stat-fingerprint "file.txt")
; => (66306 1835011 42 1702847123123456789 1702847123123456789)file-tree-fingerprintprocedureGet one line per file under DIR, sorted by relative path: <relative path> TAB <dev> TAB <inode> TAB <size> TAB <mtime-ns> TAB <ctime-ns>.
Two equal results mean no file in the tree was added, removed, written or replaced in between. Follows symlinks. Returns #f when any part of the tree cannot be read.
(file-tree-fingerprint "build/dev/lib")
; => "sigil/core.sgb\t66306\t1835011\t48213\t...\n..."file-tree-metadataprocedureGet one line per entry at and under PATH, sorted by relative path, without following symlinks (PATH itself is the empty path): <relative path> TAB <type> TAB <mode> TAB <size> TAB <mtime-ns> TAB <ctime-ns> TAB <link target>.
type is f, d, l, p, s, b, c or ?; mode is the permission bits in octal; the link target is empty except for a symlink. Directories are listed, so an entry added or removed shows in its directory's times. Two equal results mean nothing in the tree was written, replaced, added, removed, retargeted or had its mode changed in between. The fields are those of GNU find PATH -printf '%P\t%y\t%m\t%s\t%T@\t%C@\t%l\n' | LC_ALL=C sort, on any platform, with the times as integer nanoseconds (whole seconds where the platform's stat has no finer field).
Returns #f, rather than raising, when PATH does not exist or any part of the tree cannot be read (a partial listing never passes as a whole). Raises a type error when PATH is not a string or contains a NUL byte.
(file-tree-metadata "build/dev/lib")
; => "\td\t755\t4096\t...\t\nsigil\td\t755\t4096\t...\t\n..."file-sha256procedureGet the SHA-256 of a file's contents as 64 lowercase hex digits.
Returns #f when the file cannot be read.
(file-sha256 "file.txt") ; => "2cf24dba5fb0a30e26e83b2ac5b9e29e..."file-atimeprocedureGet file access time as Unix timestamp.
Returns #f if file doesn't exist.
(file-atime "file.txt") ; => 1702850000file-ctimeprocedureGet file creation/change time as Unix timestamp.
On Unix systems this is the inode change time (ctime), not creation time. Returns #f if file doesn't exist.
(file-ctime "file.txt") ; => 1702847100stat-sizeprocedureExtract file size from a stat result.
(stat-size (file-stat "file.txt")) ; => 1234stat-mtimeprocedureExtract modification time from a stat result.
(stat-mtime (file-stat "file.txt")) ; => 1702847123stat-atimeprocedureExtract access time from a stat result.
(stat-atime (file-stat "file.txt")) ; => 1702850000stat-ctimeprocedureExtract creation/change time from a stat result.
(stat-ctime (file-stat "file.txt")) ; => 1702847100stat-modeprocedureExtract file mode (permissions) from a stat result.
Returns the raw Unix file mode bits.
(stat-mode (file-stat "script.sh")) ; => 33261 (0o100755)stat-uidprocedureExtract owner user ID from a stat result.
(stat-uid (file-stat "file.txt")) ; => 1000stat-gidprocedureExtract owner group ID from a stat result.
(stat-gid (file-stat "file.txt")) ; => 1000stat-nlinkprocedureExtract hard link count from a stat result.
(stat-nlink (file-stat "file.txt")) ; => 1stat-devprocedureExtract device ID from a stat result.
(stat-dev (file-stat "file.txt")) ; => 2049stat-inoprocedureExtract inode number from a stat result.
(stat-ino (file-stat "file.txt")) ; => 1234567stat-typeprocedureExtract file type from a stat result.
Returns a symbol: regular, directory, symlink, socket, fifo, block-device, char-device, or unknown.
(stat-type (file-stat "file.txt")) ; => 'regular
(stat-type (file-stat "src")) ; => 'directory
(stat-type (file-stat "link" #f)) ; => 'symlinksymlink?procedureTest if path is a symbolic link.
Does not follow the link—checks the link itself.
(symlink? "mylink") ; => #t if symlink
(symlink? "regular") ; => #fsocket-file?procedureTest if path is a Unix socket file.
(socket-file? "/var/run/docker.sock") ; => #tfifo?procedureTest if path is a named pipe (FIFO).
(fifo? "mypipe") ; => #t if named pipeblock-device?procedureTest if path is a block device.
(block-device? "/dev/sda") ; => #tchar-device?procedureTest if path is a character device.
(char-device? "/dev/tty") ; => #tmake-symlinkprocedureCreate a symbolic link.
Creates a symlink at link-path pointing to target. The target does not need to exist. Returns #t on success and #f when the link cannot be created (missing parent directory, existing path).
raises: never
(make-symlink "actual-file.txt" "shortcut")
(make-symlink "/usr/bin/python3" "python")read-symlinkprocedureRead the target of a symbolic link.
Returns the path the symlink points to, without resolving it. Returns #f if path is not a symlink.
(read-symlink "python") ; => "/usr/bin/python3"copy-fileprocedureCopy a file to a new location.
Copies the contents of source to destination. The destination file is created or overwritten. Returns #t on success and #f when the source cannot be read or the destination cannot be written (a missing source, a missing destination directory).
raises: never
(copy-file "original.txt" "backup.txt")
(copy-file "src/main.sgl" "dist/main.sgl")touchprocedureUpdate file timestamps or create an empty file.
If the file exists, updates its access and modification times to now. If it doesn't exist, creates an empty file. Returns #t on success and #f when the file cannot be created or touched (a missing parent directory).
raises: never
(touch "newfile.txt") ; Creates empty file
(touch "existing.txt") ; Updates timestampsdelete-fileprocedureDelete a file.
raises: io-error if the file doesn't exist or can't be deleted. Test with file-exists? first when a missing file is not an error.
(delete-file "temp.txt")rename-fileprocedureRename or move a file.
raises: io-error if the source doesn't exist or the destination cannot be written.
(rename-file "old-name.txt" "new-name.txt")make-directoryprocedureCreate a directory.
Creates a single directory. Parent directories must exist. Use ensure-directory to create parent directories as needed.
raises: io-error if a parent is missing or the directory cannot be created.
(make-directory "output")directory-listprocedureList the contents of a directory.
Returns a list of filenames (not full paths). Does not include . or ...
(directory-list "src") ; => ("main.sgl" "utils.sgl" "lib")%missing-file-defaultvariableRead entire file contents as a string.
Convenient for small to medium files. For large files or streaming, use (sigil io) port functions instead.
raises: io-error if the file cannot be opened, unless missing: is given, in which case a missing file returns that value instead (other errors still raise); error if PATH is a directory.
(define config (read-file-string "config.json"))
(define source (read-file-string "src/main.sgl"))
(read-file-string "optional.cfg" missing: #f) ; => #f when absentread-file-stringprocedureMISSING applies only to absent files. Other errors still propagate. TRIM removes surrounding whitespace from successfully read text.
read-file-bytesprocedureRead entire file contents as a bytevector.
For binary files like images, audio, etc.
raises: io-error if the file cannot be opened, unless missing: is given, in which case a missing file returns that value instead.
(define image (read-file-bytes "photo.png"))
(read-file-bytes "cache.bin" missing: #f) ; => #f when absentwrite-file-stringprocedureWrite a string to a file.
Creates the file if it doesn't exist, or replaces its contents if it does. Parent directories must exist.
raises: io-error if the file cannot be opened for writing (a missing parent directory, no permission).
(write-file-string "output.txt" "Hello, World!")
(write-file-string "data.json" (json-encode data))write-file-bytesprocedureWrite a bytevector to a file.
Creates the file if it doesn't exist, or replaces its contents. Parent directories must exist.
raises: io-error if the file cannot be opened for writing.
(write-file-bytes "output.bin" my-bytevector)delete-directoryprocedureRemove an empty directory.
The directory must be empty. Use directory-walk to remove contents first if needed. Returns #t on success and #f when the directory is missing or not empty.
raises: never
(delete-directory "empty-dir")ensure-directoryprocedureCreate directory and all parent directories.
Like mkdir -p—creates any missing parent directories. Does nothing if the directory already exists.
raises: io-error if a directory on the path cannot be created.
(ensure-directory "build/lib/sigil")
(ensure-directory "output")directory-walkprocedureWalk a directory tree, calling a procedure on each entry.
The procedure receives two arguments: the full path and a type symbol (file, directory, or other). For directories, return #t to recurse into them, or #f to skip.
;; Print all files
(directory-walk "src"
(lambda (path type)
(when (eq? type 'file)
(println "~a" path))
#t))
;; Count files, skip hidden directories
(let ((count 0))
(directory-walk "project"
(lambda (path type)
(cond
((eq? type 'file) (set! count (+ count 1)) #t)
((eq? type 'directory)
(not (string-starts-with? (path-basename path) ".")))
(else #t))))
count)directory-walk-listprocedureCollect all paths in a directory tree.
Returns a flat list of all files and directories, recursively.
(directory-walk-list "src")
; => ("src/main.sgl" "src/lib" "src/lib/utils.sgl" ...)directory-findprocedureFind files matching a predicate.
Walks the directory tree and returns paths where the predicate returns #t. The predicate receives path and type.
;; Find all .sgl files
(directory-find "src"
(lambda (path type)
(and (eq? type 'file)
(string-ends-with? path ".sgl"))))
;; Find empty directories
(directory-find "project"
(lambda (path type)
(and (eq? type 'directory)
(null? (directory-list path)))))find-filesprocedureRecursively find all files in a directory.
Returns a list of all file paths (not directories) under the given path, optionally filtered by a predicate. Hidden files (starting with .) are skipped by default.
;; All files recursively
(find-files "src")
; => ("src/main.sgl" "src/lib/utils.sgl" ...)
;; Filter to only .md files
(find-files "docs"
(lambda (path) (string-ends-with? path ".md")))
; => ("docs/index.md" "docs/guide/intro.md" ...)globprocedureFind files matching a glob pattern. Returns a list of matching file paths.
Supports shell-style wildcards: * matches any characters except /, and ** matches any path including subdirectories.
(glob "*.sgl") ; Files in current directory
(glob "src/*.sgl") ; Files in src/
(glob "src/**/*.sgl") ; All .sgl files recursively
(glob "test/*-test.sgl") ; Test filesmake-temp-fileprocedureCreate a temporary file.
Returns the path to a new unique temporary file. The file is created empty. Remember to delete it when done.
(define tmp (make-temp-file))
(write-file-string tmp "temporary data")
;; ... use the file ...
(delete-file tmp)make-temp-directoryprocedureCreate a temporary directory.
Returns the path to a new unique temporary directory. Remember to remove it when done (it must be empty first).
(define tmp-dir (make-temp-directory))
;; ... use the directory ...
(delete-directory tmp-dir)call-with-temp-fileprocedureUse a temporary file, then automatically delete it.
Creates a temp file, calls proc with its path, deletes the file, and returns the result of proc.
(call-with-temp-file
(lambda (path)
(write-file-string path "test data")
(run-external-tool path)))current-directoryprocedureGet the current working directory.
(current-directory) ; => "/home/user/project"set-current-directory!procedureChange the current working directory.
(set-current-directory! "/tmp")
(current-directory) ; => "/tmp"with-current-directoryprocedureExecute code in a different directory, then restore.
Changes to the given directory, runs thunk, then restores the original directory regardless of how thunk exits.
raises: io-error if the directory cannot be entered (missing, not a directory, no permission); the thunk does not run.
(with-current-directory "/tmp"
(lambda ()
(write-file-string "output.txt" "data")
(current-directory)))
; => "/tmp"
(current-directory)
; => "/home/user/project" (restored)make-hard-linkvariable(No description)
call-with-temp-directoryvariable(No description)
with-temp-dirvariable(No description)