===============================================================================
FileOps 1.0.0
Techlosoft "File Command Center" product line - Vertical variant
===============================================================================
FileOps is a bulk rename engine. You give it a regular expression and a name
template, and it rewrites file names IN PLACE. It never moves a file to a
different directory, never deletes anything, and never overwrites anything.
Where the siblings differ:
FilePilot - general file browsing and inspection.
FolderForge - rule-based sorting; MOVES files between folders.
FileOps - REWRITES NAMES in place, with a real pattern language,
all-or-nothing collision safety, and an undo journal that
can put every single file back where it started.
FolderForge changes where a file lives. FileOps changes what it is called,
and can take it back.
-------------------------------------------------------------------------------
INSTALLING
-------------------------------------------------------------------------------
Pre-built binaries are in dist/:
dist/fileops-linux-amd64
dist/fileops-darwin-amd64
dist/fileops-darwin-arm64
dist/fileops-windows-amd64.exe
Copy the one for your machine somewhere on your PATH and rename it to
"fileops" (or "fileops.exe" on Windows). On macOS and Linux you may need to
mark it executable first:
chmod +x fileops
Building from source needs nothing but a Go 1.24 toolchain. There are no
third-party dependencies, so no network access is required:
go build -o fileops .
-------------------------------------------------------------------------------
COMMANDS
-------------------------------------------------------------------------------
fileops rename
--match --to [options]
fileops preview --match --to [options]
fileops undo --journal [--apply] [--json]
fileops help
rename Computes the full rename plan and prints it. It is a DRY RUN unless
you pass --apply. Without --apply, not one byte on disk changes and
no journal file is created.
preview Identical to rename but permanently read-only. Passing --apply to
preview is ignored on purpose, so preview is safe to wire into
scripts, aliases, and right-click menus.
undo Reads a journal written by an earlier "rename --apply" and reverses
every rename it recorded, newest first. Also a DRY RUN unless you
pass --apply.
help Prints usage and exits 0. "-h" and "--help" do the same, anywhere
on the command line.
OPTIONS
--match Go regular expression, matched against the FILE NAME
only (not the full path). Files that do not match are
left completely alone.
--to Destination name template. See TEMPLATE LANGUAGE.
--recursive Descend into subdirectories. Each file is renamed inside
its own directory; directories themselves are never
renamed.
--start First value of the {n} counter. Default 1.
--pad Zero-pad {n} to N digits. Default 0 (no padding).
--apply Actually perform the renames.
--journal Where to write the undo journal (rename), or which
journal to reverse (undo).
--json Emit a machine-readable JSON report on stdout.
Flags may appear before or after the directory argument; both orders work.
Exit codes: 0 on success (including "nothing matched"), 1 on any usage error,
invalid pattern, invalid template, collision abort, or I/O failure.
-------------------------------------------------------------------------------
TEMPLATE LANGUAGE
-------------------------------------------------------------------------------
{1} {2} {3} ... Capture groups from --match. {0} is the whole match.
Referring to a group the pattern does not define is an
error caught before anything is renamed.
{name} Original file name without its extension.
{ext} Extension including the leading dot. Empty if the file
has no extension.
{n} Sequence counter. Starts at --start, zero-padded to
--pad digits, incremented once per MATCHED file.
{date} File modification time, formatted YYYY-MM-DD.
{size} File size in bytes, as a plain decimal number.
{upper:X} Uppercase whatever X renders to.
{lower:X} Lowercase whatever X renders to.
{title:X} Title-case whatever X renders to: the first letter of
each word is uppercased, the rest lowercased. A word
boundary is any character that is not a letter or digit.
Case transforms take an arbitrary sub-template, so they nest and compose:
{upper:{name}}{ext} REPORT.txt
{lower:{name}}{lower:{ext}} report.txt
{title:{1}}-{n}{ext} Annual-007.txt
Examples:
fileops preview ./photos --match '^IMG_(\d+)\.jpg$' --to 'holiday-{1}.jpg'
fileops rename ./photos --match '\.jpg$' --to 'photo-{n}{ext}' \
--start 1 --pad 3 --apply --journal ./photos.journal.json
fileops rename ./scans --match '^(.*)\.pdf$' --to '{1}-{date}-{size}b.pdf' \
--apply --journal ./scans.journal.json
fileops undo --journal ./photos.journal.json --apply
-------------------------------------------------------------------------------
DETERMINISTIC ORDER
-------------------------------------------------------------------------------
Files are always processed in a stable sorted order: directory path first,
then file name, both compared as plain strings with forward slashes. The {n}
counter follows that order. The same directory contents plus the same command
always produce exactly the same mapping, on every run and every machine. That
is what makes {n} safe to use in scripts and what makes a plan you previewed
yesterday still be the plan that runs today.
-------------------------------------------------------------------------------
COLLISION SAFETY - ALL OR NOTHING
-------------------------------------------------------------------------------
Before a single file is touched, FileOps computes EVERY destination and checks
the whole batch:
1. If two or more source files would land on the same destination name,
that is a collision.
2. If a destination already exists on disk and is NOT itself one of the
files being renamed away, that is a collision.
If there is even one collision anywhere in the batch, the ENTIRE run aborts.
Nothing is renamed - not the colliding files, and not the perfectly fine files
that happened to be in the same batch. FileOps reports every collision and
every source involved in it, and exits 1. No journal is written.
There is no partial rename and no "--force" that turns this off. A half-renamed
directory is much worse than a directory that did not change.
-------------------------------------------------------------------------------
SWAPS AND CYCLES - HOW WE HANDLE THEM
-------------------------------------------------------------------------------
We implement swaps CORRECTLY rather than aborting on them.
The classic hard case is a -> b and b -> a in the same batch (or a longer
cycle such as a -> b -> c -> a). A naive tool renames a onto b and destroys
b's contents. FileOps executes every batch in two phases:
Phase 1: every source file is moved to a unique temporary name in its own
directory.
Phase 2: every temporary is moved to its final destination.
Because no destination is occupied when phase 2 runs, arbitrary permutations,
swaps, and cycles of any length all work. If any individual rename fails
partway through, the moves already made are rolled back and the tree is left
exactly as it was found.
Verified behaviour: with two files ab.txt and ba.txt and the template
"--match '^(.)(.)\.txt$' --to '{2}{1}.txt'", the two files genuinely swap
contents-under-names; a three-file rotation does the same; and undo reverses
either one perfectly.
-------------------------------------------------------------------------------
THE UNDO JOURNAL
-------------------------------------------------------------------------------
When "rename --apply" is given a --journal path, FileOps writes a small JSON
file recording the absolute from/to path of every rename it performed:
{
"tool": "fileops",
"version": "1.0.0",
"created": "2026-08-10T00:00:00Z",
"directory": "/home/you/photos",
"entries": [
{ "from": "/home/you/photos/IMG_0001.jpg",
"to": "/home/you/photos/trip-10-0001.jpg" }
]
}
"fileops undo --journal --apply" replays that list backwards, putting
every file back at its original path. Undo runs through exactly the same
collision checker and the same two-phase engine as rename, so it is equally
all-or-nothing. If any file recorded in the journal is no longer where the
journal says it is - you moved it, deleted it, or renamed it again by hand -
undo aborts, names each missing file, and moves nothing.
Undo is a DRY RUN by default, just like rename.
The journal is only written when renames actually happen. A dry run never
creates one. If you use --apply without --journal, FileOps warns you on stderr
that the operation will not be undoable.
-------------------------------------------------------------------------------
WHAT IS IMPLEMENTED
-------------------------------------------------------------------------------
- rename, preview, and undo commands.
- Regex matching against file names, with capture groups.
- Template tokens: {N}, {name}, {ext}, {n}, {date}, {size}.
- Case transforms {upper:X}, {lower:X}, {title:X}, nestable.
- --start and --pad control of the sequence counter.
- Deterministic, reproducible processing order.
- --recursive descent into subdirectories.
- Full-batch collision detection, both source-to-source and against files
already on disk, with an all-or-nothing abort.
- Correct swap and cycle handling via two-phase temporary names.
- Rollback if an individual rename fails mid-batch.
- JSON undo journal and a real undo command.
- Dry run by default everywhere; --apply required to change anything.
- --json machine-readable output for rename, preview, and undo, including
on collision and missing-file aborts.
- Clean errors and exit code 1 for invalid regexes, unknown or unbalanced
template tokens, out-of-range capture groups, empty destination names,
and destination names containing a path separator.
-------------------------------------------------------------------------------
WHAT IS NOT IMPLEMENTED
-------------------------------------------------------------------------------
Stated plainly so nobody is surprised:
- Directories are never renamed. Only regular files inside them are.
- Files are never moved between directories. A template that produces a
path separator is rejected rather than silently moving the file. If you
need files reorganised into folders, that is FolderForge's job.
- Nothing is ever deleted, and no destination is ever overwritten. There is
no --force and no --overwrite.
- No file CONTENTS are read or modified. FileOps only ever renames.
- No EXIF, ID3, PDF, or other embedded-metadata tokens. {date} is the
filesystem modification time, not the date a photo was taken.
- No interactive or TUI mode. Output is plain text or JSON.
- No undo-of-undo. Undo replays a journal backwards; it does not write a
new journal of its own.
- No cross-filesystem or network-share-specific handling beyond what the
operating system's rename call provides.
- Case-only renames (report.TXT -> report.txt) are fully supported on
case-sensitive filesystems, which is the normal Linux case. On
case-INSENSITIVE filesystems (default macOS and Windows) the operating
system considers the old and new names to be the same file, so the
collision checker sees a destination that already exists on disk and
conservatively aborts the run rather than guessing. Working around that
safely is on the roadmap; today, do it in two passes with an
intermediate name.
- No configuration file, no profiles, no saved rulesets.
-------------------------------------------------------------------------------
ROADMAP
-------------------------------------------------------------------------------
- EXIF and ID3 metadata tokens: {exif:DateTimeOriginal}, {exif:Camera},
{id3:Artist}, {id3:Album}, {id3:Track}, so photo and music libraries can
be renamed from what is actually inside the file rather than from the
filesystem timestamp.
- An interactive TUI preview: a scrollable two-column before/after table
that updates live as you edit the pattern and template, with per-file
include/exclude toggles before you commit.
- Regex find-and-replace inside file CONTENTS, with the same dry-run-first,
all-or-nothing, journal-backed undo model that renaming already has.
- Windows Explorer shell-extension integration: a right-click "Bulk rename
with FileOps" entry on a selection or folder that opens the preview with
the selection pre-loaded.
-------------------------------------------------------------------------------
SUPPORT
-------------------------------------------------------------------------------
Techlosoft - File Command Center product line.
Run "fileops help" for the built-in reference.