Acquis 7 - Filesystem

Contents (17)
  1. Listing directories
  2. File type inspection
  3. Copying files
  4. Moving and renaming
  5. Removing files and directories
  6. Creating directories
  7. Symbolic links
  8. Path manipulation
    1. Joining paths
    2. Splitting paths
    3. Extracting filename and parent
    4. Platform constants
  9. Error handling
  10. Complete example
  11. Motivation
    1. Missing operations
    2. Path handling

The fs library provides cross-platform filesystem operations. It exposes a global fs table with functions for file manipulation, directory operations, symlinks, and path utilities.

Listing directories#

Use fs.list to get an array of filenames in a directory:

local files = fs.list(".")
for i = 1, #files do
    print(files[i])
end

Filter results with glob patterns:

local lua_files = fs.list("src", "*.lua")
local configs = fs.list(".", "config?.json")

The patterns support * (match any characters) and ? (match single character).

File type inspection#

Use fs.type to determine if a path is a file or directory:

local t, is_link = fs.type("myfile.txt")
assert(t == "file")
assert(is_link == false)

local t2, is_link2 = fs.type("mydir")
assert(t2 == "directory")

The second return value indicates whether the path is a symbolic link.

Copying files#

Use fs.copy to copy a file:

fs.copy("original.txt", "backup.txt")

The operation fails if the destination already exists:

local ok = catch fs.copy("src.txt", "existing.txt")
assert(not ok)  -- error: target already exists

Moving and renaming#

Use fs.move to move or rename files and directories:

fs.move("old_name.txt", "new_name.txt")
fs.move("file.txt", "subdir/file.txt")

Like fs.copy, it fails if the destination exists:

local ok = catch fs.move("a.txt", "b.txt")  -- fails if b.txt exists

Removing files and directories#

Use fs.remove to delete files or empty directories:

fs.remove("unwanted.txt")
fs.remove("empty_dir")

For non-empty directories, pass true as the second argument:

fs.remove("project_dir", true)  -- recursive delete

The recursive remove safely handles symlinks without following them.

Creating directories#

Use fs.createdirectory to create a new directory:

fs.createdirectory("new_folder")

By default, the parent directory must exist:

local ok = catch fs.createdirectory("nonexistent/child")
assert(not ok)  -- error: parent does not exist

Pass true as the second argument to create parent directories automatically:

fs.createdirectory("a/b/c/d", true)  -- creates a, a/b, a/b/c, and a/b/c/d

With recursive mode, existing directories are silently skipped:

fs.createdirectory("existing_dir", true)  -- no error

Create symbolic links with fs.createlink:

fs.createlink("shortcut", "path/to/target")

Read where a symlink points with fs.follow:

local target = fs.follow("shortcut")
print(target)  -- "path/to/target"

Path manipulation#

The fs.path subtable provides path utilities.

Joining paths#

Use fs.path.join to combine path components:

local path = fs.path.join("dir", "subdir", "file.txt")
-- "dir/subdir/file.txt" on Unix
-- "dir\subdir\file.txt" on Windows

Absolute paths in later arguments replace earlier components:

local path = fs.path.join("relative", "/absolute")
assert(path == "/absolute")

Splitting paths#

Use fs.path.split to break a path into components:

local parts = fs.path.split("a/b/c.txt")
assert(parts[1] == "a")
assert(parts[2] == "b")
assert(parts[3] == "c.txt")

Extracting filename and parent#

Use fs.path.name to get the filename:

local name = fs.path.name("/home/user/document.pdf")
assert(name == "document.pdf")

Use fs.path.parent to get the parent directory:

local parent = fs.path.parent("/home/user/document.pdf")
assert(parent == "/home/user")

Platform constants#

The separator and delimiter vary by platform:

print(fs.path.separator)  -- "/" on Unix, "\" on Windows
print(fs.path.delimiter)  -- ":" on Unix, ";" on Windows

Error handling#

All fs functions throw errors on failure. Use catch for error handling:

local ok, err = catch fs.copy("missing.txt", "dest.txt")
if not ok then
    print("Copy failed:", err)
end

Error messages include the path and specific reason:

catch fs.remove("nonexistent")
-- "cannot remove 'nonexistent': path does not exist"

catch fs.createdirectory("existing_dir")
-- "cannot create directory 'existing_dir': already exists"

catch fs.remove("nonempty_dir")
-- "cannot remove directory 'nonempty_dir': not empty"

Complete example#

A function to copy a directory recursively:

local function copydir(src, dst)
    fs.createdirectory(dst)

    for _, name in ipairs(fs.list(src)) do
        local src_path = fs.path.join(src, name)
        local dst_path = fs.path.join(dst, name)

        local t = fs.type(src_path)
        if t == "directory" then
            copydir(src_path, dst_path)
        else
            fs.copy(src_path, dst_path)
        end
    end
end

copydir("project", "project_backup")

Motivation#

Lua’s standard library provides minimal filesystem support. The os library only offers os.remove and os.rename, with platform-dependent behavior and inconsistent error handling.

Missing operations#

Lua has no built-in way to list directory contents, copy files, create directories, work with symbolic links, or manipulate paths portably. Developers must use external libraries like LuaFileSystem or shell commands, making the language less sovereign.

Path handling#

Building paths with string concatenation is error-prone:

-- Lua: manual path building
local path = dir .. "/" .. file  -- wrong on Windows
local path = dir .. "\\" .. file  -- wrong on Unix

The fs.path functions handle platform differences:

-- Lus: portable path handling
local path = fs.path.join(dir, file)  -- correct everywhere