Peiosutils
The Peios core utilities — the everyday commands, reworked for the Peios security model.
Single-page view · as markdown
Files and directories
Peios / Using Peios / Peiosutils / Files and directories
This topic covers the commands that change the file system: creating files and directories, copying and moving them, removing them, and adjusting their size. If a command brings a file into existence, takes one out of it, or duplicates one, it is here.
This page names every command, and then explains the one theme that runs through the whole topic on Peios: what happens to a file's security descriptor when the file is created or copied.
The commands #
| Command | Purpose |
|---|---|
cp | Copy files and directories. |
mv | Move or rename files and directories. |
rm | Remove files, and with -r, directories. |
rmdir | Remove directories, but only empty ones. |
mkdir | Create directories. |
mkfifo | Create named pipes (FIFOs). |
mknod | Create special files — device nodes and FIFOs. |
touch | Create empty files, or update a file's timestamps. |
ln | Create hard links and symbolic links. |
link and unlink | The single-purpose, low-level versions of "make a hard link" and "remove a file". |
shred | Destroy a file's contents by overwriting them, so they cannot be recovered. |
dd | Copy data block by block, converting it on the way. |
df | Report how much space each file system has, and how much is free. |
du | Report how much space files and directories use. |
truncate | Shrink or extend a file to an exact size. |
mktemp | Create a temporary file or directory with a safely unique name. |
Every file has a security descriptor #
On Peios, every file and directory carries a security descriptor — the record of who owns it and who may do what to it. Access to a file is decided from its descriptor; see Security descriptors for the full picture.
That fact shapes two groups of commands in this topic.
Creating a file gives it a descriptor #
When mkdir, mkfifo, mknod, or touch creates a new object, that object needs a security descriptor. By default it gets one inherited from the directory it is created in — the new object picks up the access rules its parent directory hands down.
All four of these commands share one set of options — the creation flags — that let you set the new object's descriptor explicitly instead of taking the inherited default: name an owner, set a different group, lock the object so it does not inherit, give it an integrity label, or supply a complete descriptor. The flags are documented in full on the mkdir page; mkfifo, mknod, and touch each link back to it.
Copying a file makes a fresh descriptor — unless you preserve #
cp creates new files, and a cross-file-system mv does too. A brand-new copy gets a brand-new descriptor inherited from its destination directory — it does not carry the source file's security across by default.
When you do want the copy to keep the source's owner, access rules, timestamps, or other attributes, that is the job of the --preserve family of options. cp and mv share an identical --preserve surface; it is documented on the cp page.
Removing a file checks whether you may write it #
rm and shred both make a decision based on whether you can write a file. rm prompts before removing a file you cannot write to; shred's --force overrides a file's protection so it can be destroyed. Both judge "can you write this?" by a live access check against the file's security descriptor — not by any decorative metadata on the inode. Each command's page explains exactly where that check is made.
Where to start #
For copying — and for the whole --preserve model — read cp.
For creating files and directories, and the shared creation flags, read mkdir.
For removing things safely, read rm.
cp
Peios / Using Peios / Peiosutils / Files and directories
cp copies files and directories.
cp [options] source dest
cp [options] source... directory
cp [options] -t directory source...
In the first form, cp copies one source to one dest. In the other two, it copies any number of sources into a directory, keeping their names.
$ cp report.txt report-backup.txt
$ cp report.txt notes.txt /home/jack/archive/
Copying directories #
cp refuses to copy a directory unless you ask it to descend into one:
| Option | Effect |
|---|---|
-r, -R, --recursive | Copy directories, and everything inside them, recursively. |
-a, --archive | A faithful recursive copy: copy the whole tree, follow no symbolic links, and preserve every attribute. Equivalent to -d -R --preserve-all. |
-a is the option to use when you want the copy to be as close to the original as possible — same structure, same security, same timestamps. A plain -r copies the contents but, as the next section explains, gives the copies fresh security of their own.
What a copy's security descriptor is #
This is the part of cp that is specific to Peios, so it is worth being precise.
Every file carries a security descriptor — the record of who owns it and who may do what to it. When cp copies a file, it creates a new file, and that new file needs a descriptor.
By default, a copy gets a fresh descriptor — it does not inherit the source's. Specifically, a plain cp:
- gives the copy a descriptor inherited from the destination directory, exactly as if you had created a new file there;
- makes you the owner of the copy;
- gives the copy fresh timestamps — the copy's modification time is "now".
This is usually what you want. A file copied into your home directory should be governed by your home directory's rules and owned by you, regardless of where it came from. But when you need the copy to keep something from the source, you ask for it explicitly — with the --preserve family.
The --preserve family #
--preserve carries chosen attributes from the source onto the copy. The attributes you can preserve:
| Attribute | What it carries from the source |
|---|---|
owner | The owner SID. |
dacl | The whole DACL — every access rule on the file, inherited rules included. |
sacl | The whole SACL — the audit rules and the integrity label. |
daclni | The DACL with inherited rules stripped out — only the rules set explicitly on the source. |
saclni | The SACL with inherited rules stripped out. |
timestamps | The access and modification times. |
links | The hard-link structure — files hard-linked together in the source stay hard-linked in the copy. |
security | The security.peios.* extended attributes (other than the descriptor itself). |
xattrs | All other extended attributes. |
You rarely name those individually. These options select sensible sets:
| Option | Preserves |
|---|---|
-p | Timestamps only. |
--preserve | Timestamps only — the same as -p. |
--preserve=LIST | Exactly the comma-separated attributes you name. |
--preserve-all | Every attribute in the table above. |
--sd | owner, dacl, sacl — the full security descriptor, copied verbatim. |
--sd-explicit | owner, daclni, saclni — the source's explicitly set rules only. |
--no-preserve=LIST | Turns the named attributes back off — useful to subtract from a broader option. |
--sd versus --sd-explicit #
Both carry the source's security across; they differ in how they treat inherited rules.
--sdcopies the descriptor exactly — inherited rules and all. The copy ends up with precisely the access rules the source had, even if it lands in a directory that would have handed down something different. Use it when the copy must be a security-exact duplicate.--sd-explicitcopies only the rules that were set explicitly on the source, and lets the destination directory supply inheritance for the rest. Use it when copying a file into a different directory and you want the file's own tailored rules to come along while still picking up the new location's inherited rules.
For what "inherited" versus "explicit" means, see Inheritance.
When a preserve cannot be honoured #
A requested preserve is a firm instruction. If cp cannot carry an attribute across — for example, writing the copy's SACL requires the SeSecurityPrivilege privilege, and the caller does not have it — cp treats that as a hard error and the copy fails. It does not quietly produce a copy that is missing what you asked to preserve.
There is one exception, and it is a matter of definition rather than a softening of the rule. security and xattrs name extended attributes, and some filesystems have no extended attributes at all — FAT, 9p, and a squashfs image built without an attribute table are the ones you will meet. Copying from such a filesystem carries nothing, because there was nothing there: the preserve is satisfied vacuously, and the copy succeeds.
That is narrow on purpose. It applies only to reading the source. If the source does have extended attributes and the destination filesystem cannot hold them, the preserve genuinely failed and cp still stops:
# cp -a /home/jack/notes /mnt/usb-fat/
cp: failed to set extended attributes on '/mnt/usb-fat/notes': Operation not supported
The distinction matters most for -a, which requests both attribute classes on every file it touches. Without it, one xattr-less filesystem anywhere in a tree would abort the whole copy partway through.
Overwriting an existing destination #
By default, if the destination already exists, cp overwrites it. These options change that.
| Option | Effect |
|---|---|
-i, --interactive | Ask before overwriting an existing file. |
-n, --no-clobber | Never overwrite an existing file; skip it silently. |
-u, --update[=WHICH] | Overwrite only when the source is newer than the destination. WHICH can be all, none, or older. |
-f, --force | If an existing destination cannot be opened for writing, remove it and try the copy again. |
--remove-destination | Remove the destination before opening it, always — contrast --force, which only removes on failure. |
-b, --backup[=CONTROL] | Before overwriting, make a backup of the destination. |
Symbolic links in the source #
When a source is a symbolic link, cp can copy the link itself or the file it points to.
| Option | Effect |
|---|---|
-L, --dereference | Always follow symbolic links — copy the file they point to. |
-P, --no-dereference | Never follow symbolic links — copy the link itself. |
-H | Follow only the symbolic links named directly on the command line, not links found while recursing. |
-d | Copy links as links, and preserve hard-link structure. Short for --no-dereference --preserve=links. |
Other ways to copy #
cp can also produce something other than a plain duplicate.
| Option | Effect |
|---|---|
-l, --link | Create a hard link to the source instead of copying its data. |
-s, --symbolic-link | Create a symbolic link to the source instead of copying. |
--reflink[=WHEN] | Make a lightweight copy-on-write clone where the file system supports it. WHEN is always, auto, or never. |
--sparse[=WHEN] | Control whether runs of zero bytes are stored as holes rather than written out. WHEN is always, auto, or never. |
--attributes-only | Create the destination and copy its attributes, but not its data. |
Other options #
| Option | Effect |
|---|---|
-t, --target-directory=DIR | Copy every source into DIR. Useful when the directory is not the last argument. |
-T, --no-target-directory | Treat dest as a plain file even if it is a directory. |
--parents | Recreate the source's leading directories under the destination. |
-x, --one-file-system | Do not cross into a different file system while recursing. |
--strip-trailing-slashes | Strip any trailing slashes from each source argument before using it. |
-v, --verbose | Print each file as it is copied. |
--debug | Explain in detail how each file was copied. Implies -v. |
--progress | Show a progress bar. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was copied successfully. |
1 | A file could not be copied — including a requested --preserve that could not be honoured. |
mv
Peios / Using Peios / Peiosutils / Files and directories
mv moves files and directories from one place to another. Moving a file within the same directory, under a new name, is how you rename it.
mv [options] source dest
mv [options] source... directory
mv [options] -t directory source...
$ mv draft.txt report.txt # rename
$ mv report.txt notes.txt /home/jack/done/ # move into a directory
A move is either a rename or a copy-and-delete #
mv does its job in one of two ways, and which one decides what happens to the file's security.
Within one file system, a move is a rename. Nothing is copied: the file stays exactly where it physically is, and only its name and directory entry change. The file keeps its inode, and so it keeps its security descriptor unchanged — every owner, access rule, audit rule, and timestamp is exactly as before. A rename does not re-evaluate inheritance; a file does not pick up new rules just because it landed in a new directory.
Across file systems, a move is a copy followed by a delete. When the destination is on a different file system, mv cannot just rename — it copies the data to a new file on the destination, then removes the original. That new file is a genuinely new object, so, exactly as with cp, it gets a fresh security descriptor inherited from the destination directory unless you ask otherwise.
So: rename a file and its security is untouched; move it to another file system and the copy starts fresh.
Preserving security on a cross-file-system move #
For the copy-and-delete case, mv carries the same --preserve family that cp does — an identical set of options, behaving the same way. They let a cross-file-system move carry the source's owner, access rules, timestamps, and other attributes onto the new file instead of taking the destination directory's inherited default.
The full --preserve reference — the attribute list, --sd, --sd-explicit, --preserve-all, --no-preserve, and what happens when a preserve cannot be honoured — is on the cp page. Everything there applies to mv unchanged.
On a same-file-system move the --preserve options have nothing to do: a rename already keeps every attribute, because it is the same file.
Overwriting an existing destination #
By default mv overwrites an existing destination. These options change that. When more than one of -i, -f, -n is given, the last one wins.
| Option | Effect |
|---|---|
-i, --interactive | Ask before overwriting an existing file. |
-f, --force | Do not prompt before overwriting. |
-n, --no-clobber | Never overwrite an existing file. |
-u, --update[=WHICH] | Overwrite only when the source is newer. WHICH can be all, none, or older. |
-b, --backup[=CONTROL] | Make a backup of the destination before overwriting it. |
Other options #
| Option | Effect |
|---|---|
-t, --target-directory=DIR | Move every source into DIR. |
-T, --no-target-directory | Treat dest as a plain file even if it is a directory. |
--strip-trailing-slashes | Strip any trailing slashes from each source argument. |
-v, --verbose | Print each file as it is moved. |
--debug | Explain in detail how each file was moved. Implies -v. |
--progress | Show a progress bar. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was moved successfully. |
1 | A file could not be moved. |
rm
Peios / Using Peios / Peiosutils / Files and directories
rm removes files. With -r, it removes directories and everything inside them.
rm [options] file...
$ rm draft.txt old-notes.txt
$ rm -r build/
Removal is not reversible — once rm removes a file, its directory entry is gone. rm has a few safety behaviours, below, but the basic rule stands: think before you confirm.
Removing directories #
rm will not remove a directory unless you say so:
| Option | Effect |
|---|---|
-r, -R, --recursive | Remove directories and all of their contents, recursively. |
-d, --dir | Remove a directory, but only if it is already empty. |
The write-protected prompt #
By default — when you have not passed -f and rm is running interactively — rm prompts before removing a file you cannot write to:
$ rm policy.db
rm: remove write-protected regular file 'policy.db'?
The point of that prompt is to catch mistakes: a file you cannot write is one you most likely did not mean to delete.
"Write-protected" here means a real access check. rm decides it by asking the kernel whether your token may write the file — a live check against the file's security descriptor. It is not read off any decorative metadata on the inode; it is the same authority question that any write to the file would face. So the prompt is honest: if rm says a file is write-protected, it is a file the access model would genuinely stop you writing.
(The check is about write access to the file's contents. Whether you may actually remove the file is a separate question, decided by its own access right — so you can be prompted about a write-protected file and still be allowed to delete it. The prompt is a courtesy check, not the authorisation.)
Controlling the prompts #
| Option | Effect |
|---|---|
-f, --force | Never prompt. Also ignore files that do not exist instead of reporting them. |
-i | Prompt before every removal. |
-I | Prompt just once before removing more than three files or before a recursive removal. Less intrusive than -i, but still a check against a slip. |
--interactive[=WHEN] | Set the prompting explicitly: never, once, or always. |
The root failsafe #
rm refuses to recursively remove / — the root of the file system — because doing so by accident would be catastrophic.
| Option | Effect |
|---|---|
--preserve-root | Refuse to recurse on /. This is the default; you do not need to ask for it. |
--no-preserve-root | Disable the failsafe. Required, in full and unabbreviated, if you genuinely intend a recursive operation on /. |
Other options #
| Option | Effect |
|---|---|
-v, --verbose | Print each file as it is removed. |
--progress | Show a progress bar. |
rm and shred #
rm unlinks a file — it removes the name, and the space becomes free. The file's data may still be physically present on the device until something else overwrites it. When you need the contents to be genuinely unrecoverable, use shred, which overwrites the data before removing the file.
Exit status #
| Code | Meaning |
|---|---|
0 | Every requested removal succeeded (or, with -f, the file did not exist). |
1 | A file could not be removed. |
rmdir
Peios / Using Peios / Peiosutils / Files and directories
rmdir removes directories — but only empty ones. If a directory still has anything in it, rmdir leaves it alone and reports the failure.
rmdir [options] directory...
$ rmdir old-cache/
That refusal to touch a non-empty directory is the whole point of rmdir: it is the safe way to remove a directory you believe is empty. If it is not empty, you find out instead of losing the contents. To remove a directory together with everything inside it, use rm -r.
Options #
| Option | Effect |
|---|---|
-p, --parents | Remove the directory and then its ancestors, as long as each becomes empty in turn. rmdir -p a/b/c removes a/b/c, then a/b, then a. |
--ignore-fail-on-non-empty | Do not treat "directory not empty" as a failure. Other failures are still reported. Useful when clearing out whatever directories happen to be empty and leaving the rest. |
-v, --verbose | Print a line for each directory as it is removed. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every directory was removed. |
1 | A directory could not be removed — it was not empty, did not exist, or removal was refused. |
mkdir
Peios / Using Peios / Peiosutils / Files and directories
mkdir creates directories.
mkdir [options] directory...
$ mkdir reports
$ mkdir -p projects/2026/q2
Creating parent directories #
By default mkdir creates exactly the directories you name, and fails if a parent is missing. -p removes that restriction:
| Option | Effect |
|---|---|
-p, --parents | Create any missing parent directories along the way. Also makes mkdir succeed quietly if the directory already exists. |
-v, --verbose | Print a line for each directory as it is created. |
The creation flags #
A new directory needs a security descriptor — the record of who owns it and who may do what to it. By default, a new directory inherits its descriptor from the directory it is created in: it picks up the access rules its parent hands down. For most directories that is exactly right, and you pass no extra options at all.
When you need the new directory to have a different descriptor, the creation flags set it explicitly. These five flags are shared, unchanged, by mkdir, mkfifo, mknod, and touch — whenever one of those commands creates a new object, it accepts this same set. This page is the full reference for them.
| Flag | Argument | Effect |
|---|---|---|
--owner | SID | Make this principal the owner, instead of you. |
--group | SID | Set the object's group. |
--label | level | Give the object a mandatory integrity label. |
--no-inherit | — | Do not inherit rules from the parent directory; lock the object to its owner. |
--sddl | SDDL string | Supply the entire security descriptor directly. |
A SID argument is either a literal S-1-… value or a well-known alias — BA for the built-in administrators, for example.
--label takes one of these levels, lowest to highest: untrusted, low, medium, medium-plus, high, system, protected. The label applies the standard rule for files and directories — a principal below the object's level cannot write to it.
Inherited, or explicit #
The default and --no-inherit are the two ends of a choice.
- Inherited (the default) — the new directory tracks its parent. Rules the parent hands down apply to it, and as the parent's rules change the directory follows along. This keeps a tree of directories consistent without per-directory effort.
--no-inherit— the new directory takes nothing from its parent. Its access is locked to its owner, and it does not follow the parent's rules. Use it to carve out a directory whose security stands apart from the tree around it.
See Inheritance for the full model.
--sddl for a complete descriptor #
The four flags above are shortcuts for common adjustments. When you need to specify the whole descriptor — a particular set of access rules, audit rules, owner, and group all at once — --sddl takes it as a single string in the security-descriptor definition language:
$ mkdir --sddl 'O:BAG:BAD:(A;OICI;FA;;;BA)' secure-area
--sddl is mutually exclusive with --owner, --group, --label, and --no-inherit — it already says everything those flags would say, so combining them is rejected.
How the descriptor is applied #
mkdir creates the directory first and then applies the descriptor to it. The two steps are not separately visible — mkdir either finishes with the directory created and secured as asked, or fails as a whole. If applying the descriptor fails, the half-created directory is not left behind.
Exit status #
| Code | Meaning |
|---|---|
0 | Every directory was created. |
1 | A directory could not be created, or a creation flag could not be applied. |
mkfifo
Peios / Using Peios / Peiosutils / Files and directories
mkfifo creates named pipes, also called FIFOs.
mkfifo [options] name...
$ mkfifo events
A named pipe is a file that two programs use to pass a stream of data: one writes into it, the other reads from it, and the data flows through in order — first in, first out, which is what "FIFO" stands for. Unlike an ordinary pipe between two commands, a named pipe has a name on the file system, so the two programs do not have to be started together or be related to each other.
Setting the new pipe's security #
A FIFO is a file, and a new file needs a security descriptor. By default a new FIFO inherits its descriptor from the directory it is created in.
mkfifo accepts the shared creation flags — --owner, --group, --label, --no-inherit, and --sddl — to set that descriptor explicitly instead. They behave exactly as they do for mkdir; the full reference is on the mkdir page.
Exit status #
| Code | Meaning |
|---|---|
0 | Every FIFO was created. |
1 | A FIFO could not be created — for example, the name already exists. |
mknod
Peios / Using Peios / Peiosutils / Files and directories
mknod creates special files — files that are not data but an interface to something else: a device, or a named pipe.
mknod [options] name type [major minor]
$ mknod /dev/mydisk b 8 0
$ mknod backlog p
The type argument #
The type says what kind of special file to create:
| Type | Creates |
|---|---|
b | A block device — a device addressed in fixed-size blocks, such as a disk. |
c or u | A character device — a device addressed as a stream of bytes, such as a terminal. |
p | A named pipe (FIFO). |
For a block or character device (b, c, u) you must also give a major and a minor number. The major number selects which driver handles the device; the minor number tells that driver which specific device is meant. For a pipe (p), the major and minor numbers are omitted — a pipe has no driver behind it.
A major or minor number is read as hexadecimal if it begins with 0x, as octal if it begins with 0, and as decimal otherwise.
mknod NAME p and mkfifo NAME do the same thing; mkfifo exists as the clearer way to ask for just a pipe.
Setting the new file's security #
A special file needs a security descriptor like any other file, and by default a new one inherits its descriptor from the directory it is created in.
mknod accepts the shared creation flags — --owner, --group, --label, --no-inherit, and --sddl — to set that descriptor explicitly. They behave exactly as they do for mkdir; the full reference is on the mkdir page.
Exit status #
| Code | Meaning |
|---|---|
0 | The special file was created. |
1 | It could not be created — a missing or invalid type, missing device numbers, or a name that already exists. |
touch
Peios / Using Peios / Peiosutils / Files and directories
touch does one of two things, depending on whether the file already exists:
- if the file does not exist,
touchcreates it, empty; - if the file does exist,
touchupdates its timestamps to the current time.
touch [options] [file...]
$ touch notes.txt # create notes.txt if absent, else bump its times
The "create an empty file" behaviour is the everyday use. The "update timestamps" behaviour matters to tools that decide what work to do by comparing file times — touch is how you tell such a tool that a file should be considered freshly changed.
Which timestamps, and to what #
By default touch sets both the access time and the modification time to the current moment. These options narrow or redirect that.
| Option | Effect |
|---|---|
-a | Change only the access time. |
-m | Change only the modification time. |
--time=WORD | Choose which time to change by name: access, atime, or use for the access time; modify or mtime for the modification time. |
-d, --date=STRING | Use the time described by STRING instead of the current time. |
-t STAMP | Use the time given as [[CC]YY]MMDDhhmm[.ss] instead of the current time. |
-r, --reference=FILE | Use the timestamps of FILE instead of the current time. |
-h, --no-dereference | If the named file is a symbolic link, change the link's own timestamps rather than those of the file it points to. |
Not creating a file #
To use touch purely to update timestamps, and never to create anything:
| Option | Effect |
|---|---|
-c, --no-create | Do not create a file that does not exist. A named file that is absent is silently skipped. |
Setting a created file's security #
When touch creates a file, that new file needs a security descriptor, and by default it inherits one from the directory it is created in.
touch accepts the shared creation flags — --owner, --group, --label, --no-inherit, and --sddl — to set that descriptor explicitly. They behave exactly as they do for mkdir; the full reference is on the mkdir page.
These flags apply only on the create path. If touch is updating the timestamps of a file that already exists, there is no new descriptor to set, and the creation flags have nothing to do — they do not alter an existing file's security. Pair them with -c and they never take effect at all, since -c means nothing is ever created.
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was created or updated as requested. |
1 | A file could not be created or updated. |
ln
Peios / Using Peios / Peiosutils / Files and directories
ln creates links — extra ways to reach a file.
ln [options] target link_name
ln [options] target
ln [options] target... directory
ln [options] -t directory target...
$ ln -s /opt/app/bin/app /lcl/bin/app # a symbolic link
$ ln data.csv data-archive.csv # a hard link
Hard links and symbolic links #
There are two kinds of link, and they are genuinely different things.
A hard link is another name for the same file. The file's data exists once; a hard link is an additional directory entry pointing at it. Every hard link to a file is equal — none is "the original" — and the file's data stays as long as at least one hard link remains. Hard links cannot span file systems, and cannot be made to directories.
A symbolic link is a small file that simply contains a path. When something opens a symbolic link, it is redirected to whatever that path names. The symbolic link and its target are separate files: if the target is moved or removed, the link still exists but now points at nothing — it "dangles". Symbolic links can point anywhere, across file systems and at directories.
ln creates hard links by default, and symbolic links with -s. When in doubt, -s — a symbolic link's behaviour is the easier one to reason about.
The two also differ in how they are secured. A hard link is not a new file, so creating one involves no new security descriptor — every name for the file shares the file's existing one. A symbolic link is a new file, and like any new file it inherits its own descriptor from the directory it is created in.
The forms #
ln target link_name— create one link namedlink_name.ln target— create a link totargetin the current directory, with the same final name.ln target... directory— create a link to eachtargetinsidedirectory.ln -t directory target...— the same, with the directory named first.
Options #
| Option | Effect |
|---|---|
-s, --symbolic | Make symbolic links instead of hard links. |
-f, --force | Remove an existing destination file so the link can be created. |
-i, --interactive | Ask before removing an existing destination. |
-r, --relative | For a symbolic link, write the target as a path relative to the link's own location, rather than an absolute path. |
-n, --no-dereference | If link_name is itself a symbolic link to a directory, treat it as an ordinary file — replace the link rather than create something inside the directory it points to. |
-L, --logical | When the target is a symbolic link, link to what it points to. |
-P, --physical | Make a hard link to a symbolic link itself, rather than to its target. |
-t, --target-directory=DIR | Create all links inside DIR. |
-T, --no-target-directory | Treat link_name as a plain file, never as a directory to create links inside. |
-b, --backup[=CONTROL] | Back up an existing destination before replacing it. |
-S, --suffix=SUFFIX | Use SUFFIX for backup file names. |
-v, --verbose | Print the name of each link as it is created. |
Inspecting links afterwards #
readlink prints where a symbolic link points. The low-level link command makes a single hard link with no options, for scripts that want exactly that and nothing else.
Exit status #
| Code | Meaning |
|---|---|
0 | Every link was created. |
1 | A link could not be created. |
link and unlink
Peios / Using Peios / Peiosutils / Files and directories
link and unlink are the bare, single-purpose versions of two operations that ln and rm also perform. Each does exactly one thing, takes exactly its operands, and has no options. They exist for scripts that want one precise operation with no surrounding behaviour — no prompting, no link kinds to choose, no recursion.
link #
link file1 file2
link creates one hard link: a new name, file2, for the existing file file1. file1 must exist; file2 must not.
$ link data.csv data-archive.csv
That is the whole command. It performs the link operation directly and reports whether it succeeded. For anything more — symbolic links, replacing an existing destination, linking into a directory — use ln.
unlink #
unlink file
unlink removes one file: it deletes the directory entry file. It takes a single name and removes it directly.
$ unlink stale.lock
unlink does not prompt, does not recurse, and does not remove directories. For removing several files, removing directories, or any of the safety prompts, use rm.
Why they exist #
ln and rm are the commands to use day to day — they are flexible and have the safety behaviours. link and unlink are deliberately rigid: a script that calls unlink will only ever remove a single file, and a reviewer can see that at a glance. The narrowness is the feature.
Exit status #
Both commands:
| Code | Meaning |
|---|---|
0 | The operation succeeded. |
1 | The operation failed — a missing or already-existing operand, or a refused request. |
shred
Peios / Using Peios / Peiosutils / Files and directories
shred destroys a file's contents. It does not just unlink the file — it overwrites the data, repeatedly, so that what was there cannot be read back even with effort and specialised equipment.
shred [options] file...
$ shred -u -v secret-keys.txt
rm removes a file's name; the data may sit on the device, recoverable, until something else happens to overwrite it. shred overwrites the data on purpose. Use it when a file held something that must not be recoverable.
By default shred overwrites a file three times and then leaves the file in place — empty of its old contents but still present. That default exists because shred is often pointed at device files, which usually should not be removed. To remove the file after shredding it, pass -u.
When shredding actually works #
shred relies on one assumption: that writing to the file overwrites the same physical storage the old data occupied. On a traditional file system that holds. Several common file system designs break it — anything that writes new data to a fresh location instead of in place, keeps snapshots, journals file data, caches copies elsewhere, or compresses. On those, shred cannot guarantee the old data is gone.
Backups and remote mirrors are a separate matter entirely: shred cannot reach a copy of the file that lives somewhere else.
Treat shred as effective on a plain local file system and uncertain otherwise.
Options #
| Option | Effect |
|---|---|
-n, --iterations=N | Overwrite N times instead of the default of 3. |
-u, --remove[=HOW] | Remove the file after overwriting it. HOW controls how thoroughly the name itself is obscured before the file is unlinked. |
-z, --zero | Add a final pass that writes zeros, so the file does not visibly look shredded afterwards. |
-s, --size=N | Shred N bytes, rather than the whole file. Accepts size suffixes such as K, M, G. |
-x, --exact | Do not round the shredded size up to the next full block. Without it, shred rounds up so the block's slack space is overwritten too. |
--random-source=FILE | Take the random bytes for the overwrite passes from FILE. |
-v, --verbose | Show progress as each pass runs. |
-f, --force | Override a file's protection: when a live access check shows you cannot write the file, rewrite its security descriptor so the overwrite can proceed (see below). |
--force and a file's protection #
To overwrite a file, shred has to be able to write it. If the file's security descriptor does not grant you write access, an ordinary shred cannot proceed.
-f (--force) handles that case. When --force is given and a live access check shows you cannot currently write the file, shred rewrites the file's security descriptor to one that grants full access, and then overwrites the file. The reasoning is deliberate: the file is about to be destroyed anyway, and --force is you saying "override this file's protection."
--force can do this only when you are allowed to change the file's security in the first place — that is, when you own the file or hold the right to rewrite its access rules. If you cannot change the descriptor, --force cannot grant itself write access, and the shred still fails. --force overrides a file's protection; it does not manufacture authority you do not have.
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was shredded successfully. |
1 | A file could not be shredded. |
dd
Peios / Using Peios / Peiosutils / Files and directories
dd copies data from one place to another, a block at a time, and can transform the data as it goes. It is the tool for low-level copying — writing a disk image to a device, extracting an exact byte range out of a file, copying from or to a raw device.
dd [operand]...
$ dd if=disk.img of=/dev/mydisk bs=4M status=progress
How dd is told what to do #
dd does not take options in the usual -x form. It takes operands, each written name=value, in any order. With no if= operand it reads from standard input; with no of= operand it writes to standard output.
The operands #
| Operand | Meaning |
|---|---|
if=FILE | Read input from FILE. |
of=FILE | Write output to FILE. |
bs=BYTES | Read and write BYTES at a time. Sets both the input and output block size at once. |
ibs=BYTES | Input block size (default 512). |
obs=BYTES | Output block size (default 512). |
cbs=BYTES | Conversion block size — used by the block and unblock conversions. |
count=N | Stop after N input blocks, rather than reading to the end. |
skip=N | Skip N input blocks before copying. Also spelled iseek=N. |
seek=N | Skip N output blocks before writing. Also spelled oseek=N. |
conv=LIST | Apply the comma-separated conversions below. |
iflag=LIST | Comma-separated input flags — how the input is opened and read. |
oflag=LIST | Comma-separated output flags — how the output is opened and written. |
status=LEVEL | How much to report: progress (periodic stats while copying), noxfer (final counts only), none (silent). |
A size value accepts a unit suffix (K, M, G, …). So bs=4M is four mebibytes per block.
Conversions (conv=) #
| Value | Effect |
|---|---|
ucase / lcase | Convert the data to upper-case / lower-case. |
swab | Swap every adjacent pair of bytes. |
sync | Pad each input block out to its full size — with zeros, or with spaces alongside block/unblock. |
block / unblock | Convert between newline-terminated lines and fixed-size cbs records. |
sparse | Where an output block is all zeros, seek past it instead of writing it. |
excl | Fail if the output file already exists. |
nocreat | Fail if the output file does not already exist. |
notrunc | Do not truncate the output file when opening it. |
noerror | Continue past read errors instead of stopping. |
fdatasync / fsync | Flush the data — or the data and metadata — to storage before finishing. |
When dd creates the output file — an of= file that does not already exist — the new file needs a security descriptor, and it inherits one from the directory it is created in.
Input and output flags (iflag=, oflag=) #
| Flag | Applies to | Effect |
|---|---|---|
count_bytes | input | Interpret count=N as a number of bytes, not blocks. |
skip_bytes | input | Interpret skip=N as a number of bytes. |
fullblock | input | Wait for a full ibs of data on each read. |
seek_bytes | output | Interpret seek=N as a number of bytes. |
append | output | Open the output in append mode. |
direct | both | Use direct I/O, bypassing the cache. |
dsync / sync | both | Use synchronised I/O — for data, or for data and metadata. |
nonblock | both | Use non-blocking I/O. |
noatime | both | Do not update the file's access time. |
nocache | both | Ask the system to drop the file's cached pages. |
directory | both | Fail unless the file is a directory. |
What dd prints #
Unless status=none is set, dd prints a summary when it finishes:
16+0 records in
16+0 records out
67108864 bytes (67 MB, 64 MiB) copied, 1.234 s, 54.4 MB/s
The records in / records out counts are written complete+partial — full-sized blocks plus any short final block. status=progress prints the last line periodically during a long copy.
Exit status #
| Code | Meaning |
|---|---|
0 | The copy completed. |
1 | The copy failed — a bad operand, an I/O error, or a file that could not be opened. |
df
Peios / Using Peios / Peiosutils / Files and directories
df — "disk free" — reports, for each file system, how much space it has and how much is still available.
df [options] [file...]
With no arguments, df reports on every mounted file system. Given a file, it reports on the one file system that file lives on.
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 456G 198G 235G 46% /
/dev/sda1 511M 12M 499M 3% /boot
The default columns #
Each line is one file system:
- Filesystem — the device or source backing it.
- Size — its total capacity.
- Used — space in use.
- Avail — space still available.
- Use% — used space as a percentage.
- Mounted on — where it is attached in the directory tree.
Choosing the units #
By default sizes are shown in blocks. These options change that.
| Option | Effect |
|---|---|
-h, --human-readable | Show sizes with unit suffixes — 456G, 12M — using powers of 1024. |
-H, --si | Likewise, but using powers of 1000. |
-B, --block-size=SIZE | Show sizes scaled to SIZE — -BM reports in megabytes. |
-k | Use 1024-byte blocks. |
Choosing what to report #
| Option | Effect |
|---|---|
-a, --all | Include dummy and otherwise-hidden file systems that df would normally skip. |
-l, --local | Report only local file systems, skipping network-mounted ones. |
-t, --type=TYPE | Report only file systems of type TYPE. |
-x, --exclude-type=TYPE | Report everything except file systems of type TYPE. |
--total | Add a final line giving the grand total across everything reported. |
Changing the output #
| Option | Effect |
|---|---|
-i, --inodes | Report inode counts — total, used, and free — instead of block space. A file system can run out of inodes while it still has space. |
-T, --print-type | Add a column showing each file system's type. |
--output[=LIST] | Replace the default columns with exactly the fields named in LIST. With no list, print every available field. |
-P, --portability | Use a fixed, simple column layout that does not wrap a long device name onto its own line. |
Sync behaviour #
| Option | Effect |
|---|---|
--sync | Flush pending writes before reading the usage figures, for the most up-to-date numbers. |
--no-sync | Do not flush first. This is the default and is faster. |
Exit status #
| Code | Meaning |
|---|---|
0 | The report was produced. |
1 | A failure — for example, a named file does not exist, or the table of file systems could not be read. |
du
Peios / Using Peios / Peiosutils / Files and directories
du — "disk usage" — reports how much space files and directories occupy. Where df tells you about a whole file system, du tells you where the space has actually gone.
du [options] [file...]
$ du -sh /home/jack/projects
4.2G /home/jack/projects
With no arguments, du works on the current directory. By default it walks the directory tree, prints the cumulative size of each directory (a directory's size includes everything under it), and ends with the total for the argument itself. Individual files are not listed unless you ask for them.
What to count and how deep #
| Option | Effect |
|---|---|
-a, --all | List every file, not only directories. |
-s, --summarize | Print only a single total for each argument — no per-subdirectory breakdown. |
-d, --max-depth=N | Show totals only down to N levels below each argument. -d 0 is the same as -s. |
-S, --separate-dirs | Give each directory's own size, excluding its subdirectories. |
-c, --total | Add a final grand-total line. |
--inodes | Count inodes used rather than space used. |
What "size" means #
| Option | Effect |
|---|---|
-h, --human-readable | Sizes with unit suffixes — 4.2G — using powers of 1024. |
--si | Likewise, using powers of 1000. |
-B, --block-size=SIZE | Scale sizes to SIZE. |
-k / -m | Use 1024-byte / 1024×1024-byte units. |
--apparent-size | Report the apparent size of files — how much data they contain — rather than the disk space they occupy. The two differ for sparse files and because of block rounding. |
-b, --bytes | Report plain byte counts. Equivalent to --apparent-size --block-size=1. |
Following symbolic links #
By default du does not follow symbolic links.
| Option | Effect |
|---|---|
-L, --dereference | Follow all symbolic links and count what they point to. |
-D, -H, --dereference-args | Follow only the symbolic links named directly on the command line. |
-P, --no-dereference | Follow no symbolic links. This is the default. |
-x, --one-file-system | Do not cross into a different file system while walking. |
-l, --count-links | Count a hard-linked file every time it is encountered, instead of once. |
Limiting the output #
| Option | Effect |
|---|---|
--exclude=PATTERN | Skip files and directories whose name matches PATTERN. |
--exclude-from=FILE | Skip everything matching any pattern listed in FILE. |
-t, --threshold=SIZE | Show only entries at least SIZE (or, with a negative SIZE, at most that big). |
--files0-from=F | Take the list of files to measure from F, NUL-separated. - means standard input. |
--time[=WORD] | Also show a timestamp — by default the latest modification time anywhere in the directory. |
-0, --null | End each output line with a NUL character instead of a newline. |
-v, --verbose | Report extra detail about what is being processed. |
Exit status #
| Code | Meaning |
|---|---|
0 | The report was produced. |
1 | A file or directory could not be accessed. |
truncate
Peios / Using Peios / Peiosutils / Files and directories
truncate sets a file's size to an exact figure — shrinking it, or extending it.
truncate [options] file...
$ truncate -s 0 logfile # empty the file
$ truncate -s 1G disk.img # make a 1 GiB file
Shrinking a file discards everything past the new size. Extending it adds space that reads back as zero bytes; that added space is a hole — it costs no actual storage until something writes real data into it, which is how truncate -s 1G can create a "1 GiB file" instantly.
By default, truncate creates a file that does not yet exist (as an empty file, then sized as asked). A file created this way needs a security descriptor, and it inherits one from the directory it is created in.
Specifying the size #
-s (--size) takes the size. A plain number sets the size outright. A unit suffix scales it: K, M, G, T, … where the bare letter is a power of 1024 (K = 1024) and the letter with B is a power of 1000 (KB = 1000).
A size may also begin with a prefix, which makes it relative to the file's current size:
| Prefix | Meaning |
|---|---|
+ | Extend by this much. |
- | Reduce by this much. |
< | Shrink to this size only if the file is currently larger ("at most"). |
> | Extend to this size only if the file is currently smaller ("at least"). |
/ | Round the size down to a multiple of this number. |
% | Round the size up to a multiple of this number. |
$ truncate -s +4K notes.txt # 4 KiB larger than it is now
$ truncate -s '<1M' notes.txt # cap it at 1 MiB, leave smaller files alone
Options #
| Option | Effect |
|---|---|
-s, --size=SIZE | Set or adjust the size to SIZE, as described above. |
-r, --reference=RFILE | Base the size on the current size of RFILE instead of giving a number. Combine with a relative -s to mean "the size of RFILE, adjusted". |
-c, --no-create | Do not create a file that does not exist; skip it instead. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was resized. |
1 | A file could not be resized — a bad size, or a file that could not be opened. |
mktemp
Peios / Using Peios / Peiosutils / Files and directories
mktemp creates a temporary file — or, with -d, a temporary directory — gives it a name that is not already taken, and prints that name.
mktemp [options] [template]
$ mktemp
/tmp/tmp.QV8x3kZ1aR
$ mktemp -d
/tmp/tmp.7bNc0wPe2L
The reason to use mktemp rather than inventing a name yourself is safety. A script that picks a fixed name like /tmp/work has two problems: two copies of the script collide, and a name an attacker can predict is a name an attacker can interfere with before the script gets to it. mktemp chooses an unpredictable name and creates the file in the same step, so nothing can slip in between. It prints the name it used, for the script to capture:
work=$(mktemp)
# ...use "$work"...
rm "$work"
The temporary file or directory mktemp creates is a new object, and a new object needs a security descriptor; it inherits one from the directory it is created in.
Templates #
The name comes from a template — a name with a run of trailing X characters, each of which mktemp replaces with a random character. More Xs means a wider range of possible names.
$ mktemp report-XXXXXX.txt
report-a8Kp2Q.txt
With no template, mktemp uses a built-in default that places the file in the temporary directory. A template needs at least three Xs.
Options #
| Option | Effect |
|---|---|
-d, --directory | Create a directory instead of a file. |
-p, --tmpdir[=DIR] | Create the temporary object inside DIR. With no DIR, use the directory named by the TMPDIR environment variable, or the system temporary directory. The template is then just the final name component. |
--suffix=SUFFIX | Append SUFFIX after the random part of the name — for giving the file an extension. |
-u, --dry-run | Do not create anything; just print a name that would be free. This reintroduces the race the command exists to avoid — use it only when you genuinely cannot create the object yet. |
-q, --quiet | Print no error message if creation fails; rely on the exit status. |
Exit status #
| Code | Meaning |
|---|---|
0 | The temporary file or directory was created; its name was printed. |
1 | Creation failed — a bad template, or a directory that does not exist. |
sd
Peios / Using Peios / Peiosutils / Files and directories
sd is the command-line tool for working with security descriptors on files. Everything this topic describes — owners, DACLs, ACEs, the SACL, integrity labels, inheritance — sd is how you read it and change it from a shell.
sd subcommand path [arguments]
$ sd show ./report.txt
$ sd allow ./report.txt alice:read
$ sd owner ./report.txt BA
Where ls -l shows a file's owner and a summary, and cp --preserve carries a descriptor across, sd is the tool that edits the descriptor directly.
The subcommands #
| Group | Subcommand | Does |
|---|---|---|
| Inspect | show | Print the descriptor on a path. |
check | Simulate an access check against the path. | |
| DACL | allow | Add an allow rule for one or more principals. |
deny | Add a deny rule for one or more principals. | |
remove | Drop every DACL rule for the named principals. | |
| Auditing | audit | Add an audit rule to the SACL. |
unaudit | Drop every SACL rule for the named principals. | |
| Ownership | owner | Set the descriptor's owner. |
group | Set the descriptor's group. | |
| Integrity | integrity | Set the mandatory integrity label. |
| Inheritance | inherit | Turn inheritance protection on or off. |
reset | Drop the file's own rules and re-inherit from the parent. | |
propagate | Push inheritance down to descendants. | |
| Wholesale | set | Replace the entire descriptor at once. |
Naming a principal #
Wherever a subcommand takes a PRINCIPAL, it accepts any of:
| Form | Example | Meaning |
|---|---|---|
@self | @self | The user SID of the token running sd. |
@owner | @owner | A placeholder that the access check substitutes with the file's own owner. |
| A well-known label | Everyone, Administrators, LocalSystem | A named built-in principal. |
| A two-letter alias | WD, BA, SY | The short alias for a well-known principal. |
| A raw SID | S-1-5-32-544 | Any SID, written out in full. |
See SIDs for what these are.
Naming permissions #
Wherever a subcommand takes PERMS, several notations are accepted, and may be mixed:
| Form | Example | Meaning |
|---|---|---|
| Single letters | rwx, r, m | r read, w write, x execute, d delete, m modify, f full, c change-permissions, o take-ownership. |
| Words | read,write, modify | The same set, spelled out. |
| Fine-grained names | read-data,append,traverse | Individual low-level rights, for precise rules. |
| Raw hex | 0x1F01FF | An access mask written directly. |
Run letters together (rwx) or separate names with commas (read,write,execute).
Inspecting #
sd show #
Prints the descriptor on a path — the owner, the group, the DACL, the SACL.
$ sd show ./report.txt
| Flag | Effect |
|---|---|
--sddl | Render the descriptor as an SDDL string. |
--raw | Render SIDs in raw S-1-… form only. |
--label | Render SIDs as their labels where known. |
--all | Verbose — decode every flag and show raw masks alongside. |
--json | Emit JSON. |
sd check #
Simulates an access decision: "would this access be allowed?" — without performing it.
$ sd check ./report.txt write
$ sd check ./report.txt read --pid 4821 --explain
| Argument / flag | Effect |
|---|---|
PERMS | The access to test for. |
--pid PID | Check against process PID's token instead of your own. |
--explain | Show why the decision came out as it did — the rule-by-rule walk. |
sd check is the first thing to reach for when an access is denied and you do not know why. --explain walks the descriptor the same way the kernel does.
Changing the DACL #
The DACL is the list of allow and deny rules. These three subcommands edit it.
sd allow and sd deny #
Add an allow (or deny) rule for one or more PRINCIPAL:PERMS pairs.
$ sd allow ./report.txt alice:read bob:rw
$ sd deny ./report.txt Everyone:write
| Flag | Effect |
|---|---|
--flags LIST | ACE inheritance flags — CI container-inherit, OI object-inherit, NP no-propagate, IO inherit-only; none clears them. |
--if EXPR | Make it a conditional rule, applied only when EXPR is true. |
--replace | Drop any existing rules for this principal and kind first, instead of appending. |
--recursive, -r | Apply to every descendant of the path. |
Remember that a deny rule, when it matches, wins over any allow — see DACL evaluation.
sd remove #
Drops every DACL rule — allow and deny — for the named principals.
$ sd remove ./report.txt bob carol
| Flag | Effect |
|---|---|
--allow-empty | Permit the result to be a present-but-empty DACL, which denies everyone. Without this, sd refuses to produce one. |
--recursive, -r | Apply to every descendant. |
Auditing #
sd audit and sd unaudit #
The SACL holds audit rules — see The SACL. sd audit adds one; sd unaudit drops every SACL rule for the named principals.
$ sd audit ./secrets.db Everyone:write:failure
An audit spec is PRINCIPAL:PERMS:WHEN, where WHEN is success, failure, or both — which outcomes to log. sd audit takes the same --flags, --if, --replace, and -r options as sd allow.
Ownership #
sd owner and sd group #
Set the descriptor's owner or group SID.
$ sd owner ./report.txt alice
$ sd group ./report.txt Administrators
Both take a single PRINCIPAL and accept -r. Changing an owner is itself an access-controlled act — see Ownership.
Integrity #
sd integrity #
Sets the file's mandatory integrity label.
$ sd integrity ./report.txt high
The level is one of untrusted, low, medium, medium-plus, high, system, protected. The standard catalog is five levels (untrusted, low, medium, high, system); medium-plus (RID 8448) and protected (RID 20480) are non-standard Windows-compatibility levels — the kernel compares any S-1-16-<rid> numerically.
| Flag | Effect |
|---|---|
--policy BITS | The label's policy bits, comma-separated: NW no write-up, NR no read-up, NX no execute-up. |
--recursive, -r | Apply to every descendant. |
For what the label does, see Mandatory integrity control.
Inheritance #
sd inherit #
Turns inheritance protection on or off — the + mark ls -l shows.
$ sd inherit off ./report.txt # lock the file; stop inheriting
$ sd inherit on ./report.txt # let it inherit from its parent again
inherit on lets the file inherit rules from its parent directory. inherit off protects the file — it keeps its current rules and stops tracking the parent.
| Flag | Effect |
|---|---|
--strip-inherited | When turning protection off, also drop the inherited rules already on the file. |
--recursive, -r | Apply to every descendant. |
sd reset #
Drops the file's own explicit rules and rebuilds its DACL purely from what the parent directory hands down — returning the file to "inherits everything".
sd propagate #
Pushes this directory's inheritable rules down into its descendants, refreshing what they inherit. Use it after changing a directory's rules so the children pick up the change.
See Inheritance for the full model.
Replacing the whole descriptor #
sd set #
Replaces the entire descriptor in one step.
$ sd set ./report.txt 'O:BAG:BAD:P(A;;FA;;;BA)(A;;0x1200a9;;;BU)'
| Argument / flag | Effect |
|---|---|
SDDL | The new descriptor as an SDDL string. - reads it from standard input. |
--binary FILE | Instead of SDDL, read the raw descriptor bytes from FILE (- for standard input). |
--components LIST | Override which parts of the descriptor (owner, group, DACL, SACL) the operation writes. |
Common flags #
These apply across the subcommands:
| Flag | Effect |
|---|---|
--recursive, -r | Apply the change to every descendant of the path. |
--no-follow-symlinks, -P | Operate on a symbolic link itself, not the file it points to. |
--json | Emit JSON instead of human-readable output. |
Exit status #
| Code | Meaning |
|---|---|
0 | The operation succeeded — or, for sd check, the access would be allowed. |
| non-zero | The operation failed, the path was unreachable, or — for sd check — the access would be denied. |
Listing and paths
Peios / Using Peios / Peiosutils / Listing and paths
Before you can do anything with a file you usually have to find it — see what a directory holds, learn one file's details, turn a relative name into an absolute one. This topic covers the commands for that: listing directories, inspecting a single file, and resolving path names.
This page names every command in the topic, says in one line what each is for, and points you at the page that covers it in full.
The commands #
| Command | Purpose |
|---|---|
ls | List the contents of a directory. The everyday command for "what's in here?". |
dir and vdir | ls with a fixed output style. dir lists in columns; vdir lists in long format. |
stat | Show the detailed status of one file — its type, size, timestamps, and raw inode metadata. |
pwd | Print the directory you are currently working in. |
readlink | Print where a symbolic link points. |
realpath | Resolve a path to its canonical, absolute form — every symlink followed, every . and .. removed. |
basename | Strip the directory part off a path, leaving just the final name. |
dirname | The opposite of basename — strip the final name, leaving the directory. |
pathchk | Check whether a path name is valid and portable before you try to create it. |
dircolors | Produce the colour settings that ls uses when it colours its output. |
These commands and file security #
Most commands in this topic work purely on path strings — they never touch the file the path names. basename, dirname, realpath, and pathchk are all string operations.
Two report on real files, and they relate to file security differently:
ls -lsurfaces security-descriptor information directly. On Peios a file's access is governed by its security descriptor — the record of who owns the file and who may do what to it. The long listing shows the owner and the parts of the descriptor that fit in a one-line-per-file format.statis a low-level diagnostic. It dumps the raw contents of a file's inode. The inode carries some numeric fields — an owner id, a group id, a mode value — that the Peios access model does not consult.statreports them because it reports the inode verbatim, but they are not the file's access policy.
Neither command prints the full security descriptor; inspecting that is a job for the dedicated security tooling. If a line of ls -l looks unfamiliar, or you want to know why stat shows fields that "don't count", the explanation is in Security descriptors.
Where to start #
If you want the everyday "what's in this directory" command and the meaning of every column it prints, read ls.
If you need the full detail of one specific file, read stat.
If you are writing a script and need to take a path apart or canonicalise it, the string commands — basename, dirname, realpath — are what you want.
ls
Peios / Using Peios / Peiosutils / Listing and paths
ls lists the contents of a directory. It is the command you use to answer "what is in here?" — and, with the right options, "who owns these files, how big are they, and when were they last changed?".
ls [options] [file...]
Each file is a directory to list the contents of, or a single file to report on. With no file argument, ls lists the directory you are currently in.
$ ls
build.sh notes.txt projects
The default listing #
Run on its own, ls prints the names of the entries in a directory and nothing else. Three rules shape that default:
- Hidden entries are skipped. A name beginning with a dot (
.config,.cache) is hidden.lsleaves hidden entries out unless you ask for them with-aor-A. - Entries are sorted by name. Alphabetical order, case-sensitive. Other sort orders are available.
- The layout adapts to where output goes. When
lsis writing to a terminal it arranges names into columns sized to the terminal width. When output is redirected to a file or a pipe it prints one name per line, so the result is easy for another program to read.
Everything past this default is opt-in. The rest of this page is the options, grouped by what they do.
The long format #
-l (or --long) switches ls into the long format: one entry per line, each with its mode, owner, size, modification time, and name.
$ ls -l
total 28
d-- S-1-5-21-9f3a-1c4e-7b20-1001 4096 May 14 09:12 projects
-x- S-1-5-21-9f3a-1c4e-7b20-1001 8344 May 16 17:40 build.sh
--- S-1-5-21-9f3a-1c4e-7b20-1001 219 May 15 11:03 notes.txt
--+ S-1-5-18 12048 May 10 22:15 policy.db
The total line at the top reports the combined disk space used by the listed files, measured in blocks.
Each entry line has five columns: mode, owner, size, modification time, name. There is no permission-bits column and no group column — the reason is below.
The mode column #
The mode column is three characters: [type][executable][protected].
The first character is the file's type:
| Character | Type |
|---|---|
- | Regular file |
d | Directory |
l | Symbolic link |
p | Named pipe (FIFO) |
s | Socket |
c | Character device |
b | Block device |
The second character is the executable mark. It is x when the entry is a regular file that is marked as an executable, and - otherwise (it is always - for directories, links, and other non-regular entries).
The executable mark describes the file, not your relationship to it. x means "this file is runnable code." It does not mean that you, specifically, are allowed to run it — whether a given principal may execute a file is a separate access decision made against the file's security descriptor. A file can show x and still be one you cannot run, and the reverse. See Access decisions for how execution is actually authorised.
The third character is the protected mark. It is + when the file's access control is inheritance-protected, and - otherwise.
A - here means the file's access control is inherited from the directory it lives in: it tracks its parent. A + means the file's access control has been set deliberately and locked, so it no longer tracks the parent directory. The + is the signal that someone has tailored this file's security on purpose. See Inheritance.
So in the example above: projects is a directory (d--); build.sh is an executable file (-x-); notes.txt is an ordinary file inheriting its security from its directory (---); and policy.db is an ordinary file whose access control has been protected (--+).
Why there are no permission bits #
A long listing does not carry a fixed block of permission characters. On Peios, what a principal may do to a file is decided by the file's security descriptor — a record that can contain any number of entries granting or denying specific rights to specific principals. There is no small, fixed set of bits that captures that, so ls -l does not pretend there is.
The mode column tells you the file's type and two facts about it (is it executable, is its security protected). To see who may do what, look at the security descriptor itself with stat or the dedicated security tooling. The concepts are in Security descriptors.
The owner column #
The owner column is the SID of the file's owner — shown in full, in the S-1-… form. ls does not translate SIDs into account names; it prints the identifier exactly as the security descriptor stores it. A well-known owner such as the system itself appears as its fixed SID (S-1-5-18); an ordinary account appears as a longer domain-style SID.
For what a SID is and how to read one, see SIDs. For what "owner" means and what it grants, see Ownership.
If ls cannot read a file's descriptor — for a dangling symlink, say — the owner column shows ? and the mode column shows the type character followed by ??.
Choosing what to list #
By default ls shows non-hidden entries, and for a directory argument it shows the directory's contents. These options change that.
| Option | Effect |
|---|---|
-a, --all | Show hidden entries too, including . (the directory itself) and .. (its parent). |
-A, --almost-all | Show hidden entries, but leave out . and ... |
-d, --directory | List a directory as an entry in its own right, instead of listing its contents. Useful with -l to see a directory's own owner and mode. |
-R, --recursive | Descend into every subdirectory and list it too. |
-B, --ignore-backups | Skip entries whose name ends in ~. |
--ignore=PATTERN | Skip entries whose name matches the shell pattern. May be given more than once. |
--hide=PATTERN | Like --ignore, but overridden by -a or -A — so a hide rule can be cancelled by asking to see everything. |
Sorting #
ls sorts by name by default. These options change the sort key; -r reverses whatever order is in effect.
| Option | Sort order |
|---|---|
-t | By modification time, newest first. |
-S | By size, largest first. |
-X | By file extension, alphabetically. |
-v | By version: runs of digits in the name sort numerically, so f2 comes before f10. |
-U | No sorting at all — entries appear in the order the directory stores them. Fast for very large directories. |
-r, --reverse | Reverse the current sort. |
--sort=WORD | Choose the sort key by name: none, time, size, extension, version, or width. |
--group-directories-first | List directories before other entries, with each group sorted normally. |
Output layout #
When several entries fit on a line, ls has to decide how to arrange them.
| Option | Layout |
|---|---|
-C | Columns, filled top-to-bottom. The default when writing to a terminal. |
-x | Columns, filled left-to-right (across the rows) instead. |
-1 | One entry per line. The default when output is not a terminal. |
-m | All entries on as few lines as possible, separated by commas. |
-l, --long | The long format described above. |
--format=WORD | Choose the layout by name: across, commas, horizontal, long, single-column, or vertical. |
-w, --width=COLS | Assume the terminal is COLS columns wide instead of detecting it. 0 means unlimited. |
File-type indicators and colour #
These options make a listing easier to scan by marking entries with a symbol or a colour.
| Option | Effect |
|---|---|
-F, --classify | Append a type symbol to each name: / for a directory, @ for a symlink, | for a FIFO, = for a socket, and * for an executable file. |
--file-type | The same, but without the * on executables. |
-p | Append only the / on directories. |
--indicator-style=WORD | Choose the indicator set: none, slash, file-type, or classify. |
--color[=WHEN] | Colour entries by type. WHEN is auto (colour only when writing to a terminal — the usual choice), always, or never. |
The colours ls uses are configurable. dircolors generates the colour settings and explains how to install them.
File sizes #
In the long format, and with -s, sizes are printed in bytes by default. These options rescale them.
| Option | Effect |
|---|---|
-h, --human-readable | Print sizes with a unit suffix — 4.0K, 234M, 56G — using powers of 1024. |
--si | Like -h, but using powers of 1000, so the suffixes are decimal. |
-s, --size | Print the disk space each entry occupies, in blocks, before its name. |
-k, --kibibytes | Use 1024-byte blocks for the size figures and directory totals. |
--block-size=SIZE | Scale every size by SIZE (for example --block-size=1M to count in megabytes). |
Timestamps #
The long format shows the modification time by default. These options change which timestamp is shown — and, when sorting by time, which one is sorted on.
| Option | Timestamp |
|---|---|
-u | Access time — when the file was last read. |
-c | Status-change time — when the file's metadata last changed. |
--time=WORD | Choose the timestamp by name: atime/access, ctime/status, mtime/modification, or birth/creation. |
--time-style=STYLE | Choose the date format: full-iso, long-iso, iso, locale, or +FORMAT for a custom layout. |
--full-time | Shorthand for the long format with --time-style=full-iso — a complete, unabbreviated timestamp. |
Symbolic links #
By default ls reports on a symbolic link itself — its type is l, its size is the length of the link text. These options make it report on the link's target instead.
| Option | Effect |
|---|---|
-L, --dereference | Always report on the file a link points to, not the link. |
-H, --dereference-command-line | Dereference only the links named directly on the command line, not links found inside a listed directory. |
--dereference-command-line-symlink-to-dir | Dereference a command-line link only when it points to a directory. |
readlink and realpath are the dedicated tools for inspecting and resolving links.
Other options #
The long-tail options, each in one line.
| Option | Effect |
|---|---|
-i, --inode | Print each entry's index number (its inode number). |
-Z, --context | Print each entry's security context. Available only when the build enables it. |
-b, --escape | Print non-printable characters in names using C-style backslash escapes. |
-q, --hide-control-chars | Replace non-printable characters in names with ?. The default when writing to a terminal. |
--show-control-chars | Print names verbatim, control characters and all. |
-N, --literal | Print names exactly, with no quoting. |
-Q, --quote-name | Wrap each name in double quotes. |
--quoting-style=WORD | Choose the quoting scheme: literal, shell, shell-always, shell-escape, c, escape, and others. |
-T, --tabsize=COLS | Assume tab stops every COLS columns when laying out output. |
-f | List everything, unsorted, including hidden entries — equivalent to -aU. Also turns colour off unless --color is given explicitly. |
--hyperlink[=WHEN] | Emit terminal hyperlinks for file names, so a capable terminal can make them clickable. |
-D, --dired | Emit extra position markers designed for the Emacs dired editing mode. |
--zero | End each line with a NUL character instead of a newline, and list one entry per line. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success — everything requested was listed. |
1 | A minor problem — for example, a named file could not be accessed. The rest of the listing still printed. |
2 | A serious problem — for example, a directory could not be read, or an option was invalid. |
dir and vdir
Peios / Using Peios / Peiosutils / Listing and paths
dir and vdir list the contents of a directory. They are ls with one decision already made for you: the output style is fixed instead of adapting to where the output is going.
dir [options] [file...]
vdir [options] [file...]
What they fix #
ls chooses its layout based on whether it is writing to a terminal — columns for a terminal, one name per line for a pipe or file. dir and vdir each pick one layout and always use it:
| Command | Layout | Equivalent ls |
|---|---|---|
dir | Columns, regardless of where output goes. | ls -C |
vdir | Long format, regardless of where output goes. | ls -l |
Both also default to a quoting style that escapes unusual characters in names without wrapping every name in quotes.
That is the only difference. The fixed style is just a default — pass an explicit format option (-1, -m, -l, -C, …) and it overrides the built-in choice, so dir -l produces a long listing and vdir -C produces columns.
Everything else is ls #
dir and vdir accept every option ls accepts and behave identically in every other respect — sorting, filtering, the long-format columns, colour, indicators, sizes, timestamps. vdir's long format is the same Peios long format documented for ls, with the [type][executable][protected] mode column and the owner SID.
For the full option set and the meaning of every long-format column, see ls. This page exists only to explain how dir and vdir differ from it — and the answer is "they pin the output style."
Exit status #
The same as ls:
| Code | Meaning |
|---|---|
0 | Success. |
1 | A minor problem — a named file could not be accessed. |
2 | A serious problem — a directory could not be read, or an option was invalid. |
stat
Peios / Using Peios / Peiosutils / Listing and paths
stat displays the detailed status of a file. Where ls gives you a line per file, stat gives you everything the system records about one file: its type, its size, its timestamps, and the low-level metadata stored in its inode.
stat [options] file...
stat is a diagnostic tool. It dumps what a file's inode physically contains, verbatim. That makes it the right command for "tell me exactly what is recorded about this file" — and it means some of what it prints needs a word of explanation, below.
The default output #
With no options, stat prints a labelled, multi-line block per file:
$ stat notes.txt
File: notes.txt
Size: 219 Blocks: 8 IO Block: 4096 regular file
Device: 8,2 Inode: 1572931 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/ jack) Gid: ( 1000/ jack)
Access: 2026-05-15 11:03:42.000000000 +0000
Modify: 2026-05-15 11:03:42.000000000 +0000
Change: 2026-05-15 11:03:18.000000000 +0000
Birth: 2026-05-15 11:03:18.000000000 +0000
Reading it line by line:
- File — the name, and for a symbolic link, what it points to.
- Size / Blocks / IO Block / type — the size in bytes, the number of allocated disk blocks, the preferred I/O block size, and the file type in words.
- Device / Inode / Links — the device the file lives on, its inode number (its unique index within that device), and the number of hard links to it.
- Access / Uid / Gid — a numeric owner id, a group id, and a mode value. See the caveat below — these are not the access policy.
- Access / Modify / Change / Birth — four timestamps: when the file was last read, last written, last had its metadata changed, and first created.
The Uid, Gid, and mode fields are decorative #
The Access: (0644/-rw-r--r--) Uid: (…) Gid: (…) line needs care.
A file's inode carries a numeric owner id, a numeric group id, and a mode value, and stat reports them because stat reports the inode verbatim. But the Peios access model does not consult these fields. What a principal may do to a file is decided by the file's security descriptor — see Security descriptors. The numbers on the Uid/Gid/mode line are inert metadata: they are stored, they are reported, and they have no effect on any access decision.
Do not read that line as a permission summary. A file showing 0644 is not "world-readable" in any meaningful sense — whether anyone can read it depends entirely on its security descriptor. stat shows these fields for completeness as a low-level diagnostic; it does not claim they mean anything.
To see the parts of a file's security that do count, use ls -l, which shows the owner SID and the mode column built from the security descriptor.
Custom output formats #
Two options replace the default block with output you control.
| Option | Effect |
|---|---|
-c FORMAT, --format=FORMAT | Print FORMAT for each file, substituting % directives. A newline is added after each file. |
--printf=FORMAT | Like --format, but interpret backslash escapes (\n, \t) in FORMAT and add no trailing newline. Include \n yourself if you want one. |
$ stat --format='%n is %s bytes' notes.txt
notes.txt is 219 bytes
File directives #
Used in the format string when stating files (the default mode):
| Directive | Substitutes |
|---|---|
%n | File name. |
%N | Quoted file name; for a symlink, with the target shown. |
%F | File type in words (regular file, directory, …). |
%s | Total size, in bytes. |
%b | Number of allocated blocks. |
%B | Size in bytes of each block counted by %b. |
%o | Preferred I/O transfer block size. |
%i | Inode number. |
%h | Number of hard links. |
%d / %D | Device number, in decimal / in hexadecimal. |
%t / %T | For a device file, the major / minor device type, in hexadecimal. |
%m | Mount point of the file system the file is on. |
%f | Raw mode value, in hexadecimal. |
%x / %X | Last access time — human-readable / seconds since the epoch. |
%y / %Y | Last modification time — human-readable / seconds since the epoch. |
%z / %Z | Last status-change time — human-readable / seconds since the epoch. |
%w / %W | File creation (birth) time — human-readable / seconds since the epoch. - or 0 if unknown. |
The directives %a and %A (the mode in octal and in symbolic form) and %u, %U, %g, %G (the owner and group, numeric and by name) report the decorative inode fields described above. They are available for completeness; they are not the file's access policy.
The %C directive (security context) is inactive — it produces no meaningful value on a standard Peios system.
File-system directives #
With -f, stat reports on the file system a file lives on rather than the file itself, and the format string uses a different directive set:
| Directive | Substitutes |
|---|---|
%n | File name. |
%i | File-system ID, in hexadecimal. |
%t / %T | File-system type — in hexadecimal / in words. |
%l | Maximum length of a file name. |
%s | Block size, for fast transfers. |
%S | Fundamental block size, used for the block counts. |
%b | Total data blocks in the file system. |
%f | Free blocks. |
%a | Free blocks available to an ordinary principal. |
%c | Total inodes (file nodes). |
%d | Free inodes. |
Options #
| Option | Effect |
|---|---|
-f, --file-system | Report on the file system containing each file, instead of the file. |
-L, --dereference | Follow symbolic links — report on the link's target rather than the link itself. |
-t, --terse | Print the information on a single line, as bare values with no labels. Useful for scripts. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was stated successfully. |
1 | A file could not be stated, or an option or format directive was invalid. |
pwd
Peios / Using Peios / Peiosutils / Listing and paths
pwd — "print working directory" — prints the absolute path of the directory you are currently in.
pwd [options]
$ pwd
/home/jack/projects
Every process has a working directory: the directory that relative path names are interpreted against. pwd tells you what yours is.
Physical and logical paths #
There are two honest answers to "where am I", and they differ only when symbolic links are involved.
Suppose /home/jack/work is a symbolic link pointing at /data/projects/jack, and you move into it. The physical path — the real location, with every link resolved — is /data/projects/jack. The logical path — the route you took to get there — is /home/jack/work.
| Option | Path printed |
|---|---|
-P, --physical | The physical path: every symbolic link resolved to its target. |
-L, --logical | The logical path: the route recorded as you navigated, taken from the PWD environment variable when it is accurate. |
By default pwd prints the physical path — -P is the default, and naming it explicitly just makes that choice visible. Use -L when you want the path as you navigated it, links and all.
If -L is requested but the recorded PWD value is missing or does not actually match the current directory, pwd falls back to the physical path rather than print something wrong.
A note on shells #
Many command shells provide their own built-in pwd, and the shell's version is what runs when you type pwd at a prompt. The two behave the same for everyday use. To be certain you are running this command rather than the shell built-in, invoke it by its full path.
Exit status #
| Code | Meaning |
|---|---|
0 | The working directory was printed. |
1 | The working directory could not be determined or could not be printed. |
readlink
Peios / Using Peios / Peiosutils / Listing and paths
readlink prints where a symbolic link points.
readlink [options] file...
A symbolic link is a file whose contents are another path. readlink reads that path and prints it:
$ readlink /home/jack/work
/data/projects/jack
By default readlink resolves exactly one level: it prints the link's immediate target, whether or not that target is itself a link, and whether or not it exists. If the named file is not a symbolic link, readlink prints nothing and reports failure.
Canonicalising a whole path #
The canonicalise options change readlink from "read this one link" into "resolve this entire path to its real, absolute form" — following every symbolic link in every component, collapsing . and ... The three differ only in how strict they are about components existing:
| Option | Resolves | Existence requirement |
|---|---|---|
-f, --canonicalize | Every link in the path. | Every component except the last must exist. |
-e, --canonicalize-existing | Every link in the path. | Every component must exist. |
-m, --canonicalize-missing | Every link in the path. | No component need exist. |
With any of these, readlink succeeds on an ordinary (non-link) file too — it simply returns the canonical path. realpath is the dedicated command for this canonicalising job and has more options for it; the canonicalise flags here exist so readlink can do it without a second tool.
Output and error control #
| Option | Effect |
|---|---|
-n, --no-newline | Do not print the trailing newline. Ignored, with a warning, when more than one file is given. |
-z, --zero | End each output line with a NUL character instead of a newline. |
-q, --quiet / -s, --silent | Suppress most error messages. This is the default. |
-v, --verbose | Report error messages that the quiet default would suppress. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every named file was resolved and printed. |
1 | A file was not a symbolic link, or a path could not be resolved under the chosen strictness. |
realpath
Peios / Using Peios / Peiosutils / Listing and paths
realpath resolves a path to its canonical form: absolute, with every symbolic link followed and every . and .. component removed. Given any path, it tells you the one true location it refers to.
realpath [options] file...
$ realpath ../work/./notes.txt
/data/projects/jack/notes.txt
A single file can be named many ways — through links, through relative paths, with redundant components. realpath reduces all of them to the single canonical path, which makes it the right tool for comparing two paths, or for turning a path a user typed into one a script can rely on.
How much must exist #
By default realpath requires every component of the path except the last to exist. These options change that requirement:
| Option | Existence requirement |
|---|---|
| (default) | Every component except the last must exist. |
-e, --canonicalize-existing | Every component must exist — including the final one. |
-m, --canonicalize-missing | No component need exist. The path is canonicalised purely as text where it cannot be walked. |
How symbolic links are handled #
| Option | Effect |
|---|---|
-P, --physical | Resolve each symbolic link as it is encountered. This is the default. |
-L, --logical | Resolve .. components before resolving the symbolic links they follow. |
-s, --strip, --no-symlinks | Do not resolve symbolic links at all — only remove . and .. components. The result is canonical as text, but a link in the path is left as-is. |
Output options #
| Option | Effect |
|---|---|
--relative-to=DIR | Print the result as a path relative to DIR instead of as an absolute path. |
--relative-base=DIR | Print an absolute path, unless the result lies inside DIR, in which case print it relative to DIR. |
-z, --zero | End each output line with a NUL character instead of a newline. |
-q, --quiet | Do not print a warning when a path is invalid. The exit status still reports the failure. |
realpath and readlink #
readlink with -f/-e/-m does the same canonicalising job. The difference is focus: readlink is primarily for reading a single link's target, with canonicalising as an extra; realpath is built for canonicalising and carries the richer option set — relative output, the symlink-handling modes, the strip-only mode. Use realpath when canonicalising is the actual goal.
Exit status #
| Code | Meaning |
|---|---|
0 | Every path was resolved and printed. |
1 | A path could not be resolved under the chosen options. |
basename
Peios / Using Peios / Peiosutils / Listing and paths
basename takes a path and prints just its final component — the file name, with all the leading directories removed.
basename name [suffix]
basename [options] name...
$ basename /home/jack/projects/notes.txt
notes.txt
It is a pure text operation: basename never looks at the file system. It works on the string you give it, so the path need not exist.
Removing a suffix #
Give a second argument and basename also strips that suffix from the end of the name — handy for turning a file name into a bare stem:
$ basename /home/jack/projects/notes.txt .txt
notes
The suffix is only removed if it is actually there, and never if it would leave an empty string.
Options #
basename takes one name by default. The options let it process several at once.
| Option | Effect |
|---|---|
-a, --multiple | Treat every argument as a name to process, instead of treating the second argument as a suffix. Required to pass more than one name. |
-s, --suffix=SUFFIX | Remove SUFFIX from each name. Implies -a, so it applies to every name given. |
-z, --zero | End each output line with a NUL character instead of a newline. |
$ basename -s .txt notes.txt readme.txt changelog.txt
notes
readme
changelog
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A usage error — a missing or extra operand. |
dirname
Peios / Using Peios / Peiosutils / Listing and paths
dirname takes a path and prints everything except its final component — the directory the name lives in. It is the counterpart of basename.
dirname [options] name...
$ dirname /home/jack/projects/notes.txt
/home/jack/projects
Like basename, it is a pure text operation — dirname never touches the file system, so the path need not exist.
How it handles edge cases #
- If the name has no
/in it at all, there is no directory part, sodirnameprints.— the current directory. - Trailing slashes on the name are ignored before the final component is removed.
$ dirname notes.txt
.
$ dirname /home/jack/projects/
/home
Multiple names #
dirname accepts any number of names and prints the directory part of each on its own line:
$ dirname /etc/hosts /var/log/messages report.txt
/etc
/var/log
.
Options #
| Option | Effect |
|---|---|
-z, --zero | End each output line with a NUL character instead of a newline. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A usage error — no name was given. |
pathchk
Peios / Using Peios / Peiosutils / Listing and paths
pathchk checks whether a path name is valid and usable. It tells you, before you try to create a file, whether the name would be rejected — because it is too long, contains an unusable component, or could not be reached.
pathchk [options] name...
$ pathchk /home/jack/projects/notes.txt
$ echo $?
0
pathchk prints nothing when a name is fine; it prints a diagnostic and fails when a name is not. It is meant for scripts that build up path names and want to fail early, with a clear message, instead of discovering the problem halfway through an operation.
Like the other name commands in this topic, pathchk does not require the file to exist — it is checking the name, not looking for the file.
What the default check covers #
With no options, for each name pathchk checks:
- that the name is not empty;
- that no part of the name exceeds the length limits of the file systems the path would actually touch;
- that the leading directories of the name can be searched.
Stricter checks #
The options replace "valid on this system" with "valid across a wide range of systems" — useful when a name has to work somewhere other than where you are checking it.
| Option | Adds |
|---|---|
-p | Check against a fixed, conservative set of length limits instead of the local file system's, and flag characters that are not broadly portable. |
-P | Reject an empty name, and reject any component that begins with -. |
--portability | Apply both -p and -P — the strictest check. |
A name with a leading - on a component is worth catching: many commands would read it as an option rather than a file name. -P flags exactly that class of trouble.
Exit status #
| Code | Meaning |
|---|---|
0 | Every name passed every requested check. |
1 | At least one name failed. A diagnostic naming the problem was printed. |
dircolors
Peios / Using Peios / Peiosutils / Listing and paths
When ls colours its output, it reads the colours from an environment variable named LS_COLORS. dircolors is the command that produces the value of that variable.
dircolors [options] [file]
dircolors does not set the variable itself — a command cannot change its parent shell's environment. Instead it prints shell commands that, when run by the shell, set LS_COLORS. The usual way to use it is to have the shell evaluate that output:
eval "$(dircolors)"
Put that line in a shell startup file and every ls --color afterwards picks up the colours. With no arguments, dircolors uses a built-in default colour scheme.
Choosing the shell syntax #
The commands dircolors prints have to match the shell that will run them. By default it guesses the syntax from the SHELL environment variable; these options state it outright.
| Option | Output syntax |
|---|---|
-b, --sh, --bourne-shell | Bourne-shell-family syntax. |
-c, --csh, --c-shell | C-shell-family syntax. |
If no shell option is given and SHELL is not set, dircolors cannot guess and reports an error.
Customising the colours #
To change the colours, you start from the default scheme, edit it, and feed it back.
| Option | Effect |
|---|---|
-p, --print-database | Print the built-in colour database in its editable source form, instead of shell commands. |
--print-ls-colors | Print the colours fully escaped, one per line, for inspection. |
The workflow is:
dircolors -p > ~/.dircolors # save the default scheme to a file
# ...edit ~/.dircolors to taste...
eval "$(dircolors ~/.dircolors)" # load the edited scheme
When dircolors is given a file argument, it reads the colour definitions from that file instead of using the built-in database. The file maps file types and name extensions to colours; dircolors -p shows the format, with each line commented.
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A usage error, or a colour-definition file that could not be read or parsed. |
Viewing and joining text
Peios / Using Peios / Peiosutils / Viewing and joining text
Much of the work at a command line is reading text — looking at a file, checking the start or end of one, pulling a column out, stitching two files together. This topic covers the commands for that: viewing files, showing parts of them, and joining or splitting text by line and by column.
This page names every command in the topic. The companion topic, Transforming text, covers the commands that reshape text — sorting, de-duplicating, substituting characters.
The commands #
Viewing a whole file
| Command | Purpose |
|---|---|
cat | Print one or more files straight through — and join several into one. |
tac | Print a file with its lines in reverse — last line first. |
more | Show a file one screen at a time, pausing between screens. |
od | Dump a file's raw bytes in octal, hex, decimal, or character form. |
Viewing part of a file
| Command | Purpose |
|---|---|
head | Print the first lines (or bytes) of a file. |
tail | Print the last lines of a file — and, with -f, keep printing as the file grows. |
nl | Print a file with its lines numbered. |
Cutting and combining
| Command | Purpose |
|---|---|
cut | Pull selected columns — byte ranges or delimited fields — out of each line. |
paste | Join files side by side, line for line. |
join | Join two files on a shared field, like a database join. |
comm | Compare two sorted files and report which lines they share. |
Splitting a file into files
| Command | Purpose |
|---|---|
split | Break a file into equal-sized pieces. |
csplit | Break a file into pieces at lines that match a pattern. |
A note on input #
Almost every command here follows the same convention for where its text comes from. Name one or more files and it reads those; name none and it reads from standard input, so it can sit in the middle of a pipeline. Where a command accepts files, the name - stands for standard input, so you can mix files and piped input in one invocation.
Where to start #
To simply print a file, or join a few together, read cat — the most-used command in the topic.
cat
Peios / Using Peios / Peiosutils / Viewing and joining text
cat prints the contents of files. Its name is short for "concatenate" — given several files, it prints them one after another, as a single stream.
cat [options] [file...]
$ cat notes.txt
$ cat part1.txt part2.txt part3.txt > whole.txt
With no file — or with the name - — cat reads standard input, which is what makes it useful in a pipeline or for capturing typed input into a file.
Plain use #
Most of the time cat is run with no options at all: it copies its input to its output, byte for byte, unchanged. The options exist for the times you want to see something about the text that is normally invisible, or to adjust spacing and numbering.
Numbering lines #
| Option | Effect |
|---|---|
-n, --number | Number every output line. |
-b, --number-nonblank | Number only the non-empty lines. Overrides -n. |
Making invisible characters visible #
These options reveal characters that normally print as nothing, or as whitespace — useful when a file is not behaving and you suspect a stray tab or control character.
| Option | Effect |
|---|---|
-E, --show-ends | Print a $ at the end of each line, so trailing spaces become visible. |
-T, --show-tabs | Print tab characters as ^I instead of as whitespace. |
-v, --show-nonprinting | Print control and other non-printing characters using ^ and M- notation (leaving newline and tab as they are). |
-e | Shorthand for -vE. |
-t | Shorthand for -vT. |
-A, --show-all | Shorthand for -vET — show ends, tabs, and all non-printing characters at once. |
Adjusting spacing #
| Option | Effect |
|---|---|
-s, --squeeze-blank | Collapse runs of blank lines into a single blank line. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was printed. |
1 | A file could not be read. |
tac
Peios / Using Peios / Peiosutils / Viewing and joining text
tac prints a file with its lines in reverse — the last line first, the first line last. The name is cat spelled backwards, which is exactly what it does.
tac [options] [file...]
$ tac events.log
Reversing a log file so the newest entries come first is the everyday use. With no file, tac reads standard input.
Reversing on something other than lines #
tac does not have to split on newlines. You can tell it to treat some other string as the separator, and it will reverse the order of the pieces between those separators.
| Option | Effect |
|---|---|
-s, --separator=STRING | Use STRING as the separator between records, instead of a newline. |
-r, --regex | Treat the separator as a regular expression rather than a literal string. |
-b, --before | Expect the separator before each record rather than after it. This matters for how the separator is reattached when the records are reordered. |
$ tac -s ', ' -r names.csv
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was printed. |
1 | A file could not be read, or the separator regular expression was invalid. |
head
Peios / Using Peios / Peiosutils / Viewing and joining text
head prints the beginning of a file — by default, the first 10 lines.
head [options] [file...]
$ head config.toml
It is the quick way to glance at the top of a file without printing the whole thing — a header, the first few records, the start of a log. With no file, head reads standard input.
Choosing how much #
| Option | Effect |
|---|---|
-n, --lines=NUM | Print the first NUM lines instead of 10. |
-c, --bytes=NUM | Print the first NUM bytes instead of counting lines. |
NUM accepts a unit suffix (K, M, …) when counting bytes.
Counting from the end #
If NUM is given a leading -, the meaning flips: head prints everything except the last NUM.
$ head -n -5 report.txt # all but the final 5 lines
Headers for multiple files #
When given more than one file, head prints a header line before each one so you can tell them apart. Two options override that:
| Option | Effect |
|---|---|
-q, --quiet | Never print the file-name headers. |
-v, --verbose | Always print the header, even for a single file. |
Other options #
| Option | Effect |
|---|---|
-z, --zero-terminated | Treat the NUL character as the line delimiter instead of newline. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was printed. |
1 | A file could not be read. |
tail
Peios / Using Peios / Peiosutils / Viewing and joining text
tail prints the end of a file — by default, the last 10 lines.
tail [options] [file...]
$ tail server.log
It is the counterpart of head: the quick way to see the most recent lines of a file. With no file, tail reads standard input.
Choosing how much #
| Option | Effect |
|---|---|
-n, --lines=NUM | Print the last NUM lines instead of 10. |
-c, --bytes=NUM | Print the last NUM bytes instead of counting lines. |
If NUM is given a leading +, the meaning flips: tail prints everything from line NUM onward.
$ tail -n +20 report.txt # from line 20 to the end
Following a growing file #
tail's most useful trick is -f. Instead of printing the end and exiting, it stays running and prints new lines as they are appended — the standard way to watch a log file live.
| Option | Effect |
|---|---|
-f, --follow | Keep the file open and print new data as it is added. |
-F | Follow the file by name, and keep retrying if it is missing. Equivalent to --follow=name --retry. This survives log rotation — when the file is replaced, -F picks up the new one. |
--retry | Keep trying to open a file that is not yet accessible. |
--pid=PID | While following, stop once process PID exits. |
-s, --sleep-interval=N | Wait N seconds between checks of the file. |
Press Ctrl-C to stop a tail -f.
Headers for multiple files #
As with head, when given more than one file tail prints a header before each.
| Option | Effect |
|---|---|
-q, --quiet | Never print the file-name headers. |
-v, --verbose | Always print the header, even for a single file. |
Other options #
| Option | Effect |
|---|---|
-z, --zero-terminated | Treat the NUL character as the line delimiter instead of newline. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read. |
more
Peios / Using Peios / Peiosutils / Viewing and joining text
more displays a file one screenful at a time. Where cat prints a whole file at once — and a long file scrolls straight past — more shows the first screen, then waits for you before showing the next.
more [options] file...
$ more long-report.txt
Moving through the file #
While more is paused, it is waiting for a keypress:
| Key | Action |
|---|---|
| Space | Show the next screenful. |
| Return | Show the next line. |
q | Quit. |
/ | Search forward for a string. |
h | Show the built-in help. |
more moves forward through a file. It is the simple, always-available pager — enough for reading something through once.
Options #
| Option | Effect |
|---|---|
-n, --lines=NUM | Show NUM lines per screenful instead of filling the terminal. --number=NUM is the same. |
-F, --from-line=NUM | Begin displaying at line NUM. |
-P, --pattern=STRING | Search for STRING and begin displaying at the first match. |
-e, --exit-on-eof | Exit automatically at the end of the file, rather than waiting. |
-s, --squeeze | Collapse runs of blank lines into one. |
-u, --plain | Suppress underlining in the displayed text. |
-p, --print-over | Clear the screen and print the next page, instead of scrolling. |
-c, --clean-print | Redraw each page in place, cleaning line ends, instead of scrolling. |
-d, --silent | When an unrecognised key is pressed, show a short hint instead of ringing the terminal bell. |
-l, --logical | Do not pause when a line contains a form-feed character. |
-f, --no-pause | Count logical lines rather than screen lines when deciding where to pause. |
Exit status #
| Code | Meaning |
|---|---|
0 | The file was displayed. |
1 | A file could not be opened. |
nl
Peios / Using Peios / Peiosutils / Viewing and joining text
nl prints a file with its lines numbered. cat -n also numbers lines; nl is the command for when you need control over the numbering — which lines count, how the number looks, where it restarts.
nl [options] [file...]
$ nl chapter.txt
1 Once the system is installed,
2 the first boot brings up
3 the configuration service.
Which lines get a number #
By default nl numbers only the non-empty lines. -b sets the rule:
-b STYLE | Numbers… |
|---|---|
-b a | every line. |
-b t | only non-empty lines. This is the default. |
-b n | no lines. |
-b pBRE | only lines that match the basic regular expression BRE. |
| Option | Effect |
|---|---|
-b, --body-numbering=STYLE | The numbering rule, as above. |
-l, --join-blank-lines=N | Count a run of N blank lines as one numbered line. |
How the number looks #
| Option | Effect |
|---|---|
-n, --number-format=FORMAT | ln = left-justified; rn = right-justified; rz = right-justified with leading zeros. |
-w, --number-width=N | Use N columns for the number. |
-s, --number-separator=STRING | Put STRING between the number and the line text. |
-v, --starting-line-number=N | Start counting from N. |
-i, --line-increment=N | Increase the number by N at each counted line. |
Logical pages #
nl can treat one file as a sequence of logical pages, each with a header, a body, and a footer, and number the three parts by different rules. Pages are separated by special delimiter lines in the input.
| Option | Effect |
|---|---|
-h, --header-numbering=STYLE | Numbering style for header sections. |
-f, --footer-numbering=STYLE | Numbering style for footer sections. |
-d, --section-delimiter=CC | The characters that mark a section boundary in the input. |
-p, --no-renumber | Do not reset the line number at the start of each logical page. |
The styles for -h and -f are the same a / t / n / pBRE set as -b. For an ordinary file with no delimiter lines, the whole file is one body and only -b matters.
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or an option value was invalid. |
cut
Peios / Using Peios / Peiosutils / Viewing and joining text
cut extracts columns from text. For each line of input it prints only the parts you select, dropping the rest.
cut option... [file...]
$ cut -d , -f 1,3 people.csv # the 1st and 3rd comma-separated field
$ cut -c 1-8 log.txt # the first 8 characters of each line
Every run of cut needs two decisions: what counts as a column, and which columns to keep.
What counts as a column #
Choose exactly one mode:
| Option | A column is… |
|---|---|
-b, --bytes | a single byte. |
-c, --characters | a single character. |
-f, --fields | a field — a stretch of text between delimiters. |
-b and -c cut by position. -f cuts by field, which is what you want for tabular data — a CSV, a table, the columns of a report.
Which columns to keep #
The mode option takes a sequence: numbers and inclusive ranges, separated by commas.
| Sequence | Selects |
|---|---|
3 | column 3 only. |
2,5,9 | columns 2, 5, and 9. |
5-7 | columns 5 through 7. |
3- | column 3 to the end of the line. |
-4 | the start of the line through column 4. |
1,4-6,9 | any mixture of the above. |
| Option | Effect |
|---|---|
--complement | Invert the selection — keep every column except those named. |
Field mode: the delimiter #
In field mode, cut needs to know what separates the fields.
| Option | Effect |
|---|---|
-d, --delimiter=CHAR | The character that separates fields. The default is a tab. |
-w | Separate fields on runs of whitespace (spaces and tabs) instead of a single character. Cannot be combined with -d. |
-s, --only-delimited | Print only lines that actually contain the delimiter; drop lines that have no fields to cut. |
--output-delimiter=STRING | Put STRING between the kept fields in the output, instead of repeating the input delimiter — handy for converting one delimited format to another. |
Other options #
| Option | Effect |
|---|---|
-z, --zero-terminated | Treat the NUL character as the line delimiter, so a "line" may itself contain newlines. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A mode was missing or given twice, an option was misused, or a file could not be read. |
paste
Peios / Using Peios / Peiosutils / Viewing and joining text
paste joins files side by side. It takes the first line of each file and prints them on one line, then the second line of each, and so on — merging files into columns.
paste [options] [file...]
$ paste names.txt ages.txt
Ada 36
Grace 41
Linus 29
By default the merged pieces are separated by a tab. paste is the column-wise counterpart of cat, which joins files end to end.
Pasting one file at a time #
| Option | Effect |
|---|---|
-s, --serial | Paste each file's lines onto a single line, one file at a time, instead of merging files in parallel. A file's lines become a row. |
$ paste -s -d , names.txt
Ada,Grace,Linus
Choosing the separator #
| Option | Effect |
|---|---|
-d, --delimiters=LIST | Use the characters in LIST as separators instead of a tab. When LIST has more than one character, paste cycles through them. |
-z, --zero-terminated | Treat the NUL character as the line delimiter instead of newline. |
paste and join #
paste merges by position — line 1 with line 1, line 2 with line 2, blind to content. When you want to merge by a matching value — pairing rows that share a key — that is join.
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or the delimiter list was malformed. |
join
Peios / Using Peios / Peiosutils / Viewing and joining text
join merges two files on a shared field. For every pair of lines — one from each file — that have the same value in their join field, it prints a combined line. It is the command-line form of a database join.
join [options] file1 file2
$ join employees.txt salaries.txt
If employees.txt has 101 Ada and salaries.txt has 101 80000, join pairs them on the shared 101 and prints 101 Ada 80000.
The input must be sorted #
join works by stepping through both files together, so both files must be sorted on the join field. If they are not, join will miss matches. Sort them first with sort, on the same field join will use.
join checks the ordering as it goes and reports a file that is out of order; --nocheck-order turns that check off, and --check-order forces it on.
Choosing the join field #
By default join joins on the first field of each file, and fields are separated by whitespace.
| Option | Effect |
|---|---|
-1 FIELD | Join on FIELD of file 1. |
-2 FIELD | Join on FIELD of file 2. |
-j FIELD | Join on FIELD of both files — shorthand for -1 FIELD -2 FIELD. |
-t CHAR | Use CHAR as the field separator for input and output. |
Unmatched lines #
By default join prints only the lines that paired. A line with no match on the other side is dropped. These options bring unmatched lines back:
| Option | Effect |
|---|---|
-a FILENUM | Also print unpaired lines from file FILENUM (1 or 2). |
-v FILENUM | Print only the unpaired lines from file FILENUM, and suppress the joined output. |
-e EMPTY | Fill in any missing field with the string EMPTY. |
Shaping the output #
| Option | Effect |
|---|---|
-o FORMAT | Build each output line from the field list FORMAT, rather than the default layout. |
-i, --ignore-case | Ignore letter case when comparing join fields. |
--header | Treat the first line of each file as column headers — print them, paired, without trying to match them. |
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, was not sorted, or an option was invalid. |
comm
Peios / Using Peios / Peiosutils / Viewing and joining text
comm compares two sorted files and tells you which lines are unique to each and which are common to both.
comm [options] file1 file2
By default it prints three columns:
$ comm list-a.txt list-b.txt
apple
banana
cherry
date
- Column 1 — lines only in
file1. - Column 2 — lines only in
file2. - Column 3 — lines in both.
Each column is indented past the one before, so a line's column position tells you where it was found.
The input must be sorted #
comm steps through both files together, so both must be sorted. On unsorted input the comparison is meaningless. Sort the files first with sort.
comm checks the ordering and reports a file that is out of order. --check-order forces the check; --nocheck-order disables it.
Showing only the columns you want #
Each column can be switched off. The remaining columns close up to fill the gap.
| Option | Effect |
|---|---|
-1 | Suppress column 1 — hide lines unique to file1. |
-2 | Suppress column 2 — hide lines unique to file2. |
-3 | Suppress column 3 — hide lines common to both. |
These combine to answer specific questions. comm -12 shows only the common lines — the intersection of the two files. comm -23 shows lines in file1 that are not in file2 — the difference.
Other options #
| Option | Effect |
|---|---|
--output-delimiter=STR | Separate the columns with STR instead of spaces. |
--total | Print a final summary line with the count in each column. |
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or was not sorted. |
split
Peios / Using Peios / Peiosutils / Viewing and joining text
split breaks one file into several smaller files of roughly equal size.
split [options] [input [prefix]]
$ split -l 1000 big.log
$ ls
xaa xab xac xad
By default split writes 1000 lines per piece, names the pieces xaa, xab, xac, … — a prefix of x followed by a two-letter suffix — and reads its input from a named file or from standard input.
To put the file back together, concatenate the pieces in order: cat xaa xab xac … > original.
Each piece is a newly created file, and a new file needs a security descriptor; each piece inherits one from the directory it is created in.
How big each piece is #
Choose one of these to set the piece size:
| Option | Each piece holds… |
|---|---|
-l, --lines=N | N lines. This is the default behaviour, with N = 1000. |
-b, --bytes=SIZE | SIZE bytes. |
-C, --line-bytes=SIZE | up to SIZE bytes, but only whole lines — never a line split across two pieces. |
-n, --number=CHUNKS | the input divided into a fixed number of pieces (see below). |
A SIZE accepts a unit suffix: K, M, G, … as powers of 1024, or KB, MB, … as powers of 1000.
Fixed number of chunks (-n) #
-n divides the input into a set number of pieces rather than fixing each piece's size. CHUNKS may be:
| Form | Effect |
|---|---|
N | Split into N pieces by size. |
l/N | Split into N pieces without splitting any line across pieces. |
r/N | Like l/N, but distribute lines round-robin across the pieces. |
K/N | Write only the Kth of N pieces, to standard output. |
Naming the pieces #
| Option | Effect |
|---|---|
prefix (operand) | The leading part of each output name. Default x. |
-a, --suffix-length=N | Use N characters for the suffix. Default 2. |
-d, --numeric-suffixes[=START] | Use numeric suffixes (00, 01, …) instead of letters, optionally from START. |
-x, --hex-suffixes[=START] | Use hexadecimal suffixes. |
--additional-suffix=SUFFIX | Append a fixed SUFFIX to every output name — for giving the pieces an extension. |
Other options #
| Option | Effect |
|---|---|
-e, --elide-empty-files | With -n, do not write out pieces that would be empty. |
-t, --separator=SEP | Use SEP as the line separator instead of newline. |
--verbose | Print a line as each output file is opened. |
Exit status #
| Code | Meaning |
|---|---|
0 | The file was split. |
1 | A failure — the input could not be read, or the suffixes were exhausted. |
csplit
Peios / Using Peios / Peiosutils / Viewing and joining text
csplit — "context split" — breaks a file into pieces at points you choose: a line number, or a line that matches a pattern. Where split cuts a file into equal sizes, csplit cuts it at meaningful boundaries.
csplit [options] file pattern...
$ csplit report.txt '/^Chapter/' '{*}'
That splits report.txt into one piece per chapter, cutting at every line beginning with Chapter. The pieces are written to xx00, xx01, xx02, … and csplit prints the byte size of each.
Each piece is a newly created file, and a new file needs a security descriptor; each piece inherits one from the directory it is created in.
Patterns #
Each pattern argument marks a cut point. csplit copies everything up to that point into the next output file, then continues.
| Pattern | Cut where… |
|---|---|
N (a number) | line N is reached — the piece is lines up to N-1. |
/REGEX/ | a line matches REGEX; that line starts the next piece. |
%REGEX% | a line matches REGEX — but skip the text up to it instead of writing it to a piece. |
/REGEX/+N or /REGEX/-N | an offset of N lines from the matching line. |
{N} | repeat the previous pattern N more times. |
{*} | repeat the previous pattern as many times as the file allows. |
A pattern that finds no match is an error, and by default csplit removes the pieces it had already written. -k keeps them.
Naming the pieces #
| Option | Effect |
|---|---|
-f, --prefix=PREFIX | Use PREFIX instead of xx at the start of each output name. |
-n, --digits=N | Use N digits in the numeric part of the name instead of 2. |
-b, --suffix-format=FORMAT | Use a custom printf-style FORMAT for the numeric suffix. |
Other options #
| Option | Effect |
|---|---|
-k, --keep-files | Do not delete the output files if csplit fails partway. |
-z, --elide-empty-files | Do not write out pieces that would be empty. |
--suppress-matched | Drop the lines that the patterns matched, rather than keeping them in a piece. |
-s, --quiet | Do not print the byte size of each piece. |
Exit status #
| Code | Meaning |
|---|---|
0 | The file was split. |
1 | A pattern did not match, a line number was out of range, or the input could not be read. |
od
Peios / Using Peios / Peiosutils / Viewing and joining text
od — short for "octal dump" — prints the raw bytes of a file. Where cat shows you a file as text, od shows you the actual bytes underneath: each one rendered as a number, a character, or a floating-point value, in whatever base you ask for. It is the tool for looking at binary files, checking exactly which bytes a text file contains, or finding a stray control character that isn't visible on screen.
od [options] [file...]
$ od hello.txt
0000000 062510 066154 020157 067527 066162 020144 000012
0000015
With no file — or with the name - — od reads standard input, so it can sit at the end of a pipeline. Given several files, it reads them one after another as a single continuous stream, and the byte offsets run straight through from one file into the next.
How the output is laid out #
Every line of output has two parts:
- An offset on the left: the position, counted in bytes from the start of the input, of the first byte on that line. By default it is printed in octal (see
-Ato change the base). - One or more format columns on the right: the bytes of that line rendered in the format(s) you chose.
With no format option, od prints the input as octal, two bytes at a time — the traditional default. The very last line of output is a lone offset marking the total length of the input.
If you ask for more than one format at once, each format is printed on its own line, stacked under a single shared offset. Only the first line of the group carries the offset; the rest are indented to line up beneath it.
Choosing what the bytes look like #
There are two ways to say how bytes should be rendered: a set of single-letter named shortcuts for the common cases, and the general -t / --format option for full control. You can give several at once, and they are applied in the order they appear on the command line.
Named format shortcuts #
Each of these selects one output format. Combine them freely (od -cx prints characters and hex together).
| Option | Renders each unit as |
|---|---|
-a | Named characters, ignoring the high-order bit (control characters shown by name, e.g. nul, sp, del). |
-b | Octal, one byte at a time. |
-c | Printable ASCII / UTF-8 characters, with backslash escapes (\n, \t, …) for the rest. |
-d | Unsigned decimal, 2-byte units. |
-D | Unsigned decimal, 4-byte units. |
-o | Octal, 2-byte units. |
-O | Octal, 4-byte units. |
-s | Signed decimal, 2-byte units. |
-i | Signed decimal, 4-byte units. |
-l | Signed decimal, 8-byte units. |
-I, -L | Signed decimal, 8-byte units. |
-x, -h | Hexadecimal, 2-byte units. |
-X, -H | Hexadecimal, 4-byte units. |
-f | Floating point, single precision (32-bit). |
-e, -F | Floating point, double precision (64-bit). |
Type specifications (-t, --format) #
-t takes a type specification and gives you every combination the named shortcuts cover and more. Repeat -t (or list several specs in one argument) to print multiple formats.
A type specification is a type letter, an optional size, and an optional z suffix:
| Type letter | Meaning |
|---|---|
a | Printable 7-bit ASCII, named (like -a). |
c | UTF-8 characters, with octal escapes for undefined bytes (like -c). |
d | Signed decimal. |
u | Unsigned decimal. |
o | Octal. |
x | Hexadecimal. |
f | Floating point. |
For the numeric types (d, u, o, x, f) a size says how many bytes make up one unit. It can be a number, or a letter:
| Size | Applies to | Bytes per unit |
|---|---|---|
1 / C | integer types | 1 |
2 / S | integer types | 2 |
4 / I | integer types | 4 |
8 / L | integer types | 8 |
4 / F | floating point | 4 (single precision) |
8 / D | floating point | 8 (double precision) |
16 / L | floating point | 16 (extended / long double) |
2 / H | floating point | 2 (IEEE half precision) |
2 / B | floating point | 2 (bfloat16) |
If you leave the size off, integer types default to 4 bytes and floating point defaults to 8 bytes. For the character types a and c, a size is not allowed.
Add a z at the end of a spec to append an ASCII dump — the bytes of that line shown as text, . for anything non-printable — to the right of the numbers. For example, od -t x1z gives the familiar hex-plus-text layout:
$ od -A x -t x1z file.bin
000000 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 0a >Hello, world!.<
Examples: od -t d2 is signed decimal in 2-byte units; od -t x1 is hex byte-by-byte; od -t fD is double-precision floats; od -t c is characters. od -t x1 -t c prints hex and characters as two stacked lines.
Byte order (--endian) #
For multi-byte numeric formats, --endian sets the byte order used to assemble each unit. Without it, od uses the machine's native byte order. Use --endian=big or --endian=little to force one regardless of the host.
The offset column #
| Option | Effect |
|---|---|
-A RADIX, --address-radix=RADIX | Print offsets in the given base. RADIX is one of o (octal, the default), d (decimal), x (hexadecimal), or n (none — omit the offset column entirely). |
Only the first character of the value is examined, so od -A n and od -Anone mean the same thing.
Which bytes to read #
By default od dumps the whole input. Two options narrow that to a window:
| Option | Effect |
|---|---|
-j BYTES, --skip-bytes=BYTES | Skip BYTES bytes of input before starting to dump. When several files are given, the skip runs across the concatenated stream. |
-N BYTES, --read-bytes=BYTES | Dump at most BYTES bytes, then stop. |
In both, BYTES is decimal by default, octal if it starts with 0, or hexadecimal if it starts with 0x. A suffix scales it: b = ×512, KB/K = ×1000 / ×1024, MB/M = ×1000² / ×1024², GB/G = ×1000³ / ×1024³.
Collapsing repeated lines #
When several output lines in a row would be identical, od prints the first one, then a single line containing just * to stand for the run, and resumes at the next line that differs. This keeps a dump of a large block of identical bytes (a file full of zeros, say) short.
| Option | Effect |
|---|---|
-v, --output-duplicates | Turn the collapsing off — print every line, including repeats, with no * markers. |
Output width #
| Option | Effect |
|---|---|
-w[BYTES], --width[=BYTES] | Print BYTES input bytes per output line. The default is 16; giving -w with no value uses 32. |
The width must be a whole multiple of the size of the largest format unit in play; if it isn't, od warns and rounds it to a usable value.
Finding printable strings #
| Option | Effect |
|---|---|
-S[BYTES], --strings[=BYTES] | Instead of dumping bytes, scan the input and print only runs of at least BYTES printable characters, one per line, each preceded by its offset. BYTES defaults to 3 when omitted. |
This turns od into a quick way to pull the human-readable text out of a binary file. The offset in front of each string honours -A (and is dropped entirely under -A n). -j and -N still apply, so you can restrict the scan to a window.
The traditional offset form #
od also accepts the historical calling convention, where an offset (and, in --traditional mode, a label) is given as a bare operand rather than an option:
od [named-format-options] [file] [[+]offset[.][b]]
od --traditional [options] [file] [[+]offset[.][b] [[+]label[.][b]]]
The offset says where in the input to begin, exactly like -j. It is read as octal by default, hexadecimal if prefixed with 0x, or decimal if it carries a trailing .; a trailing b multiplies it by 512. The optional leading + is allowed everywhere and is required when there is no filename (so od +20 reads standard input starting at octal offset 20).
The label (only in --traditional mode) is a second such number: the dump still begins at offset, but the offset column is displayed as if it started at label — useful for making a partial dump's offsets match the original file.
od only reads an operand as an offset in the plain form when none of -A, -j, -N, -t, -v, or -w are present. So if a real filename happens to look like a number, add a harmless option such as -j0 to force it to be treated as a filename.
A note on reading files #
od has no access rules of its own — it simply opens a file and reads its bytes. That read is subject to the same check as every read on Peios: whether you may open the file for reading is decided by the file's security descriptor (see Security descriptors). od never reveals bytes you could not already read another way; it only presents the bytes you can read in a different form.
Exit status #
| Code | Meaning |
|---|---|
0 | The input was dumped successfully. |
1 | A file could not be read, or an option, format, or offset was invalid. |
Transforming text
Peios / Using Peios / Peiosutils / Transforming text
This topic covers the commands that reshape text: putting lines in order, dropping duplicates, swapping characters, reflowing paragraphs to a width, counting what is there. Its companion, Viewing and joining text, covers printing and combining files; this topic is about changing the text itself.
The commands #
Ordering lines
| Command | Purpose |
|---|---|
sort | Sort lines — alphabetically, numerically, and many other ways. |
shuf | The opposite of sorting: put lines into a random order. |
tsort | Topological sort — order items so that each comes after the things it depends on. |
Filtering lines
| Command | Purpose |
|---|---|
uniq | Collapse or report adjacent repeated lines. |
Substituting characters
| Command | Purpose |
|---|---|
tr | Translate, squeeze, or delete individual characters. |
Whitespace
Reflowing and paginating
| Command | Purpose |
|---|---|
fmt | Reflow paragraphs to a target width. |
fold | Hard-wrap long lines at a fixed width. |
pr | Paginate and columnate text for printing. |
Indexing and counting
| Command | Purpose |
|---|---|
ptx | Produce a permuted index of the words in a file. |
wc | Count the lines, words, and bytes in a file. |
Two commands that need sorted input #
A theme worth knowing before you start: uniq only collapses adjacent duplicate lines, so duplicates scattered through a file are not caught unless the file is sorted first. sort and uniq are almost always used together — and sort -u does both jobs in one step.
Where to start #
sort is the workhorse of the topic and the one with the most depth — start there.
sort
Peios / Using Peios / Peiosutils / Transforming text
sort reads lines and writes them out in order.
sort [options] [file...]
$ sort names.txt
With no file, sort reads standard input. Given several files, it sorts all of their lines together as one stream. By default it compares lines as plain text, character by character.
How lines are compared #
The default text comparison is often not the order you want — 10 sorts before 9, and case matters. These options change the comparison:
| Option | Compares lines as… |
|---|---|
-n, --numeric-sort | numbers. 9 sorts before 10. |
-g, --general-numeric-sort | numbers, including scientific notation. Slower than -n; use it only when -n is not enough. |
-h, --human-numeric-sort | human-readable sizes — 2K before 1M before 3G. |
-M, --month-sort | month names — JAN before FEB. |
-V, --version-sort | version numbers — 1.2.10 after 1.2.9. |
-R, --random-sort | a random order (a shuffle that groups equal lines together). |
And these adjust what is compared:
| Option | Effect |
|---|---|
-f, --ignore-case | Treat lower and upper case as the same. |
-d, --dictionary-order | Consider only letters, digits, and blanks. |
-i, --ignore-nonprinting | Ignore non-printing characters. |
-b, --ignore-leading-blanks | Ignore blanks at the start of a line (or field). |
Sort order options #
| Option | Effect |
|---|---|
-r, --reverse | Reverse the result. |
-u, --unique | Output only the first line of each run of equal lines — sort and de-duplicate in one step. |
-s, --stable | Keep equal lines in their original relative order. |
Sorting by a field #
By default sort compares whole lines. -k sorts on a key — a chosen part of each line — which is what you need for tabular data.
A line is split into fields (by default at whitespace). A key is written FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]] — a start field, an optional start character within it, and an optional end. The single-letter comparison options above can be appended to a key to apply only to it.
$ sort -k 2 -n scores.txt # sort on the 2nd field, numerically
$ sort -t , -k 3 people.csv # sort a CSV on the 3rd field
| Option | Effect |
|---|---|
-k, --key=KEYDEF | Sort on the key KEYDEF. May be given more than once; keys are tried in order. |
-t, --field-separator=SEP | Use SEP to split fields, instead of the whitespace default. |
Checking and merging #
| Option | Effect |
|---|---|
-c, --check | Do not sort — just check whether the input is already sorted, and report the first line that is out of order. |
-C, --check=silent | Check, but report only through the exit status. |
-m, --merge | Treat the inputs as already-sorted files and merge them, which is faster than a full sort. |
Output and resources #
| Option | Effect |
|---|---|
-o, --output=FILE | Write to FILE instead of standard output. FILE may be one of the inputs. |
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
--parallel=N | Use N threads. |
-S, --buffer-size=SIZE | Set the size of the in-memory sort buffer. |
-T, --temporary-directory=DIR | Put temporary files in DIR rather than the default location. |
--files0-from=F | Take the list of input files from F, NUL-separated. |
--debug | Underline the part of each line that was actually used as the sort key — invaluable when a -k key is not behaving. |
Exit status #
| Code | Meaning |
|---|---|
0 | The lines were sorted — or, with -c/-C, the input was already sorted. |
1 | With -c/-C, the input was not sorted. |
2 | An error — a file could not be read, or an option was invalid. |
uniq
Peios / Using Peios / Peiosutils / Transforming text
uniq deals with repeated lines: it can collapse a run of identical lines into one, count them, or print only the ones that repeated.
uniq [options] [input [output]]
$ uniq events.txt
input and output may be given as operands; with neither, uniq reads standard input and writes standard output.
uniq only sees adjacent duplicates #
This is the one thing to know about uniq: it only compares each line with the one before it. It collapses adjacent repeats. Duplicate lines scattered through a file, with other lines between them, are not caught.
So uniq is almost always paired with sort, which brings every copy of a line together first:
$ sort events.txt | uniq
If collapsing duplicates is all you need, sort -u does both jobs in one command. Use uniq itself when you want what sort -u cannot do — counting repeats, or printing only the duplicated lines.
What to output #
By default uniq prints every line, with each run of adjacent duplicates reduced to a single copy. These options change that:
| Option | Effect |
|---|---|
-c, --count | Prefix each line with the number of times it occurred. |
-d, --repeated | Print only lines that were repeated — one copy of each. |
-D | Print all copies of every repeated line, not just one. |
-u, --unique | Print only lines that were not repeated. |
--group[=WHICH] | Print every line, with runs separated by a blank line. WHICH is separate, prepend, append, or both. |
What counts as "the same" #
| Option | Effect |
|---|---|
-i, --ignore-case | Treat lines differing only in letter case as the same. |
-f, --skip-fields=N | Ignore the first N fields when comparing. |
-s, --skip-chars=N | Ignore the first N characters when comparing. |
-w, --check-chars=N | Compare at most N characters of each line. |
Other options #
| Option | Effect |
|---|---|
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read or written. |
tr
Peios / Using Peios / Peiosutils / Transforming text
tr — "translate" — works on text one character at a time. It can replace characters with other characters, remove characters, or collapse runs of a character into one.
tr [options] set1 [set2]
tr reads standard input and writes standard output — it has no file arguments, so it always sits in a pipeline.
$ tr 'a-z' 'A-Z' < notes.txt # lower-case to upper-case
$ tr -d ' ' < spaced.txt # delete every space
$ tr -s ' ' < padded.txt # collapse runs of spaces to one
The three jobs #
What tr does depends on the options and how many sets you give it.
- Translate — give two sets. Each character in
set1is replaced by the character at the same position inset2.tr 'abc' 'xyz'turns everyaintox,bintoy,cintoz. - Delete (
-d) — give one set. Every character inset1is removed. - Squeeze (
-s) — each run of a repeated character that appears in the relevant set is collapsed to a single occurrence.
-d and -s can be combined: delete one set of characters, then squeeze another.
Writing a set #
A set is a string of characters, with a few shorthands:
| Notation | Means |
|---|---|
a-z | A range — every character from a to z. |
[:alpha:], [:digit:], [:space:], … | A named character class. |
[=c=] | An equivalence class — every character that sorts the same as c. |
[c*n] | The character c repeated n times — for padding a set to a length. |
\n, \t, \\, \NNN | Escapes — newline, tab, backslash, an octal byte. |
Options #
| Option | Effect |
|---|---|
-d, --delete | Delete the characters in set1 instead of translating. |
-s, --squeeze-repeats | Collapse each run of a repeated character listed in the last set into one. |
-c, -C, --complement | Operate on the complement of set1 — every character it does not list. |
-t, --truncate-set1 | Before translating, shorten set1 to the length of set2, rather than reusing set2's last character to cover the surplus. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A usage error — the wrong number of sets, or a malformed set. |
expand
Peios / Using Peios / Peiosutils / Transforming text
expand converts tab characters into spaces, so that text lines up the same way no matter what tab width something is viewed with.
expand [options] [file...]
$ expand source.txt > source-spaces.txt
A tab is not a fixed number of spaces — it jumps to the next tab stop, and where those stops are is a display setting. expand removes the ambiguity: it replaces each tab with exactly the number of spaces needed to reach the same column, baking the layout in. With no file, it reads standard input.
unexpand is the reverse operation.
Options #
| Option | Effect |
|---|---|
-t, --tabs=N | Set tab stops every N columns instead of the default 8. |
-t, --tabs=LIST | Given a comma-separated list, place tab stops at those explicit column positions. |
-i, --initial | Convert only the tabs that come before the first non-blank character on each line — leave tabs within the text alone. Useful for re-indenting code without disturbing aligned tabs inside it. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or a tab specification was invalid. |
unexpand
Peios / Using Peios / Peiosutils / Transforming text
unexpand is the reverse of expand: it converts runs of spaces back into tab characters.
unexpand [options] [file...]
$ unexpand spaced.txt > tabbed.txt
Where a run of spaces reaches a tab stop, unexpand replaces it with a tab. By default it only converts the leading spaces on each line — the indentation — and leaves spacing within the text alone. With no file, it reads standard input.
Options #
| Option | Effect |
|---|---|
-t, --tabs=N | Set tab stops every N columns instead of the default 8. A comma-separated list places stops at explicit column positions. Using -t also enables -a. |
-a, --all | Convert all runs of spaces that reach a tab stop, not only the leading indentation. |
--first-only | Convert only the leading run of blanks on each line. Overrides -a. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or a tab specification was invalid. |
fmt
Peios / Using Peios / Peiosutils / Transforming text
fmt reflows paragraphs: it rejoins the lines of a paragraph and re-breaks them so each comes out close to a target width. Words move freely between lines to make the result fit.
fmt [options] [file...]
$ fmt -w 72 draft.txt
fmt works on paragraphs — runs of non-blank lines separated by blank lines — and treats word boundaries as places it may break. The result reads naturally. This is what distinguishes it from fold, which simply cuts every line at a fixed column without regard to words.
With no file, fmt reads standard input.
Setting the width #
| Option | Effect |
|---|---|
-w, --width=WIDTH | Fill lines up to at most WIDTH columns. The default is 75. |
-g, --goal=WIDTH | Aim for WIDTH columns — the width fmt targets, while -w is the hard maximum. Defaults to about 93% of the maximum. |
Controlling the reflow #
| Option | Effect |
|---|---|
-s, --split-only | Only split lines that are too long; never join short lines together. |
-u, --uniform-spacing | Put exactly one space between words and two between sentences. |
-c, --crown-margin | Preserve the indentation of a paragraph's first two lines; indent the rest to match the second. |
-t, --tagged-paragraph | Like -c, but the first line must be indented differently from the second, or the two are treated as separate paragraphs. |
-q, --quick | Break lines faster, accepting a more ragged right edge. |
Selecting which lines to format #
| Option | Effect |
|---|---|
-p, --prefix=PREFIX | Reformat only lines that begin with PREFIX; reattach PREFIX afterwards. Useful for reflowing comment blocks in source code. |
-m, --preserve-headers | Detect and preserve mail-style header lines rather than reflowing them. |
--tab-width=N | Treat a tab as N columns when measuring line length. Tabs are kept in the output; this affects measurement only. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or an option value was invalid. |
fold
Peios / Using Peios / Peiosutils / Transforming text
fold breaks long lines so that none exceeds a fixed width. Every line longer than the limit is cut into pieces.
fold [options] [file...]
$ fold -w 80 wide.txt
By default fold wraps at 80 columns, and it cuts at exactly that column — even in the middle of a word. That hard cut is the difference between fold and fmt: fmt reflows paragraphs and breaks between words; fold just enforces a width, mechanically. Use fold when you need a guarantee that no line is wider than N; use fmt when you want the result to read well.
With no file, fold reads standard input.
Options #
| Option | Effect |
|---|---|
-w, --width=WIDTH | Wrap at WIDTH columns instead of 80. |
-s, --spaces | Break at a space within the width where one exists, rather than mid-word — a gentler wrap. |
-b, --bytes | Count bytes rather than display columns. Control characters such as tab and newline then count as ordinary bytes. |
-c, --characters | Count character positions rather than display columns. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or the width was invalid. |
pr
Peios / Using Peios / Peiosutils / Transforming text
pr prepares text for printing on paper. It breaks a file into pages, puts a header and a trailer on each, and can arrange the text into columns.
pr [options] [file...]
$ pr report.txt
Run plainly, pr divides the input into 66-line pages, each beginning with a five-line header — the date, the file name, and a page number — and ending with a trailing margin. With no file, it reads standard input.
Columns #
pr can lay text out in several columns per page.
| Option | Effect |
|---|---|
-COLUMN | Produce COLUMN columns per page (e.g. -3 for three). Text fills down the first column, then the next. |
-a, --across | Fill the columns across — line 1 in column 1, line 2 in column 2, and so on — rather than down. |
-m, --merge | Print several files side by side, one per column. |
-w, --width=WIDTH | Set the page width, for multi-column output. |
The header and the page #
| Option | Effect |
|---|---|
-h, --header=TEXT | Use TEXT in the header instead of the file name. |
-t, --omit-header | Print neither the header nor the trailer. |
-T, --omit-pagination | Drop the header, the trailer, and all pagination — just the text. |
-l, --length=LINES | Set the page length to LINES instead of 66. |
-o, --indent=N | Indent every line by N spaces. |
-D, --date-format=FORMAT | Format the header's date with FORMAT. |
-F, -f, --form-feed | Separate pages with a form-feed character rather than blank lines. |
Selecting and numbering #
| Option | Effect |
|---|---|
--pages=FIRST[:LAST] | Print only pages FIRST through LAST. |
-n, --number-lines[=SEP[WIDTH]] | Number every line. |
-N, --first-line-number=N | Begin line numbering at N. |
-d, --double-space | Double-space the output. |
Other options #
| Option | Effect |
|---|---|
-s, --separator-char[=CHAR] | Separate columns with a single CHAR (default tab) rather than padding with spaces. |
-S, --sep-string[=STRING] | Separate columns with STRING. |
-e, --expand-tabs[=CHAR[WIDTH]] | Expand input tabs to spaces. |
-J, --join-lines | Merge full lines, turning off width truncation. |
-r, --no-file-warnings | Do not warn when a file cannot be opened. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or an option was invalid. |
ptx
Peios / Using Peios / Peiosutils / Transforming text
ptx produces a permuted index — a concordance. For each significant word in the input, it prints a line showing that word together with the text around it, so the same passage appears once per keyword it contains.
ptx [options] [input...]
$ ptx manual.txt
A permuted index is the kind of index found at the back of a reference book: every keyword, in alphabetical order, each shown in its context. ptx builds one from plain text. With no file, it reads standard input.
Choosing the keywords #
| Option | Effect |
|---|---|
-W, --word-regexp=REGEXP | Treat anything matching REGEXP as a keyword. |
-b, --break-file=FILE | Take the set of word-break characters from FILE. |
-i, --ignore-file=FILE | Read a list of words to exclude from FILE. |
-o, --only-file=FILE | Index only the words listed in FILE. |
-f, --ignore-case | Fold case together when sorting. |
Output format #
| Option | Effect |
|---|---|
-w, --width=N | Set the output width, in columns. |
-g, --gap-size=N | Set the gap, in columns, between the output fields. |
-A, --auto-reference | Generate a reference (file name and line number) for each entry automatically. |
-r, --references | Treat the first field of each input line as a reference for that line. |
-R, --right-side-refs | Put references on the right, and exclude them from the width count. |
-F, --flag-truncation=STRING | Use STRING to mark where a line was truncated. |
-O, --format=roff | Emit the index as roff typesetting directives. |
-T, --format=tex | Emit the index as TeX typesetting directives. |
-M, --macro-name=NAME | Use NAME as the macro name in roff/TeX output. |
-G, --traditional | Behave like the older System V ptx, without the extended features. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or an option value was invalid. |
tsort
Peios / Using Peios / Peiosutils / Transforming text
tsort performs a topological sort. Given a set of "this must come before that" rules, it produces an order in which everything appears after the things it depends on.
tsort [file]
$ tsort dependencies.txt
With no file, tsort reads standard input.
The input #
tsort reads its input as a flat sequence of whitespace-separated tokens, taken in pairs. Each pair A B means "A must come before B" — an edge in a dependency graph.
compiler linker
linker installer
compiler installer
That input says the linker depends on the compiler, the installer on the linker, and the installer on the compiler. tsort reads the pairs and prints the items in a valid order:
compiler
linker
installer
A token paired with itself (A A) simply introduces A with no dependency — a way to list an item that nothing else mentions.
Cycles #
A topological order only exists if the dependencies form no cycle. If A must come before B and B must come before A, there is no valid order. tsort detects this, reports the loop it found, and exits with a failure status — it still prints an order, but the cycle means that order cannot satisfy every rule.
Where it is used #
tsort answers "what order do I do these in?" — building components in dependency order, scheduling tasks, sequencing anything where some steps must precede others. It is the dependency-aware counterpart to sort, which orders by value rather than by dependency.
Exit status #
| Code | Meaning |
|---|---|
0 | A valid order was produced. |
1 | The input contained a cycle, had an odd number of tokens, or could not be read. |
wc
Peios / Using Peios / Peiosutils / Transforming text
wc — "word count" — counts what is in a file: its lines, its words, and its bytes.
wc [options] [file...]
$ wc report.txt
214 1832 12044 report.txt
By default wc prints three numbers per file — lines, words, bytes — followed by the file name. Given several files, it adds a total line. With no file, it reads standard input.
Choosing what to count #
Pass any of these and wc prints only the counts you ask for, in the fixed order lines, words, characters/bytes, longest-line:
| Option | Counts |
|---|---|
-l, --lines | The number of lines. |
-w, --words | The number of words — runs of non-whitespace. |
-c, --bytes | The number of bytes. |
-m, --chars | The number of characters. This differs from -c for multi-byte text, where one character is several bytes. |
-L, --max-line-length | The length of the longest line. |
$ wc -l report.txt # just the line count
214 report.txt
Other options #
| Option | Effect |
|---|---|
--files0-from=F | Take the list of files to count from F, as NUL-terminated names. - reads the list from standard input. |
--total=WHEN | Control the total line: auto (the default — shown for more than one file), always, only (just the total, no per-file lines), or never. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was counted. |
1 | A file could not be read. |
shuf
Peios / Using Peios / Peiosutils / Transforming text
shuf shuffles: it outputs its input lines in a random order. Every possible ordering is equally likely.
shuf [options] [file]
shuf -e [options] arg...
shuf -i lo-hi [options]
$ shuf playlist.txt
shuf is the opposite of sort — instead of imposing an order, it removes one. With no file, it reads standard input.
Where the lines come from #
shuf has three ways of getting its input, one per usage form:
| Option | Input is… |
|---|---|
| (a file, or stdin) | the lines of the file. |
-e, --echo | the command-line arguments, each treated as one line. |
-i, --input-range=LO-HI | the integers from LO to HI, each treated as one line. |
$ shuf -e red green blue # shuffle three given words
$ shuf -i 1-100 # the numbers 1..100 in random order
Shaping the output #
| Option | Effect |
|---|---|
-n, --head-count=COUNT | Output at most COUNT lines — a random sample rather than the whole shuffle. |
-r, --repeat | Allow lines to be repeated, so output can be longer than the input. Pair with -n to set how many. |
-o, --output=FILE | Write to FILE instead of standard output. |
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
shuf -n 1 is a common idiom — pick one random line.
Reproducible shuffles #
A shuffle is random by default. To get the same shuffle every time — for a repeatable test, say — fix the randomness:
| Option | Effect |
|---|---|
--random-seed=STRING | Seed the shuffle with STRING. The same seed gives the same shuffle. |
--random-source=FILE | Take the random bytes from FILE. The same file gives the same shuffle. Cannot be combined with --random-seed. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A failure — the input could not be read, or -r was used with no lines to repeat. |
Output and evaluation
Peios / Using Peios / Peiosutils / Output and evaluation
This topic gathers the small, sharp commands that scripts are built from: the ones that print something, that read or set the environment, that evaluate an expression or a condition, and that simply succeed or fail.
The commands #
Producing text
| Command | Purpose |
|---|---|
echo | Print its arguments as a line of text. |
printf | Print text from a format string, with precise control over layout. |
yes | Print a line over and over, without stopping. |
seq | Print a sequence of numbers. |
tee | Copy standard input to standard output and to each named file. |
The environment
| Command | Purpose |
|---|---|
env | Run a command with a modified environment — or print the current one. |
printenv | Print environment variables. |
Numbers
| Command | Purpose |
|---|---|
numfmt | Convert numbers to and from human-readable forms (1.5G, 1000000). |
factor | Print the prime factors of a number. |
Evaluating
| Command | Purpose |
|---|---|
expr | Evaluate an arithmetic, string, or comparison expression. |
test | Evaluate a condition — a file check or a comparison — as a true/false result. |
Exit status
| Command | Purpose |
|---|---|
true and false | Do nothing, and succeed — or do nothing, and fail. |
A note on exit status #
Several commands here exist to be used for their exit status rather than their output. Every command, when it finishes, leaves behind a number: 0 means success, anything else means a kind of failure. A shell's if, &&, and || all act on that number.
test is the clearest case — it prints nothing and exists only to set an exit status from a condition. true and false are the most minimal: fixed success and fixed failure. Where this topic's pages give an exit-status table, that number is often the whole point of the command.
Where to start #
For everyday printing, echo; for anything that needs columns, padding, or precise formatting, printf.
echo
Peios / Using Peios / Peiosutils / Output and evaluation
echo prints its arguments, separated by single spaces, followed by a newline.
echo [options] [string...]
$ echo Hello, Peios
Hello, Peios
It is the simplest way to put a line of text on standard output — printing a message, or feeding a fixed string into a pipe.
Options #
| Option | Effect |
|---|---|
-n | Do not print the trailing newline. |
-e | Interpret backslash escape sequences in the arguments (see below). |
-E | Do not interpret escape sequences. This is the default. |
Escape sequences #
With -e, echo recognises these backslash sequences and prints the character they stand for:
| Sequence | Character |
|---|---|
\\ | A literal backslash. |
\n | Newline. |
\t | Horizontal tab. |
\r | Carriage return. |
\b | Backspace. |
\f | Form feed. |
\v | Vertical tab. |
\a | Alert (the terminal bell). |
\e | Escape. |
\0NNN | The byte with octal value NNN. |
\xHH | The byte with hexadecimal value HH. |
\c | Stop — produce no further output. |
echo and printf #
echo is fixed: arguments, spaces, a newline. The moment you need control over the output — columns, padding, a number formatted a particular way, output with no spaces between pieces — use printf instead. printf is also more predictable across environments, since echo's handling of escapes and -n varies.
A note on shells #
Many command shells provide their own built-in echo, and the shell's version is what runs when you type echo at a prompt. Built-in versions vary — especially in how they treat -n, -e, and escape sequences — so their behaviour may differ from what is described here. To be certain you are running this command rather than the shell built-in, invoke it by its full path.
Exit status #
| Code | Meaning |
|---|---|
0 | The output was written. |
1 | The output could not be written. |
printf
Peios / Using Peios / Peiosutils / Output and evaluation
printf prints text built from a format string. The format string is printed literally, except that escape sequences become characters and substitution fields are replaced by the arguments that follow.
printf format [argument...]
$ printf 'the letter %X comes before %X\n' 10 11
the letter A comes before B
Where echo prints arguments as-is, printf gives you control: padding, column widths, number bases, fixed decimal places. It is the command for output that has to line up or be exact.
printf writes only what the format string says — there is no automatic trailing newline. Include \n yourself where you want one.
Escape sequences #
The format string interprets these backslash sequences:
| Sequence | Character |
|---|---|
\\ | Backslash. |
\n \t \r | Newline, tab, carriage return. |
\b \f \v \a | Backspace, form feed, vertical tab, alert. |
\e | Escape. |
\NNN | The byte with octal value NNN. |
\xHH | The byte with hexadecimal value HH. |
\uHHHH, \UHHHHHHHH | A Unicode character by hexadecimal code point. |
%% | A literal %. |
Substitution fields #
A field begins with % and is replaced by the next argument, formatted accordingly.
| Field | Formats the argument as… |
|---|---|
%s | a string. |
%b | a string, with backslash escapes in it interpreted. |
%q | a string, quoted so it is safe to reuse as shell input. |
%c | a single character. |
%d, %i | a signed integer. |
%u | an unsigned integer. |
%x, %X | an unsigned integer in hexadecimal (lower- or upper-case). |
%o | an unsigned integer in octal. |
%f, %F | a decimal floating-point number. |
%e, %E | a number in scientific notation. |
%g, %G | whichever of decimal or scientific notation is shorter. |
Width and precision #
Between the % and the field letter you may put two numbers — %[width][.precision]field:
- width — the minimum number of columns. Output narrower than this is padded with leading spaces; a negative width pads on the right instead.
- precision — after a
.; its meaning depends on the field. For a string it is a maximum length; for an integer, a minimum digit count (zero-padded); for a float, the number of decimal places.
$ printf '%-10s %5.2f\n' apples 3.1
apples 3.10
Reusing the format string #
If more arguments are supplied than the format string has fields, printf repeats the format string until the arguments run out. If there are too few, the leftover fields default to an empty string (or 0 for a number).
$ printf '%s: %d\n' alpha 1 beta 2 gamma 3
alpha: 1
beta: 2
gamma: 3
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A missing operand, a bad format string, or a write failure. |
yes
Peios / Using Peios / Peiosutils / Output and evaluation
yes prints a line over and over, without stopping.
yes [string...]
$ yes
y
y
y
...
With no argument it prints y forever. Given arguments, it prints them — joined by spaces — as the repeated line instead.
$ yes retry
retry
retry
...
yes never stops on its own. It ends when something downstream closes the pipe, or when you interrupt it with Ctrl-C.
What it is for #
yes exists to answer prompts. An older interactive command that asks "are you sure? [y/n]" again and again can be fed a stream of confirmations:
$ yes | slow-interactive-command
Most modern commands have a proper option for this — rm -f, and similar — and that is the better way when it exists. yes is the fallback for a command that offers no such option. It is also a quick way to generate an endless stream of identical lines for a test.
Exit status #
| Code | Meaning |
|---|---|
0 | The output was closed normally. |
1 | A write error occurred. |
seq
Peios / Using Peios / Peiosutils / Output and evaluation
seq prints a sequence of numbers, one per line.
seq [options] last
seq [options] first last
seq [options] first increment last
$ seq 3
1
2
3
$ seq 2 2 10
2
4
6
8
10
The three forms control where the sequence starts and how it steps:
seq last— count from 1 up tolast.seq first last— count fromfirstup tolast.seq first increment last— count fromfirsttolast, stepping byincrement.
The increment may be negative, to count down, and the numbers may be fractional. seq is most often used to drive a loop a fixed number of times.
Options #
| Option | Effect |
|---|---|
-s, --separator=STRING | Separate the numbers with STRING instead of a newline. |
-t, --terminator=STRING | End the output with STRING instead of a newline. |
-w, --equal-width | Pad the numbers with leading zeros so they all have the same width. |
-f, --format=FORMAT | Format each number with a printf-style floating-point FORMAT. |
$ seq -s , 1 5
1,2,3,4,5
$ seq -w 8 10
08
09
10
-f takes a single floating-point conversion — see printf for the format syntax — and cannot be combined with -w.
Exit status #
| Code | Meaning |
|---|---|
0 | The sequence was printed. |
1 | An argument was not a valid number, the increment was zero, or no argument was given. |
env
Peios / Using Peios / Peiosutils / Output and evaluation
env runs a command with a changed environment — the set of NAME=VALUE variables a process inherits. Run with no command, it prints the current environment instead.
env [options] [NAME=VALUE]... [command [arg]...]
$ env LANG=C TZ=UTC my-program
$ env
PATH=/bin
HOME=/home/jack
...
How it works #
You give env a list of NAME=VALUE assignments, then a command. env applies the assignments to its own environment and then runs the command, which inherits the result. The assignments affect only that one run — your shell's environment is untouched.
This is the clean way to run something with a particular variable set, without changing it for everything else, and the standard way to run a command with one variable temporarily different.
With no command, env simply prints the environment it would have used — which, with no options, is the current one.
Building the environment #
| Option | Effect |
|---|---|
-i, --ignore-environment | Start from an empty environment, not the inherited one — so the command sees only what you assign. A bare - argument means the same. |
-u, --unset=NAME | Remove NAME from the environment. |
--file=FILE | Read NAME=VALUE lines from a .env-style FILE and apply them. |
The order is: start (inherited, or empty with -i), apply --file, apply --unset, apply the NAME=VALUE arguments.
Running the command #
| Option | Effect |
|---|---|
-C, --chdir=DIR | Change to DIR before running the command. |
--argv0=NAME | Run the command but present NAME as its zeroth argument. |
-S, --split-string=S | Split S into separate arguments. This exists for script interpreter lines, where everything after the interpreter arrives as one string. |
Signal handling #
env can adjust how the command treats signals before launching it:
| Option | Effect |
|---|---|
--ignore-signal[=SIG] | Set the named signals to be ignored. |
--default-signal[=SIG] | Reset the named signals to their default handling. |
--block-signal[=SIG] | Block delivery of the named signals while the command runs. |
--list-signal-handling | List the signal-handling changes the other options requested. |
Other options #
| Option | Effect |
|---|---|
-0, --null | When printing the environment, end each line with a NUL character instead of a newline. Not valid together with a command. |
-v, --debug | Print each processing step env performs. |
Exit status #
When env runs a command, it exits with that command's status. The exception is a failure in env itself:
| Code | Meaning |
|---|---|
| (command's own) | The command ran; this is its exit status. |
125 | env itself failed — for example, a bad option. |
126 | The command was found but could not be run. |
127 | The command was not found. |
printenv
Peios / Using Peios / Peiosutils / Output and evaluation
printenv prints environment variables.
printenv [options] [variable...]
Name one or more variables and printenv prints the value of each. Name none and it prints the whole environment, one NAME=VALUE pair per line.
$ printenv HOME
/home/jack
$ printenv
PATH=/bin
HOME=/home/jack
...
Options #
| Option | Effect |
|---|---|
-0, --null | End each output line with a NUL character instead of a newline — so values that themselves contain newlines can still be told apart. |
printenv and env #
Both can print the environment. printenv is the one built for reading it — it can pick out a single variable by name. env prints the environment too, but its real job is running a command with a modified one. Use printenv to look something up; use env to change something for a command.
Exit status #
| Code | Meaning |
|---|---|
0 | Every named variable was found and printed. |
1 | A named variable was not set. |
2 | A usage error. |
numfmt
Peios / Using Peios / Peiosutils / Output and evaluation
numfmt converts numbers between plain digits and human-readable forms — turning 1500000 into 1.5M, or 1.5M back into 1500000.
numfmt [options] [number...]
$ numfmt --to=si 1500000
1.5M
$ numfmt --from=si 1.5M
1500000
numfmt takes numbers as arguments, or — with none — reads them from standard input, which lets it rescale a column of numbers flowing through a pipe.
The direction of conversion #
| Option | Effect |
|---|---|
--from=UNIT | Parse input numbers that carry a UNIT suffix, scaling them up to plain digits. |
--to=UNIT | Scale output numbers down and add a UNIT suffix. |
A UNIT is one of:
| Unit | Suffixes mean |
|---|---|
none | No suffixes — a suffix is an error. The default. |
si | Powers of 1000: 1K = 1000, 1M = 1000000. |
iec | Powers of 1024: 1K = 1024, 1M = 1048576. |
iec-i | Powers of 1024 with a two-letter suffix: 1Ki = 1024, 1Mi = 1048576. |
auto | Accept any of the above on input, reading Ki/Mi as powers of 1024 and bare K/M as powers of 1000. |
Working on fields #
By default numfmt converts the whole input line. To convert numbers sitting in a column of wider text, name the field:
| Option | Effect |
|---|---|
--field=FIELDS | Convert only the numbers in these fields. FIELDS uses the same range syntax as cut — 3, 2-5, 2-. |
-d, --delimiter=X | Use X to separate fields instead of whitespace. |
--header[=N] | Pass the first N lines through unconverted, as a header. |
Shaping the output #
| Option | Effect |
|---|---|
--format=FORMAT | Format the number with a printf-style floating-point FORMAT, controlling width, padding, and precision. |
--padding=N | Pad the output to N columns — positive right-aligns, negative left-aligns. |
--grouping | Group digits according to the locale (for example, 1,000,000). |
--round=METHOD | Choose the rounding method used when scaling. |
--suffix=SUFFIX | Append SUFFIX to each result, and accept it on input. |
--from-unit=N / --to-unit=N | Set the unit size the input or output is counted in. |
Other options #
| Option | Effect |
|---|---|
--invalid=MODE | Choose what to do with input that is not a valid number: abort, fail, warn, or ignore. |
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
--debug | Print warnings about questionable input. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every number was converted. |
2 | An input was not a valid number, or an option was misused. |
expr
Peios / Using Peios / Peiosutils / Output and evaluation
expr evaluates a single expression and prints its value.
expr expression
$ expr 6 + 7
13
$ expr length "Peios"
5
Each part of the expression — every number, operator, and keyword — is a separate argument. That is why the spaces matter: expr 6+7 is one argument and not an expression, while expr 6 + 7 is three.
Arithmetic #
| Operator | Result |
|---|---|
ARG1 + ARG2 | Sum. |
ARG1 - ARG2 | Difference. |
ARG1 * ARG2 | Product. |
ARG1 / ARG2 | Integer quotient. |
ARG1 % ARG2 | Remainder. |
Comparison #
A comparison yields 1 for true and 0 for false. It is arithmetic when both sides are numbers, and lexical otherwise.
| Operator | True when… |
|---|---|
ARG1 = ARG2 | the two are equal. |
ARG1 != ARG2 | they are unequal. |
ARG1 < ARG2 | ARG1 is less. |
ARG1 <= ARG2 | ARG1 is less or equal. |
ARG1 > ARG2 | ARG1 is greater. |
ARG1 >= ARG2 | ARG1 is greater or equal. |
Logic #
| Operator | Result |
|---|---|
ARG1 | ARG2 | ARG1 if it is neither null nor 0; otherwise ARG2. |
ARG1 & ARG2 | ARG1 if neither argument is null or 0; otherwise 0. |
String operations #
| Form | Result |
|---|---|
length STRING | The number of characters in STRING. |
substr STRING POS LENGTH | LENGTH characters of STRING, counting POS from 1. |
index STRING CHARS | The position of the first of any of CHARS in STRING, or 0. |
STRING : REGEXP | Match REGEXP anchored at the start of STRING. Returns the matched length, or the text captured by \(…\). |
match STRING REGEXP | The same as STRING : REGEXP. |
Many of these operators — *, <, |, ( — are also meaningful to a shell, so quote or escape them so they reach expr intact.
Exit status #
expr's exit status reports on the value it computed:
| Code | Meaning |
|---|---|
0 | The result was neither null nor 0. |
1 | The result was null or 0. |
2 | The expression was syntactically invalid. |
3 | An error occurred during evaluation — such as division by zero. |
factor
Peios / Using Peios / Peiosutils / Output and evaluation
factor prints the prime factors of a number.
factor [options] [number...]
$ factor 360
360: 2 2 2 3 3 5
Each line of output is the original number, a colon, and its prime factors in increasing order, repeated as often as they divide in. Give several numbers and factor factors each; give none and it reads numbers from standard input.
Options #
| Option | Effect |
|---|---|
-h, --exponents | Write a repeated factor in exponent form — p^e — instead of repeating it. |
$ factor -h 360
360: 2^3 3^2 5
Exit status #
| Code | Meaning |
|---|---|
0 | Every number was factored. |
1 | An input was not a valid positive integer. |
test
Peios / Using Peios / Peiosutils / Output and evaluation
test evaluates a single condition and reports whether it is true or false. It prints nothing — the answer is its exit status: 0 for true, 1 for false. That makes it the command a shell's if, while, &&, and || are built on.
test expression
[ expression ]
The two forms are the same command. [ is test under another name; when invoked as [, it requires a closing ] as its last argument. [ -f notes.txt ] and test -f notes.txt are identical.
$ test -f notes.txt && echo "the file exists"
File tests #
Existence and type #
| Test | True when the file… |
|---|---|
-e FILE | exists. |
-f FILE | exists and is a regular file. |
-d FILE | exists and is a directory. |
-L FILE, -h FILE | exists and is a symbolic link. |
-p FILE | exists and is a named pipe (FIFO). |
-S FILE | exists and is a socket. |
-b FILE | exists and is a block device. |
-c FILE | exists and is a character device. |
-s FILE | exists and is not empty. |
Access #
These three tests ask whether you may actually use the file:
| Test | True when… |
|---|---|
-r FILE | you may read the file. |
-w FILE | you may write the file. |
-x FILE | you may execute the file, or search the directory. |
On Peios these are real access checks. test asks the kernel whether your token is granted the right in question — a live check against the file's security descriptor, the same decision any actual read, write, or execute would face. The answer is therefore honest: test -w FILE is true exactly when a write would be permitted.
-x on a regular file means "you are permitted to execute it" — which is a different question from whether the file is an executable. A file can be runnable code and still fail -x for you, and the two are decided separately; see Access decisions.
Comparing two files #
| Test | True when… |
|---|---|
FILE1 -nt FILE2 | FILE1 is newer than FILE2. |
FILE1 -ot FILE2 | FILE1 is older than FILE2. |
FILE1 -ef FILE2 | both name the same file (same device and inode). |
Other file tests #
| Test | True when the file… |
|---|---|
-t FD | file descriptor FD is open on a terminal. |
-N FILE | has been modified since it was last read. |
-O FILE, -G FILE | carries an owner-id / group-id field matching the caller's. |
-g FILE, -u FILE, -k FILE | has its set-group-id, set-user-id, or sticky inode bit set. |
The last two rows probe decorative inode fields. The Peios access model does not consult them — they are stored on the inode and reported for completeness, but they carry no authority. For a true picture of what may be done to a file, use the access tests -r, -w, -x above, not -O, -g, or -u.
String tests #
| Test | True when… |
|---|---|
-n STRING | STRING is not empty. A bare STRING means the same. |
-z STRING | STRING is empty. |
STRING1 = STRING2 | the strings are equal. |
STRING1 != STRING2 | the strings are unequal. |
STRING1 < STRING2 | STRING1 sorts before STRING2. |
STRING1 > STRING2 | STRING1 sorts after STRING2. |
Integer comparisons #
| Test | True when… |
|---|---|
A -eq B | A equals B. |
A -ne B | A does not equal B. |
A -lt B | A is less than B. |
A -le B | A is less than or equal to B. |
A -gt B | A is greater than B. |
A -ge B | A is greater than or equal to B. |
Combining conditions #
| Form | Result |
|---|---|
! EXPRESSION | The negation. |
( EXPRESSION ) | Grouping — escape the parentheses so the shell does not take them. |
EXPR1 -a EXPR2 | True when both are true. |
EXPR1 -o EXPR2 | True when either is true. |
-a and -o are genuinely ambiguous to parse and are best avoided. Combine separate test calls with the shell's own && and || instead:
test -f notes.txt && test -r notes.txt
A note on shells #
Many command shells provide their own built-in test and [, and the shell's version is what runs when you type test at a prompt or in a script. A built-in may not behave as described here — in particular, the access tests -r, -w, and -x are the real access checks described above only when this command runs. To be certain you are running this command rather than the shell built-in, invoke it by its full path.
Exit status #
| Code | Meaning |
|---|---|
0 | The expression was true. |
1 | The expression was false. |
2 | The expression was malformed. |
true and false
Peios / Using Peios / Peiosutils / Output and evaluation
true and false do nothing at all. Their only product is an exit status: true always succeeds, false always fails.
true
false
$ true ; echo $?
0
$ false ; echo $?
1
true exits 0. false exits 1. They take no arguments that matter, produce no output, and have no effect on anything.
What they are for #
A command that is only an exit status is useful as a fixed value in places that expect a command:
- An always-true or always-false loop.
while trueis the standard way to write a loop that runs until something inside it breaks out. - A deliberate placeholder. Setting a configurable command to
truemakes that step do nothing and "succeed";falsemakes it a step that always fails. - A known result in a test. When a script or a condition needs a guaranteed pass or fail to check against,
trueandfalseprovide it.
They are the simplest commands there are — and the overview's note on exit status is the whole idea behind them.
Exit status #
| Code | Meaning |
|---|---|
0 | Returned by true, always. |
1 | Returned by false, always. |
tee
Peios / Using Peios / Peiosutils / Output and evaluation
tee reads standard input and writes it, unchanged, to standard output and to each file you name. It is the T-junction of a pipeline: the data keeps flowing down the pipe while a copy is also captured on disk.
tee [options] [file...]
$ echo saved | tee note.txt
saved
$ cat note.txt
saved
The name is the shape of the letter T — one stream in, the same stream out two ways. That makes tee the tool for the moment you want to both see (or keep piping) some data and keep it at the same time.
Writing to several places at once #
Every byte that arrives on standard input is written to standard output and to each named file. Name more than one file and each one receives a full, identical copy:
$ producer | tee a.log b.log c.log | consumer
Here producer's output reaches consumer down the pipe exactly as if tee were not there, and a.log, b.log, and c.log each end up holding the same complete copy of it. With no files named, tee copies input straight to standard output and nothing else — a plain pass-through.
tee does not buffer between reads: it writes each chunk out as it is read, so a file being written by tee fills up as the data flows, rather than all at once when the input ends.
Overwrite or append #
By default, each named file is truncated — opened fresh, so any existing contents are discarded before the new data is written. A file that does not exist is created.
With -a (--append), tee appends to each named file instead: existing contents are kept and the new data is added to the end. A file that does not exist is still created.
$ echo first | tee log.txt # log.txt now holds "first"
$ echo second | tee -a log.txt # log.txt now holds "first" then "second"
Creating a file inherits a security descriptor #
When tee creates a new output file — a named file that did not already exist — that file needs a security descriptor, and it inherits one from the directory it is created in. This is the same creation-inherits-a-descriptor rule that governs every file-creating tool on Peios; see Files and directories for the shared model.
With -a, when the named file already exists, tee opens that existing file to append and creates nothing, so no new descriptor is involved. Whether opening a file to write succeeds — whether creating a new one or writing an existing one — is decided by the normal access check against the relevant directory or file. tee reports a file it cannot open and, by default, carries on with the outputs it can write.
Files named - #
tee gives no special meaning to -. A file argument of - refers to a file literally named - in the current directory; it is opened, created, and written exactly like any other named file. It is not a stand-in for standard output — standard output is always written and needs no naming.
Ignoring interrupts #
With -i (--ignore-interrupts), tee ignores the interrupt signal (the one a terminal sends on Ctrl-C). This lets tee keep copying to its files even as an interrupt tears down the rest of a pipeline, so the capture is not cut short.
Behaviour on write errors #
By default, if a write to one output fails, tee reports it and stops writing to that output, but keeps writing to the others; a broken-pipe error on standard output is treated quietly. Two options change this policy.
-p sets write-error behaviour so that errors on a pipe (a downstream reader that has gone away) are ignored while other write errors are still reported.
--output-error[=MODE] sets the policy explicitly. Given on its own, with no =MODE, it selects warn-nopipe. The modes are:
| Mode | Behaviour |
|---|---|
warn | Warn on a write error to any output, including a broken pipe, and continue with the remaining outputs. |
warn-nopipe | Warn on a write error that is not a pipe error, and continue. Broken-pipe errors are ignored. This is the mode selected by a bare --output-error. |
exit | Exit on a write error to any output, including a broken pipe. |
exit-nopipe | Exit on a write error that is not a pipe error. Broken-pipe errors are ignored. |
Options #
| Option | Effect |
|---|---|
-a, --append | Append to each named file rather than truncating it. |
-i, --ignore-interrupts | Ignore the interrupt signal. |
-p | Ignore errors from writing to a broken pipe, still reporting other write errors. |
--output-error[=MODE] | Set the write-error policy: warn, warn-nopipe, exit, or exit-nopipe. A bare --output-error means warn-nopipe. |
Exit status #
| Code | Meaning |
|---|---|
0 | The input was copied to standard output and every named file without error. |
1 | A file could not be opened, a write failed under a policy that reports or exits on it, or the input could not be read. |
Hashing and encoding
Peios / Using Peios / Peiosutils / Hashing and encoding
This topic covers two related jobs. Hashing reduces a file to a short fixed-size value — a checksum — that can be used to tell whether the file has changed. Encoding rewrites binary data as plain text so it can travel safely through channels that only handle text.
The commands #
Checksums and hashes
| Command | Purpose |
|---|---|
| checksum commands | md5sum, sha1sum, sha256sum, and the rest — one command per hash algorithm. |
cksum | The general checksum tool — any of the algorithms, selected by an option. |
sum | A small, legacy block-checksum. |
Encoding
| Command | Purpose |
|---|---|
base32 and base64 | Encode binary data as text using the base32 or base64 alphabet, and decode it back. |
basenc | The general encoder — base64, base32, base16, and several more, selected by an option. |
What these commands are not #
It is worth being clear up front, because both jobs are easy to mistake for something they are not.
A hash is not encryption. Hashing is one-way: a checksum tells you whether data has changed, but the original cannot be recovered from it. It protects against corruption and detects tampering — it does not keep anything secret.
Encoding is not encryption either. base64 rewrites data into a text-safe form, but anyone can decode it straight back — it is a change of representation, not of secrecy. Encoding something does not protect it.
Neither hashing nor encoding hides data. They are about integrity and transport, not confidentiality.
Where to start #
To verify a file you downloaded against a published checksum, read the checksum commands. To turn binary data into something safe to paste into text, read base32 and base64.
Checksum commands
Peios / Using Peios / Peiosutils / Hashing and encoding
This page covers a family of commands that all work the same way and differ only in which hash algorithm they use:
| Command | Algorithm | Digest size |
|---|---|---|
md5sum | MD5 | 128-bit |
sha1sum | SHA-1 | 160-bit |
sha224sum | SHA-224 | 224-bit |
sha256sum | SHA-256 | 256-bit |
sha384sum | SHA-384 | 384-bit |
sha512sum | SHA-512 | 512-bit |
b2sum | BLAKE2b | up to 512-bit |
sha256sum [options] [file...]
Everything below is written with sha256sum, but applies to every command in the table.
Computing a checksum #
Run plainly, the command prints the hash of each file, followed by the file name:
$ sha256sum installer.iso
9f86d0818884...b1a5 installer.iso
With no file, it reads standard input. The hash is a fingerprint of the file's contents: change a single byte and the hash changes completely.
Verifying with a checksum #
The everyday use is verification — confirming a file is exactly what it should be, usually a download.
First, the file's publisher computes a checksum and publishes it, often in a file:
$ sha256sum installer.iso > installer.iso.sha256
Then anyone with the file and that checksum file can verify:
$ sha256sum -c installer.iso.sha256
installer.iso: OK
-c reads each hash filename line, recomputes the hash, and reports OK or FAILED. A FAILED means the file is not the one the checksum was made from — corrupted in transit, or altered.
| Option | Effect |
|---|---|
-c, --check | Read checksums from the given files and verify them. |
--ignore-missing | In check mode, do not fail over files that are listed but absent. |
--quiet | In check mode, print nothing for files that pass — only failures. |
--status | In check mode, print nothing at all; report only through the exit status. |
-w, --warn | Warn about improperly formatted lines in the checksum file. |
--strict | In check mode, fail if any checksum line is malformed. |
Output format #
| Option | Effect |
|---|---|
--tag | Produce a tagged line — SHA256 (file) = hash — that records which algorithm was used. |
--untagged | Produce the plain hash file line. This is the default. |
-z, --zero | End each output line with a NUL character instead of a newline. |
-b, --binary | Note the file as read in binary mode. |
-t, --text | Note the file as read in text mode. |
b2sum additionally accepts -l, --length=BITS to produce a shorter BLAKE2b digest.
A note on choosing an algorithm #
These commands detect change — but not all of them resist a deliberate attempt to forge a match. MD5 and SHA-1 can be defeated by an attacker who wants two different files to share a checksum. They are still fine for catching accidental corruption, but for verifying that a file has not been tampered with, use a SHA-2 command (sha256sum and up) or b2sum.
Exit status #
| Code | Meaning |
|---|---|
0 | The checksums were computed, or — in check mode — every file verified. |
1 | In check mode, a file failed verification. |
2 | An error — a file could not be read, or a checksum file was malformed. |
cksum
Peios / Using Peios / Peiosutils / Hashing and encoding
cksum is the general checksum command. Where each of the checksum commands is fixed to one algorithm, cksum does any of them — the algorithm is an option.
cksum [options] [file...]
$ cksum installer.iso
3915528286 372736000 installer.iso
$ cksum -a sha256 installer.iso
9f86d0818884...b1a5 installer.iso
With no -a, cksum computes a CRC checksum and prints the CRC value, the file's byte count, and the name. With -a, it behaves like the corresponding dedicated command.
Choosing the algorithm #
| Option | Effect |
|---|---|
-a, --algorithm=NAME | Use the named algorithm. |
NAME may be:
| Name | Algorithm |
|---|---|
crc | The default CRC. |
crc32b | A CRC-32 variant. |
md5 | MD5 — as md5sum. |
sha1 | SHA-1 — as sha1sum. |
sha224, sha256, sha384, sha512 | The SHA-2 family — as the matching sha*sum. |
sha3 | SHA-3. |
blake2b | BLAKE2b — as b2sum. |
sm3 | The SM3 hash. |
sysv, bsd | The legacy block checksums — as sum. |
crc, crc32b, sha3, and sm3 are available only through cksum — there is no dedicated command for them.
Verifying #
cksum checks files the same way the dedicated commands do:
| Option | Effect |
|---|---|
-c, --check | Read checksums and verify the files against them. |
--ignore-missing | Do not fail over listed files that are absent. |
--quiet | Print nothing for files that pass. |
--status | Print nothing; report only through the exit status. |
-w, --warn | Warn about malformed checksum lines. |
--strict | Fail on a malformed checksum line. |
Output format #
| Option | Effect |
|---|---|
--tag | Produce a tagged ALGORITHM (file) = hash line. The default for most algorithms. |
--untagged | Produce a plain hash file line. |
--raw | Output the raw binary digest, with nothing else. |
--base64 | Print the digest in base64 rather than hexadecimal. |
-l, --length=BITS | For an algorithm that supports it, produce a shorter digest. |
-z, --zero | End each output line with a NUL character. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success — or, in check mode, every file verified. |
1 | In check mode, a file failed verification. |
2 | An error — a file could not be read, or an option was invalid. |
sum
Peios / Using Peios / Peiosutils / Hashing and encoding
sum computes a small, old-style checksum of a file and reports it along with the file's size in blocks.
sum [options] [file...]
$ sum archive.tar
12345 84 archive.tar
The output is the checksum, the block count, and the file name. With no file, sum reads standard input.
Which algorithm #
sum predates the modern hashes and offers two historical algorithms:
| Option | Algorithm |
|---|---|
-r | The BSD checksum, counted in 1 KiB blocks. This is the default. |
-s, --sysv | The System V checksum, counted in 512-byte blocks. |
When to use it #
sum exists for compatibility — for reading checksums produced by old tools, and for scripts that still expect its output. Its checksum is short and weak: it catches some accidental corruption, but it is easily fooled and gives no protection against tampering.
For anything new, do not use sum. Use a checksum command such as sha256sum, or cksum — both of which cksum can still produce, via cksum -a bsd and cksum -a sysv, if you need the legacy values.
Exit status #
| Code | Meaning |
|---|---|
0 | The checksum was computed. |
1 | A file could not be read. |
base32 and base64
Peios / Using Peios / Peiosutils / Hashing and encoding
base32 and base64 encode binary data as plain text — and decode it back. They are the same command with one difference: the alphabet they use.
base32 [options] [file]
base64 [options] [file]
$ echo Peios | base64
UGVpb3MK
$ echo UGVpb3MK | base64 -d
Peios
What encoding is for #
Some channels only carry text safely — they mangle or reject arbitrary bytes. Encoding rewrites binary data using a small, safe set of characters, so it can pass through unharmed; decoding reverses it exactly. base64 uses 64 characters and is the more compact; base32 uses 32 and is more robust where case might not survive. With no file, both read standard input.
Encoding is not encryption — anyone can decode the result. See the overview.
Options #
By default these commands encode. The options below apply identically to both.
| Option | Effect |
|---|---|
-d, --decode | Decode instead of encode. |
-i, --ignore-garbage | When decoding, skip any characters that are not part of the alphabet, rather than failing on them. |
-w, --wrap=COLS | When encoding, wrap the output to lines of COLS characters. The default is 76; -w 0 disables wrapping and produces one unbroken line. |
When decoding, newlines in the input are always tolerated; -i is for other stray characters.
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or — when decoding — the input was not valid for the alphabet. |
basenc
Peios / Using Peios / Peiosutils / Hashing and encoding
basenc is the general encoding command. Where base32 and base64 each do one encoding, basenc does many — the encoding is chosen by an option.
basenc encoding [options] [file]
$ echo Peios | basenc --base16
5065696F730A
Every run of basenc must name an encoding. With no file, it reads standard input.
The encodings #
| Option | Encoding |
|---|---|
--base64 | Standard base64 — the same as the base64 command. |
--base64url | Base64 with a file- and URL-safe alphabet. |
--base32 | Standard base32 — the same as the base32 command. |
--base32hex | Base32 with the extended-hex alphabet. |
--base16 | Hexadecimal. |
--base2msbf | A bit string, most-significant bit first. |
--base2lsbf | A bit string, least-significant bit first. |
--z85 | A compact, ASCII85-style encoding. When encoding, the input length must be a multiple of 4; when decoding, a multiple of 5. |
--base58 | Base58 — an alphabet chosen so its characters are not visually confusable. |
Options #
By default basenc encodes. These options apply to whichever encoding you chose:
| Option | Effect |
|---|---|
-d, --decode | Decode instead of encode. |
-i, --ignore-garbage | When decoding, skip characters that are not part of the alphabet. |
-w, --wrap=COLS | When encoding, wrap output to lines of COLS characters. -w 0 disables wrapping. |
basenc and the dedicated commands #
For plain base64 or base32, base64 and base32 are the shorter way to ask. Use basenc when you need an encoding the dedicated commands do not offer — hex, URL-safe base64, the bit-string forms, z85, or base58.
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | No encoding was named, a file could not be read, or the input was not valid for the chosen encoding. |
System and processes
Peios / Using Peios / Peiosutils / System and processes
This topic gathers the commands that deal with the running system rather than with files: reading the time, asking what hardware and system you are on, launching another command under some constraint, signalling a process, working with the terminal.
The commands #
Time
| Command | Purpose |
|---|---|
date | Print — or set — the system date and time. |
sleep | Pause for a given length of time. |
uptime | Show how long the system has been running. |
Running a command under a constraint
| Command | Purpose |
|---|---|
timeout | Run a command, and kill it if it runs too long. |
nohup | Run a command so it survives the terminal closing. |
nice | Run a command at an adjusted scheduling priority. |
stdbuf | Run a command with altered stream buffering. |
chroot | Run a command with a different directory as its root. |
Signalling processes
| Command | Purpose |
|---|---|
kill | Send a signal to a process. |
System identity and capacity
| Command | Purpose |
|---|---|
uname | Print system information — the OS, the kernel, the machine. |
arch | Print the machine's hardware architecture. |
hostname | Display — or set — the system's host name. |
hostid | Print the host's numeric identifier. |
nproc | Print how many processor cores are available. |
Who you are
| Command | Purpose |
|---|---|
whoami | Print the name of the user a process is running as. |
id | Print the user and group identity of a process, or of a named user. |
groups | Print the groups a user belongs to. |
logname | Print the login name of the user a process is running as. |
These report the projection of a token onto POSIX user and group numbers, which is what Linux programs see. The numbers grant nothing on their own — id explains what they can and cannot say, and token shows the identity underneath them.
Storage and terminal
| Command | Purpose |
|---|---|
sync | Flush cached writes out to persistent storage. |
tty | Print the name of the terminal on standard input. |
stty | Display or change terminal settings. |
A note on changing the system #
Most commands here only report. A few can change the running system — date can set the clock, hostname can set the host name. Changing system-wide state is a privileged operation: it succeeds only for a caller whose token holds the right to do it, and is refused otherwise. The pages for those commands say so where it applies, and Privileges is the full picture.
Where to start #
uname answers "what am I running on?". kill is the one to read carefully — sending the wrong signal to the wrong process is a quick way to lose work.
date
Peios / Using Peios / Peiosutils / System and processes
date prints the current date and time. With the right option, it can also set the system clock, display some other moment, or format a date however you need.
date [options] [+format]
$ date
Sun 17 May 2026 14:32:08 BST
$ date +%Y-%m-%d
2026-05-17
Formatting the output #
A +format argument controls the output. The format string is printed literally, except for % sequences, each of which is replaced by a piece of the date. The common ones:
| Sequence | Is replaced by |
|---|---|
%Y %m %d | Year, month, day. |
%H %M %S | Hour, minute, second. |
%F | The full date — same as %Y-%m-%d. |
%T | The time — same as %H:%M:%S. |
%A %a | Weekday name, full and abbreviated. |
%B %b | Month name, full and abbreviated. |
%j | Day of the year. |
%s | Seconds since 1970-01-01 UTC. |
%z %Z | Numeric and named time zone. |
%% | A literal %. |
The full set is long; date --help lists every sequence with an example.
Displaying a different moment #
| Option | Effect |
|---|---|
-d, --date=STRING | Show the time described by STRING — "yesterday", "next Friday", "@1615432800" — instead of now. |
-r, --reference=FILE | Show FILE's last modification time. |
-f, --file=DATEFILE | Like -d, but once for each line of DATEFILE. |
-u, --universal | Work in Coordinated Universal Time (UTC) rather than the local zone. |
Standard formats #
| Option | Output |
|---|---|
-I, --iso-8601[=FMT] | ISO 8601 — FMT is date, hours, minutes, seconds, or ns. |
-R, --rfc-email | The format used in email headers. |
--rfc-3339=FMT | RFC 3339 — FMT is date, seconds, or ns. |
Setting the clock #
| Option | Effect |
|---|---|
-s, --set=STRING | Set the system clock to the time described by STRING. |
Setting the clock changes state for the whole system, so it is a privileged operation — it succeeds only for a caller whose token carries the right to set the time, and is refused otherwise. See Privileges.
Exit status #
| Code | Meaning |
|---|---|
0 | The date was printed or set. |
1 | An invalid date or format, or the clock could not be set. |
sleep
Peios / Using Peios / Peiosutils / System and processes
sleep does nothing for a given length of time, then exits.
sleep number[suffix]...
$ sleep 5 # pause for five seconds
$ sleep 1.5h # pause for an hour and a half
It is used to space things out — a pause between the steps of a script, a delay before a retry.
Specifying the duration #
number may be a whole number or a fraction. A suffix gives the unit:
| Suffix | Unit |
|---|---|
s | Seconds. This is the default if no suffix is given. |
m | Minutes. |
h | Hours. |
d | Days. |
Given more than one argument, sleep waits for the sum of them — sleep 1m 30s pauses for ninety seconds.
Exit status #
| Code | Meaning |
|---|---|
0 | The full time elapsed. |
1 | A bad argument, or sleep was interrupted before the time was up. |
timeout
Peios / Using Peios / Peiosutils / System and processes
timeout runs a command with a time limit. If the command finishes in time, nothing special happens. If it is still running when the limit is reached, timeout kills it.
timeout [options] duration command...
$ timeout 30s slow-fetch https://example.com
It is the guard against a command that might hang — a network fetch, a script that could loop forever.
The duration #
duration is a number with an optional unit suffix: s for seconds (the default), m for minutes, h for hours, d for days. A duration of 0 disables the limit.
How the command is stopped #
When the limit is reached, timeout sends the command a TERM signal — a request to shut down cleanly. A well-behaved command stops there. A command that ignores TERM keeps running, which is what -k is for.
| Option | Effect |
|---|---|
-s, --signal=SIGNAL | Send SIGNAL instead of TERM when the limit is reached. |
-k, --kill-after=DURATION | If the command is still running DURATION after the first signal, send it a KILL signal — which cannot be ignored. |
--preserve-status | Exit with the command's own status even when it timed out. |
--foreground | Allow the command to keep reading from the terminal; do not time out the command's own children. |
-v, --verbose | Report to standard error whenever a signal is sent. |
Exit status #
timeout passes through the command's exit status when the command finishes on its own. Otherwise:
| Code | Meaning |
|---|---|
| (command's own) | The command finished within the limit. |
124 | The command timed out. |
125 | timeout itself failed. |
126 | The command was found but could not be run. |
127 | The command was not found. |
137 | The command was ended by a KILL signal. |
nohup
Peios / Using Peios / Peiosutils / System and processes
nohup runs a command so that it survives the terminal closing. Normally, when a terminal session ends, the system sends a hangup signal to the commands running under it, and they stop. A command started with nohup ignores that signal and keeps running.
nohup [options] command [arg]...
$ nohup long-build &
It is how you start something that needs to outlast your session — a long build, a background job — without it being killed the moment you disconnect.
Where the output goes #
A command run with nohup is meant to outlive the terminal, so nohup cannot leave its streams attached to one. It redirects any stream that is still connected to a terminal:
- Standard input, if it is a terminal, is replaced with an empty source — the command reads nothing.
- Standard output, if it is a terminal, is appended to a file named
nohup.outin the current directory. If that file cannot be opened there,nohupfalls back tonohup.outin your home directory. - Standard error, if it is a terminal, is sent to wherever standard output now goes.
Streams that you have already redirected yourself — to a file, or a pipe — are left as they are.
How nohup.out is secured #
When nohup has to create nohup.out, that file is a new file and needs a security descriptor. nohup does not let it inherit one from the directory. It creates nohup.out with a deliberate, locked descriptor: full access for the owner, and no one else.
The reasoning is that nohup.out captures whatever the command writes, which the person running it has not chosen to share. A file that appears as a side effect should not be more open than its creator intended — so nohup gives it the safe default of owner-only, rather than whatever the surrounding directory would have handed down.
| Option | Effect |
|---|---|
--sddl=SDDL | Create nohup.out with the security descriptor given as an SDDL string, instead of the owner-only default. |
--sddl only matters when nohup actually creates the file. If nohup.out already exists, nohup appends to it and leaves its existing security alone.
Exit status #
When nohup runs a command, it exits with that command's status once it finishes. The exception is a failure in nohup itself:
| Code | Meaning |
|---|---|
| (command's own) | The command ran; this is its exit status. |
125 | nohup could not set things up — it could not detach from the terminal, or could not open nohup.out anywhere. The command was not run. |
nice
Peios / Using Peios / Peiosutils / System and processes
nice runs a command at an adjusted scheduling priority — making it more, or less, willing to give the processor up to other work.
nice [options] [command [arg]...]
$ nice -n 15 big-batch-job
With no command, nice prints the current niceness.
Niceness #
A process has a niceness — a number from -20 to 19:
- A high niceness (toward 19) means the process is "nicer" to others — it yields the processor readily. Good for background work that should not get in the way.
- A low niceness (toward -20) means the process is favoured — it gets the processor more often.
By default nice adds 10 to the niceness, making the command run in the background more gracefully.
| Option | Effect |
|---|---|
-n, --adjustment=N | Add N to the niceness instead of 10. N may be negative. |
Raising a command's niceness — running it lower priority — is always allowed. Lowering the niceness, to claim a higher priority than normal, is a privileged request and may be refused; when it is, nice warns and runs the command at the priority it was permitted.
Exit status #
When nice runs a command, it exits with that command's status. The exception is a failure in nice itself:
| Code | Meaning |
|---|---|
| (command's own) | The command ran; this is its exit status. |
0 | No command was given; the niceness was printed. |
125 | nice itself failed — for example, a bad adjustment value. |
126 | The command was found but could not be run. |
127 | The command was not found. |
kill
Peios / Using Peios / Peiosutils / System and processes
kill sends a signal to a process. Despite the name, a signal is not always fatal — it is a message, and some signals ask a process to do something other than stop.
kill [options] pid...
$ kill 4821 # ask process 4821 to terminate
$ kill -KILL 4821 # force it to stop
A pid is a process identifier — the number a running process is known by.
Signals #
With no option, kill sends TERM — a polite request to shut down, which a well-behaved process honours by cleaning up and exiting. The signals you will use most:
| Signal | Meaning |
|---|---|
TERM | Terminate. The default — a request to stop cleanly. |
KILL | Stop immediately. Cannot be caught or ignored, so it cannot be refused — but the process gets no chance to clean up. The last resort. |
HUP | Hang up. Conventionally tells a long-running service to reload its configuration. |
INT | Interrupt — the signal a terminal sends on Ctrl-C. |
STOP / CONT | Suspend the process / resume a suspended one. |
| Option | Effect |
|---|---|
-s, --signal=SIGNAL | Send SIGNAL instead of TERM. |
-SIGNAL | A shorthand — -KILL or -9 both send KILL. |
-l, --list | List the signal names. |
-L, --table | List the signals as a table of numbers and names. |
Use KILL only when TERM has failed. A process killed outright cannot save its work or release what it holds.
Exit status #
| Code | Meaning |
|---|---|
0 | Every signal was sent. |
1 | A signal could not be sent — a process that does not exist, or one you are not permitted to signal. |
uname
Peios / Using Peios / Peiosutils / System and processes
uname prints information about the system you are running on.
uname [options]
$ uname
Peios
$ uname -a
Peios host-01 1.4.0 #1 SMP 2026-05-10 x86_64 Peios
With no option, uname prints the operating-system name — Peios.
What each option prints #
| Option | Prints |
|---|---|
-s | The kernel name. |
-o | The operating-system name — Peios. |
-n | The node name — the name this system is known by on the network. |
-r | The kernel release. |
-v | The kernel version. |
-m | The machine's hardware name (its architecture). |
-p | The processor type. |
-i | The hardware platform. |
-a | Everything — equivalent to -mnrsvo. |
Give several options and uname prints those fields, in a fixed order, on one line.
The operating system is Peios #
uname -o, and the operating-system field of uname -a, report Peios. This is the field a script should check when it wants to confirm what system it is on.
For the machine architecture alone, arch is the shorter command — it prints exactly what uname -m prints.
Exit status #
| Code | Meaning |
|---|---|
0 | The requested information was printed. |
1 | The system information could not be read. |
arch
Peios / Using Peios / Peiosutils / System and processes
arch prints the machine's hardware architecture — the kind of processor the system runs on.
arch
$ arch
x86_64
That is the whole command. It takes no options and prints a single name.
arch prints exactly what uname -m prints. It exists as a short, obvious name for the one question "what architecture is this?" — convenient in a script that picks an architecture-specific path.
Exit status #
| Code | Meaning |
|---|---|
0 | The architecture was printed. |
1 | The architecture could not be determined. |
hostname
Peios / Using Peios / Peiosutils / System and processes
hostname displays the system's host name — the name the system is known by.
hostname [options] [name]
$ hostname
host-01.example.com
What is displayed #
By default hostname prints the fully qualified name. These options narrow or change what is shown:
| Option | Prints |
|---|---|
-f, --fqdn | The fully qualified domain name — the full host.domain form. This is the default. |
-s, --short | The short host name only — the part before the first dot. |
-d, --domain | The DNS domain part only. |
-i, --ip-address | The network address (or addresses) the host name resolves to. |
Setting the host name #
Given a name argument, hostname sets the system's host name to it.
$ hostname host-02
Changing the host name changes state for the whole system, so it is a privileged operation — it succeeds only for a caller whose token carries the right to do it, and is refused otherwise. See Privileges.
Exit status #
| Code | Meaning |
|---|---|
0 | The host name was displayed or set. |
1 | A failure — the name could not be read, set, or resolved. |
hostid
Peios / Using Peios / Peiosutils / System and processes
hostid prints the host's numeric identifier, in hexadecimal.
hostid
$ hostid
007f0100
The host id is a number associated with the system. It takes no options and prints one value.
Where hostname gives the system's name — meant to be read by people — hostid gives a numeric value, used by software that wants a machine identifier in a fixed numeric form.
Exit status #
| Code | Meaning |
|---|---|
0 | The host id was printed. |
nproc
Peios / Using Peios / Peiosutils / System and processes
nproc prints the number of processor cores available.
nproc [options]
$ nproc
8
By default it prints the number of cores available to the current process — which can be fewer than the machine has, if the process has been restricted to a subset. It is most often used to decide how many parallel jobs to run.
Options #
| Option | Effect |
|---|---|
--all | Print the number of cores the whole system has, ignoring any restriction on the current process. |
--ignore=N | Subtract N from the count — for leaving some cores free rather than using every one. |
The environment variables OMP_NUM_THREADS and OMP_THREAD_LIMIT, if set, also bound the number nproc reports.
Exit status #
| Code | Meaning |
|---|---|
0 | The count was printed. |
1 | An option value was invalid. |
uptime
Peios / Using Peios / Peiosutils / System and processes
uptime shows how long the system has been running, along with a few related figures.
uptime [options]
$ uptime
14:32:08 up 6 days, 3:21, load average: 0.18, 0.24, 0.21
The default line packs in three things:
- the current time;
- how long the system has been up;
- the load average — a rough measure of how busy the system has been over the last 1, 5, and 15 minutes.
If you know uptime from other systems you may be expecting a count of logged-in users between the uptime and the load average. Peios does not print one. That count comes from a login database (utmp) that Peios does not keep — logon sessions are a KACS concept, and the tooling to report them is not built yet. Rather than print a figure that would always read 0 users, the field is left out until it can be answered honestly.
Options #
| Option | Effect |
|---|---|
-p, --pretty | Show just the uptime, in a readable phrase — up 6 days, 3 hours, 21 minutes. |
-s, --since | Show the date and time the system started, rather than how long ago that was. |
Exit status #
| Code | Meaning |
|---|---|
0 | The uptime was printed. |
1 | The boot time could not be read. |
sync
Peios / Using Peios / Peiosutils / System and processes
sync flushes cached writes to persistent storage.
sync [options] [file...]
$ sync
When a program writes to a file, the data does not always reach the physical device immediately — for speed, the system holds recent writes in memory and writes them out a little later. sync forces that pending data out now, so that what is on the device matches what programs have written.
It is the command to run before doing something that should not lose recent writes — before powering off by hand, or before removing storage.
Options #
With no argument, sync flushes everything. The options narrow it:
| Option | Effect |
|---|---|
file... | Flush only the given files, rather than everything. |
-d, --data | For the given files, flush only the file data, not metadata that does not need it. |
-f, --file-system | Flush the entire file system that contains each given file. |
Exit status #
| Code | Meaning |
|---|---|
0 | The flush completed. |
1 | A named file could not be reached. |
tty
Peios / Using Peios / Peiosutils / System and processes
tty prints the name of the terminal connected to standard input.
tty [options]
$ tty
/dev/pts/3
If standard input is not a terminal — because it has been redirected from a file or a pipe — tty prints not a tty instead.
That second behaviour is the useful one: a script can run tty to find out whether it is being run interactively or as part of a pipeline, and behave accordingly.
Options #
| Option | Effect |
|---|---|
-s, --silent | Print nothing; report the answer only through the exit status. |
Exit status #
| Code | Meaning |
|---|---|
0 | Standard input is a terminal. |
1 | Standard input is not a terminal. |
2 | A usage error. |
stty
Peios / Using Peios / Peiosutils / System and processes
stty displays and changes the settings of a terminal — how it handles input, output, and special keys.
stty [options] [setting...]
$ stty
speed 38400 baud; line = 0;
-brkint -imaxbel iutf8
Run with no arguments, stty prints the settings that differ from the usual defaults. Given setting arguments, it changes them.
Viewing the settings #
| Option | Effect |
|---|---|
-a, --all | Print every setting, in a readable layout. |
-g, --save | Print every setting in a single compact line — a form that can be fed straight back to stty to restore them. |
The -g form is how you save and restore a terminal's state:
$ saved=$(stty -g) # capture the current state
$ stty -echo # ...change something...
$ stty "$saved" # restore exactly what was saved
Changing the settings #
A setting argument turns something on or off, or assigns a value. A few common ones:
| Setting | Effect |
|---|---|
echo / -echo | Show / hide typed characters — -echo is how a password prompt hides input. |
rows N / cols N | Tell the terminal its size. |
| A number | Set the connection speed (baud rate). |
stty has a large catalog of settings; stty -a shows them all with their current values.
Choosing the terminal #
| Option | Effect |
|---|---|
-F, --file=DEVICE | Operate on DEVICE instead of the terminal on standard input. |
Exit status #
| Code | Meaning |
|---|---|
0 | The settings were printed or changed. |
1 | A setting was invalid, or the terminal could not be accessed. |
stdbuf
Peios / Using Peios / Peiosutils / System and processes
stdbuf runs a command with altered buffering on its standard streams.
stdbuf [options] command...
$ slow-producer | stdbuf -oL grep error
What buffering is, and why change it #
A program usually does not write each piece of output the instant it is produced — it collects output in a buffer and writes it in batches, which is faster. The cost is delay: when a program's output feeds a pipe, that batching can hold lines back for a long time, which is unhelpful when you are watching a pipeline live.
stdbuf lets you override that batching for a command, so its output appears sooner.
Setting the buffering #
| Option | Stream |
|---|---|
-i, --input=MODE | Standard input. |
-o, --output=MODE | Standard output. |
-e, --error=MODE | Standard error. |
MODE is one of:
| MODE | Buffering |
|---|---|
0 | Unbuffered — every write goes out immediately. |
L | Line-buffered — output is flushed at the end of each line. Not valid for input. |
| a size | Fully buffered with a buffer of that many bytes. Accepts suffixes — K, M, and so on. |
-oL — line-buffered output — is the common case: it makes a command in a pipeline emit each line as it is finished.
A limitation #
stdbuf adjusts the default buffering. A command that manages its own stream buffering will override what stdbuf sets, and some commands do not use buffered streams at all — for those, stdbuf has no effect.
Exit status #
When stdbuf runs a command, it exits with that command's status. The exception is a failure in stdbuf itself:
| Code | Meaning |
|---|---|
| (command's own) | The command ran; this is its exit status. |
(a stdbuf-level error code) | The command could not be started. |
chroot
Peios / Using Peios / Peiosutils / System and processes
chroot runs a command with a different directory as its root — its /. Inside that command, the chosen directory is the top of the file system, and nothing above it can be named or reached.
chroot newroot [command [arg]...]
$ chroot /mnt/system /bin/sh
With a command, chroot changes the root to newroot and runs the command there. With no command, it starts an interactive shell.
What it does #
A process normally sees the whole file system, rooted at the real /. After chroot, the named directory becomes that process's /: a path like /etc/config inside the command resolves to newroot/etc/config, and there is no path that reaches outside newroot at all.
It is used to work inside another installed system — repairing or configuring a system image by entering it — and to run a command confined to a known subtree.
For the command to be usable, newroot must already contain what the command needs: the command's own executable, and whatever libraries and files it depends on, all present under newroot.
Options #
| Option | Effect |
|---|---|
--skip-chdir | Do not change the working directory to / after changing the root. Permitted only when newroot is the current /. |
A privileged operation #
Changing a process's root directory is a privileged operation. chroot succeeds only for a caller whose token carries the right to do it, and is refused otherwise — the ability to redraw a process's view of the file system is not something every principal holds. See Privileges.
Exit status #
chroot exits with the command's own status once it finishes. Otherwise:
| Code | Meaning |
|---|---|
125 | chroot itself failed — for example, newroot is not a directory. |
126 | The command was found but could not be run. |
127 | The command was not found. |
whoami
Peios / Using Peios / Peiosutils / System and processes
whoami prints the name of the user the calling process is running as.
whoami
$ whoami
jack
It takes no arguments. It is the same answer as id -un, which is all it has ever been.
Where the name comes from #
The name is resolved from the process's effective user ID, which is a projection of the token's user SID. The lookup goes to the authority — there is no /etc/passwd to read — and comes back as the principal's canonical name. Resolving names is the full picture.
The name is the principal's; the number it was looked up by is not what grants anything. If you want the identity access is actually decided against, id -Z prints the SID.
Exit status #
| Code | Meaning |
|---|---|
0 | The name was printed. |
1 | The user ID could not be resolved to a name. |
A failure here usually means the authority could not be reached rather than that the account is missing — identity does not resolve before the authority is running, which is the correct answer rather than a gap.
id
Peios / Using Peios / Peiosutils / System and processes
id prints who a process is: its user, its primary group, the groups it belongs to, and its security context.
id [options] [USER]...
$ id
uid=5001000(jack) gid=5000513(Users) groups=5000513(Users),100(Everyone),101(Authenticated Users),1544(Administrators) context=S-1-5-21-1004336348-1177238915-682003330-1000:High
With no argument it reports the calling process. Given one or more users, it reports on those instead.
The numbers are projections #
This is the thing to understand before reading id's output on Peios.
The uid= and gid= numbers are projections of the token's SIDs. They are real, stable, and exactly what Linux programs see and what filesystems store — but nothing is decided by them. Access is decided against the SID the number was projected from, by the access check. A number here grants nothing.
That distinction is not academic, because the projection cannot carry everything a token holds:
- Groups whose SIDs are deliberately unnumbered —
Interactive,Network,Batch,Service— cannot appear at all. They are what make a rule like "network logons cap at Low" expressible, and they have no gid by design. - A group's attributes have nowhere to go. A deny-only group, which can block access through a deny entry but grant nothing through an allow entry, appears in
groups=looking exactly like an ordinary membership. - The integrity level and the logon session have no number either.
So two tokens can project to identical numbers and mean different things. The clearest case is a filtered token: it carries the same user SID and the same group numbers as the token it came from, and differs only in attributes the projection cannot express.
That is why the default line ends with a context= field.
The context field #
context= is the authoritative half: the user SID access is actually decided against, and the integrity level.
context=S-1-5-21-1004336348-1177238915-682003330-1000:High
The integrity level is part of it precisely because the SID alone does not separate an elevated token from its filtered counterpart — both carry the same user SID, and the level is what differs.
If you know id from another Unix, this is the field that carries the SELinux context there. Peios has no SELinux; its security context is the token, so that is what the field holds.
context= is omitted when a user is named rather than the calling process — a named user has no token here to read — and when POSIXLY_CORRECT is set, so a caller that asked for the POSIX shape still gets it.
For the whole token rather than this summary, use token.
Options #
| Option | Effect |
|---|---|
-u, --user | Print only the effective user ID. |
-g, --group | Print only the effective group ID. |
-G, --groups | Print only the group IDs, space-separated. |
-n, --name | With -u, -g or -G, print names instead of numbers. |
-r, --real | With -u, -g or -G, print the real ID rather than the effective one. |
-z, --zero | Separate entries with NUL rather than whitespace. Not allowed in the default format. |
-Z, --context | Print only the security context. |
-p | A readable, multi-line form. |
-P | Print as a password-file entry. |
-a | Ignored; accepted for compatibility. |
-A | Not applicable on Peios — see below. |
-n and -r need one of -u, -g or -G; they do not change the default format.
-r means something different here #
On other Unixes the real/effective split is the setuid boundary. On Peios a setuid bit does not change a token, so that boundary does not exist. What the split tracks instead is impersonation: the real IDs come from the process's own token, the effective ones from whatever token is in force.
id never impersonates, so in practice it reports the same either way and the euid=/egid= fields never appear. That is not a fault — there is genuinely nothing differing to report.
-A is not applicable #
-A reports BSD audit session properties. Peios audits through its own event system and keeps no BSM session, so -A says so rather than silently succeeding at nothing.
id -G and id -G <yourself> differ #
Asking about yourself and asking about your name are two different questions here, and they give different answers:
id -Greads your process's credential — the projection of your token's group set, which includes the groups the authority added when the token was minted:Everyone,Authenticated Users, and anyAdministrators-style aliases.id -G jacklooks the name up through the principal store, which returns recorded memberships only. The stapled groups are not recorded anywhere, because membership in them is a rule rather than a stored fact — so they do not come back.
On other systems these two differ only in the order they print. Here the second list is genuinely shorter, and neither is wrong.
Exit status #
| Code | Meaning |
|---|---|
0 | The identity was printed. |
1 | A user could not be found, an option combination was rejected, or the context could not be read. |
groups
Peios / Using Peios / Peiosutils / System and processes
groups prints the groups a user belongs to.
groups [USERNAME]...
$ groups
Users Everyone Authenticated Users Administrators
$ groups jack
jack : Users Administrators
With no argument it reports the calling process. Given names, it reports on those.
Those two answers are both correct #
The example above is not a mistake, and it is the thing worth knowing about this command on Peios.
groups reads the calling process's credential — the projection of its token's group set. That set includes the groups the authority stapled on when the token was minted: Everyone, Authenticated Users, and the like.
groups jack looks the name up through the principal store, which returns recorded memberships — the ones something actually wrote down.
The stapled groups are not recorded anywhere, because membership in them is a rule, not a stored fact. Everyone is in Everyone; nothing needs to record it, and nothing can enumerate it. So they appear in the first answer and not the second.
On other Unixes these two forms differ only in the order they print.
What cannot appear #
Both forms are lossy in the same way, and unavoidably so — a group can only be listed if it has a group ID, and some deliberately do not:
Interactive,Network,BatchandServiceare unnumbered on purpose. They describe how you signed in rather than who you are, which is what lets a rule like "network logons cap at Low" be written at all. You are inInteractiveat the console and not over the network, so there is no static answer to record.- A group's attributes are not representable. A deny-only group — one that can block access through a deny entry but grant nothing through an allow entry — is printed exactly like an ordinary membership.
token groups is the lossless view, with every SID and its attributes.
Exit status #
| Code | Meaning |
|---|---|
0 | The groups were printed. |
1 | A named user could not be found, or the group list could not be read. |
A group ID with no name is printed as the bare number and sets the exit status, rather than stopping the listing.
logname
Peios / Using Peios / Peiosutils / System and processes
logname prints the login name the calling process runs under.
logname
$ logname
jack
It takes no arguments.
Login name, not current name #
logname and whoami look like the same command and answer slightly different questions.
whoami reports the effective identity — whoever the process is acting as right now. logname reports the login identity: the principal the process's own token is for, regardless of any token it has since taken on. When a process is impersonating a client, whoami follows the impersonation and logname does not.
Most of the time nothing is impersonating and the two agree.
If you know logname from another Unix, note that it answers this from the token rather than from a login-record file. There is no utmp on Peios — nothing writes one — so the traditional source does not exist. The token is the record of who a process is, and it is a better one: it cannot drift from the identity access is actually checked against, because it is that identity.
Exit status #
| Code | Meaning |
|---|---|
0 | The login name was printed. |
1 | No login name could be determined. |
token
Peios / Using Peios / Peiosutils / System and processes
token is the command-line tool for working with tokens directly. It reads a token's contents, adjusts it, produces derived tokens, and drives impersonation — the low-level operations this topic describes, exposed at a shell.
token subcommand [target] [arguments]
$ token # one-line summary of your own token
$ token show --all # every field of your own token
$ token privs --pid 4821 # the privileges on process 4821's token
token is a direct, debug-level tool. Day to day you do not inspect tokens by hand — the system does. token is for diagnosing an access problem, for understanding what identity a process is really running under, and for building and testing identity setups. Run with no subcommand, it prints a one-line summary of your own token.
Choosing which token #
Almost every subcommand operates on a token, and these flags choose which one. With none, the target is your own.
| Flag | Target |
|---|---|
--self | Your own token. The default. |
--real | Your primary token specifically, rather than the effective one — relevant when your thread is impersonating. |
--pid PID | The primary token of process PID. |
--tid TID | The impersonation token of thread TID (used with --pid). |
--peer SOCK_FD | The peer's captured token on a connected socket — see Peer tokens. |
Reading another process's token is itself access-controlled: it succeeds only with the right authority over that process.
Inspecting a token #
show #
token show prints a token's contents. It is the default — bare token is token show --short.
| Flag | Effect |
|---|---|
--short | A one-line summary. |
--all | Every query class — the fullest dump. |
Field accessors #
Each of these prints one part of a token, for when you want just that piece:
| Subcommand | Prints |
|---|---|
user | The user SID — who the token is. |
owner | The default owner SID. |
group | The primary group SID. |
groups | The group list. |
privs | The privileges, with their enabled state. |
caps | The capabilities. |
claims | The user and device claims. |
integrity | The integrity level. |
logon | The logon type and logon SID. |
source | What minted the token. |
origin | The originating session for a derived token. |
stats | Token statistics — IDs, timestamps, the modification counter. |
default-dacl | The token's default DACL. |
query #
token query CLASS performs a raw read of a single named token-info class and prints the result as JSON — the lowest-level inspection route, for tooling.
Changing a token #
adjust #
token adjust mutates a token in place:
| Form | Changes |
|---|---|
adjust privs NAME=STATE … | Enable, disable, or remove privileges. STATE is enabled, disabled, or removed. |
adjust groups IDX=STATE … | Enable or disable groups by their list index. |
adjust default --dacl SDDL | Replace the token's default DACL. Also --owner-idx / --group-idx. |
adjust session ID | Replace the token's session id. |
restrict #
token restrict produces a restricted token — a more limited variant of a token.
| Flag | Effect |
|---|---|
--drop-privs MASK|NAMES | Privileges to drop. |
--deny IDX,… | Group indices to mark deny-only. |
--restrict SID,… | The restricting SIDs to apply. |
duplicate #
token duplicate (alias dup) copies a token, optionally changing its --type (primary or impersonation), its impersonation --level, or its --access mask.
link and linked #
token link joins two tokens as an elevation pair — a full token and its filtered counterpart — given their file descriptors and a session id. token linked shows a token's elevation-linked counterpart, if it has one. See Elevation.
Impersonation #
| Subcommand | Effect |
|---|---|
impersonate | Begin impersonating the target token on the calling thread. With a trailing -- command …, run that command under the impersonating token. |
revert | Drop any active impersonation on the calling thread. |
See Impersonation for the model these drive.
Creating tokens #
| Subcommand | Effect |
|---|---|
create SPEC | Create a token from a binary token-spec (SPEC is a file, or - for standard input). |
install SPEC | Create a token from a spec and install it as the caller's primary token. |
Creating and installing tokens is a privileged operation, reserved for the components that legitimately mint identity.
Output options #
| Flag | Effect |
|---|---|
--raw | Render SIDs in raw S-1-… form only. |
--label | Render SIDs as their labels where known, falling back to raw. |
--json | Emit JSON instead of human-readable output. |
token and the inspection surfaces #
token is the convenient front-end. Underneath, it reads the same kernel surfaces and rules described in Inspecting tokens — that page covers the query mechanism, the access rules for reading another process's token, and what cannot be inspected.
Exit status #
| Code | Meaning |
|---|---|
0 | The operation succeeded. |
1 | A usage error. |
| non-zero | The operation failed — no such target, an access denial, or a bad spec. |
logonse
Peios / Using Peios / Peiosutils / System and processes
logonse is the command-line tool for logon sessions — the kernel's records of authentication events that this topic describes. It lists the active sessions, shows which processes belong to one, creates and destroys sessions, and (as a related low-level job) sets a process's mitigation flags.
logonse subcommand [arguments]
$ logonse list
$ logonse show 4711
logonse is a low-level administrative and debugging tool. It requires a subcommand: list, show, create, destroy, or psb.
Listing sessions #
logonse list #
Enumerates the active logon sessions, and the process IDs in each.
$ logonse list
session 0 pids: [1, 2, 14, 22]
session 4711 pids: [820, 844, 901]
logonse show #
Shows the process IDs that belong to one session.
$ logonse show 4711
A caveat on list and show #
There is no syscall that enumerates logon sessions, and logonse does not read the kernel's sessions file (whose SD restricts it to Administrators and SYSTEM). logonse list and logonse show work by walking the running processes and reading each one's token to find which session it belongs to. That has two consequences worth knowing:
- It is best-effort. A session that has no running process — held alive only by a token file descriptor somewhere — will not appear, because there is no process to find it through.
- It is a snapshot under change. Processes start and exit while the walk runs, so the result is a close approximation of the moment, not a locked one.
For routine "who is signed in" use this is fine. For an authoritative listing, the kernel's own sessions surface — described in Inspecting sessions — is the source of record.
Creating and destroying sessions #
logonse create #
Creates a new logon session for a user, described by a logon type, an authentication-package name, and the user's SID.
$ logonse create --logon-type interactive --auth-package Negotiate --user-sid S-1-5-21-...-1001
| Flag | Meaning |
|---|---|
--logon-type TYPE | The kind of logon: interactive, network, batch, service, network-cleartext, or new-credentials. |
--auth-package STR | The name of the authentication package that vouched for the logon. |
--user-sid SID | The user the session belongs to, as an S-1-… SID or an SDDL alias such as BA. |
On success logonse prints the new session's id. Creating a session is a privileged operation — minting authentication records is reserved for the components that legitimately do so.
logonse destroy #
Destroys a session — but only an empty one, with no tokens still referencing it.
$ logonse destroy 4711
A session with live tokens cannot be destroyed this way; its tokens must go first. See Session lifecycle.
Setting process mitigation flags #
logonse psb #
logonse psb sets the mitigation flags in a process's Process Security Block.
$ logonse psb --pid 4821 --mitigations 0x1c0
| Flag | Meaning |
|---|---|
--pid PID | The process to act on. |
--mitigations MASK | The mitigation bitmask to apply, in hexadecimal or decimal. |
This subcommand is about process hardening rather than logon sessions — it lives in logonse because both deal with low-level per-process kernel state. For what the mitigation flags mean and how they behave, see Process mitigations.
Output options #
| Flag | Effect |
|---|---|
--json | Emit JSON instead of human-readable output. Accepted by every subcommand. |
Exit status #
| Code | Meaning |
|---|---|
0 | The operation succeeded. |
1 | A usage error, or the operation failed. |
mount
Peios / Using Peios / Peiosutils / Disks and mounts
mount attaches a filesystem to the Peios mount tree. With no operands it instead lists what is currently mounted. It is a faithful reworking of the util-linux mount(8) surface for Peios, with two structural differences: Peios has no /etc/fstab and no /etc/mtab, so every mount is described entirely on the command line and live mount state is read from the kernel; and a mount can apply a KACS mount policy to the new filesystem at attach time (see Mount policies).
mount [-t TYPE] [-o OPTIONS] SOURCE TARGET
mount [-o remount,OPTIONS] TARGET
mount --bind|--rbind|--move SOURCE TARGET
mount --make-shared|--make-private|... TARGET
mount [-l] [-t TYPE]
Internally mount uses the fd-based mount API (fsopen / fsconfig / fsmount / move_mount, plus open_tree and mount_setattr), never the classic single-shot mount(2). This matters in one place you can see: when a mount fails, the kernel's fs_context message log is drained and printed as the reason, even without -v.
Operands and argument shapes #
Because there is no fstab to consult, operand resolution is strict:
- No operands, no verb — list mode (see below).
- Two operands —
SOURCE TARGET. - One operand — an error. In util-linux a lone operand is resolved through fstab; Peios has none, so a single operand cannot be turned into a
SOURCE TARGETpair. Supply both, or use--source/--target. - A lone target is valid only for
-o remountand for a standalone propagation change (--make-*), which act on an existing mount point.
--source SRC and --target DIR name the operands explicitly and may be combined with a single positional. --target-prefix DIR prepends DIR/ to the target after it is chosen. Options may be interspersed with operands (mount SRC -o ro TGT), matching util-linux.
Paths, -o key=value values, labels and UUIDs are handled as opaque byte strings, never assumed to be UTF-8; an embedded NUL is a usage error.
Canonicalisation #
By default source and target paths are canonicalised (made absolute, symlinks resolved). -c / --no-canonicalize disables that; X-mount.nocanonicalize[=source|target] is the -o form and can disable it for just one side. The final path handed to the kernel is protected against a TOCTOU symlink swap on its last component.
Operation modes (verbs) #
mount performs one of several operations. The structural verbs — bind, rbind, move, beneath and remount — are mutually exclusive with one another. A propagation change is not a structural verb: it may stand alone on a target, or trail another verb or a new mount in the same command (applied afterwards as one or more mount_setattr steps), and several may be combined.
| Verb | How to request it | What it does |
|---|---|---|
| New mount | mount [-t T] SRC TGT | Attach a fresh instance of the filesystem at SRC onto TGT. |
| Bind | -B / --bind, or -o bind | Make an existing subtree visible at a second location. |
| Recursive bind | -R / --rbind, or -o rbind | Bind a subtree together with every mount underneath it. |
| Move | -M / --move, or -o move | Relocate an existing mount to a new mount point. |
| Move beneath | --beneath SRC TGT | Attach SRC beneath the mount currently at TGT (the kernel enforces several constraints; violations surface as exit 32). |
| Remount | -o remount[,...] TGT | Change the options of an existing mount (see Remounting). |
| Propagation | --make-* / --make-r*, or the -o tokens below | Change how mount/unmount events propagate across a subtree. |
| List | mount / mount -l | Print the current mounts. |
Propagation flags #
Each of these sets the propagation type of the mount at the target. The --make-r* (and r-prefixed -o) forms apply recursively to the whole subtree; the recursive form is all-or-nothing (it either applies to the entire subtree or to none of it).
| Flag | -o token | Recursive flag | Recursive -o token | Meaning |
|---|---|---|---|---|
--make-shared | shared | --make-rshared | rshared | Events propagate to and from peer mounts. |
--make-slave | slave | --make-rslave | rslave | Events propagate in from the master but not back out. |
--make-private | private | --make-rprivate | rprivate | No propagation either way. |
--make-unbindable | unbindable | --make-runbindable | runbindable | Private, and cannot be bind-mounted. |
A freshly created mount, bind or move is private by default unless its destination's parent is shared, in which case it joins that peer group — the standard kernel rule.
Source specification #
| Form | Resolution |
|---|---|
Device path (/dev/sda1) | Used directly. |
-L LABEL / LABEL=, -U UUID / UUID=, PARTLABEL=, PARTUUID= | Resolved to a device via libblkid. No match is a mount failure (exit 32). |
| A directory or regular file | Used directly (a bind source or target; a file may be a bind target). |
A pseudo source (tmpfs, proc, sysfs, none, …) | Passed through; -t is required because it cannot be probed. |
| An image file | Attached through a loop device (see Loop devices). |
Filesystem type #
-t TYPE names the type explicitly. -t auto, or omitting -t entirely, asks libblkid to safely probe the source; an ambiguous or multiply-signed device is refused (exit 32) rather than guessed at. X-mount.auto-fstypes=LIST constrains the probe candidates. Type lists and no<type> negation are not accepted in the mounting path (they remain meaningful only as a listing filter).
The -o option language #
-o takes a comma-separated list of key or key=value tokens and is repeatable (all occurrences are joined). A key="..." double-quoted value protects embedded commas; the key=value split is on the first =. Every token is sorted into one of the following categories.
Per-mount attributes #
Applied to the mount itself. Each accepts its negation; ro/rw additionally accept a =vfs / =fs / =recursive scope qualifier (bare is =vfs, non-recursive; =fs targets the superblock read-only flag; =recursive applies to the whole subtree).
| Option | Effect |
|---|---|
ro / rw | Read-only / read-write. |
suid / nosuid | Honour / ignore set-user-ID bits. |
dev / nodev | Allow / disallow device nodes. |
exec / noexec | Allow / disallow execution. |
atime / noatime | Update / never update access times. |
relatime / norelatime | Relative-atime updates on or off. |
strictatime / nostrictatime | Strict-atime updates on or off. |
diratime / nodiratime | Directory access-time updates on or off. |
nosymfollow | Do not follow symlinks on this mount. There is no positive symfollow token — the attribute is cleared only via a remount mask. |
The atime tokens share a single mode field; the last one specified wins.
Superblock flags #
| Option | Effect |
|---|---|
sync / async | Synchronous / asynchronous writes. |
dirsync | Synchronous directory updates. |
lazytime / nolazytime | Lazy on-disk timestamp updates on or off. |
iversion / noiversion | Inode version counting on or off. |
silent / loud | Suppress or emit certain kernel messages. |
mand / nomand | Obsolete (removed from the kernel). Accepted and ignored with a note under -v. |
Filesystem-specific parameters #
Any token not recognised above is forwarded verbatim to the filesystem: key=value as a string parameter, a bare key as a flag. There is no built-in allow-list and no length limit; a rejection by the filesystem is reported with the offending key and the kernel's message.
For example, StrataFS takes its precedence-ordered directory stack through
strata=:
See StrataFS for the stack flags, merge
and write-routing model, and the stratafs inspection command.
Userspace-only tokens #
These never reach the filesystem. They include the meta-verbs remount, bind, rbind, move; the loop controls loop / loop=/dev/loopN, offset=, sizelimit= (numeric values accept K/M/G/T and KiB/MiB/… suffixes); the propagation tokens listed above; and defaults, which expands to rw,suid,dev,exec,async (later tokens override it).
Functional X-mount.* options #
| Option | Effect |
|---|---|
X-mount.mkdir[=mode] | Create the target directory if missing (default mode 0755; the alias of -m). |
X-mount.subdir=DIR | Attach subdirectory DIR of a freshly mounted filesystem at the target. Effective only for a new-instance mount; silently ignored (noted under -v) for bind/move/remount/propagation. |
X-mount.noloop | Suppress the implicit loop device for a regular-file source. |
X-mount.auto-fstypes=LIST | Constrain the -t auto probe to these types. |
X-mount.nocanonicalize[=source|target] | The -o form of -c; with =source or =target it disables canonicalisation for just that path. |
X-mount.idmap and X-mount.owner / group / mode are not supported and are rejected with a usage error: Peios ownership is SID/security-descriptor based, so the fix is to add a SID/ACE to the descriptor rather than remap or chown.
KACS mount policy #
This is the genuinely Peios-specific part of mount. A mount policy is a per-superblock setting that governs how FACS treats the filesystem; the full model is described in Mount policies and its detail pages. mount can set that policy at attach time:
| Option | Policy class |
|---|---|
-o policy=deny-missing | facs_deny_missing — a file with no SD is unreachable. |
-o policy=synth-ephemeral | facs_synthesize_ephemeral — a missing SD is synthesised in memory only. |
-o policy=synth-persist | facs_synthesize_persistent — a missing SD is synthesised and written back. |
--synth-sddl SDDL | Provide the mount-level SD template used during synthesis (only valid with a synth-* policy). |
policy=unmanaged is not user-settable; only the kernel sets the unmanaged class, for its own pseudo-filesystems. policy= is valid only on a new mount of a real filesystem — combining it with bind/move/remount/propagation, or with list mode, is a usage error. See Policy classes for what each class does and SD storage by filesystem for how the SD is physically stored.
The policy is applied to the detached filesystem before it is attached: if setting it fails, nothing is ever published with an unintended policy (no rollback needed). Because setting a mount policy is a SeTcbPrivilege-gated operation (see Managing mounts), a caller without that privilege gets a clean EPERM (exit 1) and no mount. --synth-sddl is validated client-side first — it must be well-formed SDDL and must include an owner.
Peios applies no coarse uid==0 check anywhere: the mount applet is not installed set-user-ID, and every privileged action is authorised per-operation by KACS.
Remounting #
-o remount changes the options of an existing mount without detaching it. Peios does not perform the util-linux "read the old flags and re-supply them" dance — the fd-based API applies deltas rather than resetting, so each remount touches only what you name. Per-mount attributes (ro/rw, nosuid, atime, …) are changed via mount_setattr; superblock flags, filesystem parameters and ro=fs/rw=fs go through the superblock reconfigure path. =recursive remounts the whole subtree, all-or-nothing.
A bind mount shares its source's superblock, so any superblock-level option on a bind remount (a Category-B flag, a filesystem parameter, or ro=fs/rw=fs) is refused as a usage error rather than silently mutating the shared superblock for every mount of that filesystem. Only per-mount VFS attributes are valid on a bind remount.
Loop devices #
A regular-file source is backed by a loop device automatically when the type is unspecified or the filesystem is recognised by libblkid; X-mount.noloop suppresses this. -o loop forces auto-allocation of a free device, loop=/dev/loopN names one, and offset= / sizelimit= select a region of the file (they imply loop and are an error against a real block device). A backing file already attached at the same offset and size is reused rather than doubly attached, to avoid corruption. Loops created by mount are auto-cleared by the kernel on unmount, so they do not leak; umount -d force-clears the rest (see umount).
Other flags #
| Flag | Effect |
|---|---|
-t, --types TYPE | Filesystem type, or auto. |
-o, --options LIST | Mount options (above); repeatable. |
-r, --ro, --read-only | Mount read-only (-o ro). |
-w, --rw, --read-write | Mount read-write and forbid the automatic read-only fallback on a write-protected device (-o rw). |
--source SRC / --target DIR | Name the operands explicitly. |
--target-prefix DIR | Prepend DIR/ to the target. |
-B, --bind / -R, --rbind / -M, --move / --beneath | The structural verbs. |
--make-*, --make-r* | Propagation changes. |
--exclusive | Force a unique superblock instance (no reuse). Meaningful only for multi-instance filesystems (e.g. tmpfs) or read-only block mounts; on an already-mounted writable block device it fails with EBUSY (exit 32). |
-m, --mkdir[=MODE] | Create the target directory if missing (default mode 0755). |
-L, --label LABEL / -U, --uuid UUID | Select the source by filesystem label / UUID. |
-c, --no-canonicalize | Do not canonicalise paths. |
-f, --fake | Dry run: parse, resolve and plan everything but skip the mount syscalls and the policy step. |
-v, --verbose | Narrate resolved values and each syscall; drain the kernel fs_context log. Repeatable but -vv is the same as -v. |
-l, --show-labels | In list mode, append each filesystem's label. |
--onlyonce | Skip the mount if it is already present (a driver-aware check against live mount state, not a naive string match). |
-N, --namespace NS | Operate inside mount namespace NS (a PID, an ns file path, or a named namespace). Source resolution happens in the caller's namespace; the mount lands in the target namespace. |
-i, --internal-only | Do not invoke a mount.<type> helper. |
-n, --no-mtab | Accepted and ignored (Peios has no mtab). |
--synth-sddl SDDL | KACS synth-policy template SD (above). |
-h, --help / -V, --version | Standard. |
No external mount helpers ship in this version, so a network or FUSE type fails with "no helper for type" (exit 1) — a clean deferral, not a crash.
List mode #
With no operands, mount prints one line per mount, read from /proc/self/mountinfo:
SOURCE on TARGET type FSTYPE (OPTIONS)
mount -t TYPE with no operands filters the list by filesystem type instead of mounting; here type lists (-t ext4,xfs) and no<type> negation (-t nosysfs) are honoured. -l appends [LABEL] to each line when a label is known.
Details of the rendering: the option field is the positional VFS-then-superblock merge with a coalesced ro/rw, not sorted; mountinfo octal escapes are decoded; control characters in the mount point become ?; and the source is shown as the kernel recorded it, with /dev/loopN resolved to its backing file and /dev/dm-N to /dev/mapper/<name>. The listing shows only mounts the caller's token may observe; a partial or empty view is correct, not an error, and the paths of filtered-out parents are never reconstructed.
For a device-oriented view of what is available to mount, use lsblk.
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | Incorrect invocation or a permission denial — bad flags, mutually-exclusive verbs, a lone operand, an invalid policy= or --synth-sddl, an authorisation failure (EPERM/EACCES), an embedded NUL, or "no helper for type". |
2 | System error (out of memory, no free loop device, cannot fork). |
4 | Internal error (an invariant failure). |
8 | Interrupted (SIGINT), after signal-safe cleanup. |
32 | Mount failure — a syscall in the flow failed, a label/UUID had no match, a probe was undetermined, or a --beneath/move/--exclusive kernel refusal. |
126 | An external mount.<type> helper was found but failed to execute (moot until a helper ships). |
Code 16 (mtab) is never produced. Code 64 (some succeeded, some failed) only arises when several source arguments are given and at least one succeeds while another fails.
umount
Peios / Using Peios / Peiosutils / Disks and mounts
umount detaches a filesystem from the Peios mount tree. It is the counterpart of mount and, like it, reads live mount state from the kernel (/proc/self/mountinfo) rather than any /etc/mtab — Peios has none. Each operand is resolved against that live state and unmounted with umount2(2).
umount [-lfRA] [-dr] TARGET|SOURCE...
Operands and how they are resolved #
Each operand is either a mount point or a source:
- If the (canonicalised) operand is itself a mount point in the current mount table, it is unmounted directly. This is always unambiguous.
- Otherwise the operand is treated as a source and matched against the sources in the mount table. If it is mounted in exactly one place, that place is unmounted. If it is mounted in several places,
umountrefuses with an error naming the candidates — resolve it by naming a specific mount point, or use-Ato unmount all of them.
If an operand matches nothing, it is "not mounted": normally an error (exit 32), but -g makes that a success and -q suppresses the message.
Several operands may be given in one invocation, and options may be interspersed with them (umount /a -f /b). Paths are handled as opaque bytes; an embedded NUL is a usage error. By default each operand is canonicalised; -c disables that and additionally selects UMOUNT_NOFOLLOW so the kernel does not follow a symlink in the final component.
Recursive and all-targets unmounts #
Two flags expand a single operand into multiple unmounts. Within one operand the expansion stops on the first failure.
-R/--recursiveunmounts the target and everything mounted underneath it, including any over-mount stack at a single point, ordered deepest-first.-A/--all-targetsunmounts every mount point of the given source in the current mount namespace. This is the live-mountinfo "unmount everywhere" complement to source resolution; it is not an fstab feature.-A -Rtogether compose: for each mount point of the source, recurse underneath it.
-R and -r are mutually exclusive (combining them is a usage error, exit 1).
Flags #
| Flag | Effect |
|---|---|
-l, --lazy | Detach the filesystem now and clean up references later (MNT_DETACH). |
-f, --force | Force the unmount (MNT_FORCE), e.g. for an unreachable server. |
-R, --recursive | Unmount the target and everything under it (see above). |
-A, --all-targets | Unmount every mount point of the given source (see above). |
-d, --detach-loop | After unmounting, free the backing loop device. Best-effort and verified: it clears the device only if it still backs the mount just removed, so a recycled loop number is never clobbered. Usually redundant, since mount-created loops auto-clear on unmount. |
-r, --read-only | If the unmount fails, remount the filesystem read-only instead (via mount_setattr). Mutually exclusive with -R. |
-g, --graceful | Exit 0 when the target is absent or not mounted (rather than failing). Applied unconditionally. |
-q, --quiet | Suppress "not mounted" messages. |
-c, --no-canonicalize | Do not canonicalise paths; selects UMOUNT_NOFOLLOW. Applied unconditionally. |
-v, --verbose | Say what is being unmounted. Repeatable. |
-N, --namespace NS | Enter mount namespace NS (a PID, an ns file path, or a named namespace) before reading its mount table and unmounting. All work happens inside that namespace. |
-n, --no-mtab | Accepted and ignored (no mtab on Peios). |
-i, --internal-only | Do not invoke a umount.<type> helper (none ship). |
--fake | Dry run: resolve operands and report, but skip the unmount syscalls. |
-h, --help / -V, --version | Standard. |
The umount2 flag mapping is direct: -l → MNT_DETACH, -f → MNT_FORCE, -c (or a symlinked final component) → UMOUNT_NOFOLLOW. MNT_EXPIRE is deliberately not exposed, matching util-linux.
On privilege #
As with mount, there is no coarse uid==0 check: the applet is not set-user-ID and every unmount is authorised per-operation by KACS. Where util-linux silently suppresses an option for non-root users (-c, and -g's effectiveness), Peios applies it unconditionally.
Exit status #
| Code | Meaning |
|---|---|
0 | Success (or a -g no-op on an absent target). |
1 | Incorrect invocation or a permission denial — including -R with -r, a missing operand, an ambiguous source, an embedded NUL, or an authorisation failure. |
2 | System error (e.g. cannot read the mount table). |
8 | Interrupted (SIGINT). |
32 | Unmount failed, or the target is not mounted (unless -g). |
64 | Several source/target arguments were given and at least one succeeded while another failed. |
126 | An external umount.<type> helper was found but failed to execute (moot until a helper ships). |
A single -R / -A / -A -R invocation stops on the first failure and yields 32; the aggregate 64 only appears across multiple top-level arguments. To see what is currently mounted before unmounting, use mount with no arguments or lsblk.
lsblk
Peios / Using Peios / Peiosutils / Disks and mounts
lsblk lists the block devices on the system and their relationships — disks, their partitions, and the device-mapper and loop devices layered on top. It is the device-oriented companion to mount: where mount with no arguments shows what is mounted, lsblk shows what is available to mount and what filesystem sits on each device.
lsblk [options] [DEVICE...]
By default it prints a tree, one row per device with children indented beneath their parent. Given one or more DEVICE operands (a name like sda, a full path like /dev/sda, or anything resolving to a device node), it re-roots the output at those devices.
Where the data comes from #
Each column is sourced from one of four places, and any that cannot be read degrades to an empty cell rather than aborting the run:
- sysfs (
/sys/block) — the device tree, sizes, and the topology/hardware columns. Peios leaves/sys/blockunpatched, so this is the standard no-udev path. - libblkid — filesystem identity:
FSTYPE,FSVER,UUID,LABEL, and the partition-table columns. libblkid is opened at runtime. - The device node's security descriptor —
OWNERandMODE, read the same wayls -lreads them. - The
/dev/disk/by-*symlink farm —ID-LINK. Populated by the device manager once it has run; there is deliberately no/run/udev/dataparser, so the column reads the links themselves and is empty in the initramfs, where no device manager runs.
Output modes #
The mode selects how the rows are formatted; it does not change which columns are shown.
| Flag | Mode |
|---|---|
| (default) | Indented tree. |
-l, --list | Flat list — the same columns, no tree glyphs. |
-J, --json | JSON. Flag columns render as bare booleans and MOUNTPOINTS as an array; children nest under a children key. |
-P, --pairs | KEY="value" pairs, one device per line. |
-r, --raw | Raw, space-separated. Values that could contain a space or control character are hex-escaped (\xNN) so fields stay parseable. |
-T, --tree[=COLUMN] | Force tree output even alongside -l, optionally attaching the tree glyphs to COLUMN instead of NAME. |
Columns #
With no column flag, lsblk prints the default set: NAME, MAJ:MIN, RM, SIZE, RO, TYPE, MOUNTPOINTS. Three flags swap in a preset, and -o names an explicit list (which wins over all of them):
| Flag | Column set |
|---|---|
-o, --output LIST | Exactly the comma-separated columns named (case-insensitive; an unknown name is a usage error). |
-O, --output-all | Every available column. |
-f, --fs | Filesystem view: NAME, FSTYPE, FSVER, LABEL, UUID, MOUNTPOINTS. |
-m, --perms | Permissions view: NAME, SIZE, OWNER, MODE. |
The available columns are NAME, KNAME, PATH, MAJ:MIN, FSTYPE, FSVER, LABEL, UUID, PTUUID, PTTYPE, PARTTYPE, PARTLABEL, PARTUUID, MOUNTPOINT, MOUNTPOINTS, SIZE, RO, RM, HOTPLUG, TYPE, OWNER, MODE, MODEL, VENDOR, REV, SERIAL, TRAN, HCTL, ALIGNMENT, MIN-IO, OPT-IO, PHY-SEC, LOG-SEC, STATE, ROTA, SCHED, PKNAME, and ID-LINK.
The OWNER and MODE columns #
OWNER and MODE describe the device node, not the filesystem on it, and they mirror ls -l exactly — there is no GROUP column and no POSIX permission bits, because access to a device node is governed by its security descriptor, not by a mode. OWNER is the owner SID (S-1-…); MODE is a three-character [type][x][+]: the device-type character (b for a block device, c for a character device), an x slot (never set for a block device), and a + when the DACL is inheritance-protected. When the SD cannot be read — for instance on a non-Peios host with no KACS syscalls — OWNER shows ? and MODE degrades honestly.
Filtering, sorting and shaping #
| Flag | Effect |
|---|---|
-a, --all | Include empty (zero-size) devices, which are hidden by default. |
-d, --nodeps | Do not print a device's holders or slaves (drop the children). |
-I, --include LIST | Show only devices with these major numbers (comma-separated). |
-e, --exclude LIST | Exclude devices by major number. The default is 1 (RAM disks); an explicit -e replaces that default, and -a clears exclusions entirely. |
-s, --inverse | Print dependencies in inverse order (holders above the devices they depend on). |
-M, --merge | Collapse a subtree shared by several parents (a multipath device) to a single occurrence. |
-E, --dedup COLUMN | Drop rows whose COLUMN value duplicates an earlier one. |
-x, --sort COLUMN | Sort siblings by COLUMN (numeric columns sort by value, not text). |
Formatting #
| Flag | Effect |
|---|---|
-b, --bytes | Print SIZE as an exact byte count instead of a human-readable value. |
-p, --paths | Print full /dev paths in the NAME column. |
-n, --noheadings | Omit the header row. |
-i, --ascii | Draw the tree with ASCII characters instead of box-drawing glyphs. |
-y, --shell | Render column keys shell-safe (MAJ:MIN becomes MAJ_MIN). |
-w, --width NUM | Truncate each table row to NUM columns wide. |
--sysroot DIR | Read sysfs, the mount table and /dev from DIR instead of / (chiefly for testing against a fixture). |
-h, --help / -V, --version | Standard. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | Incorrect invocation (an unknown column, a malformed major-number or width value) or a failed run (for example, /sys/block is unreadable). |
lsblk uses this single non-zero code, matching util-linux. A per-device read failure is not fatal: it degrades the affected columns to empty or ? and the run continues. A broken output pipe (piping into head, for instance) is treated as success.
mkirf
Peios / Using Peios / Peiosutils / Boot images
mkirf compiles an initramfs source tree into an initramfs image. The source tree is an ordinary directory — on a running Peios system that directory is /boot/initramfs/ — whose contents map 1:1 onto / inside the initramfs. The image is a single deterministic, gzip-compressed newc cpio archive that the bootloader loads into memory alongside the kernel.
mkirf [--watch] [--debounce SECS] [--exclude GLOB]... <src-dir> <out-file>
This page is the command reference. For what the initramfs stage is — prelude, the /boot/initramfs/ layout, and the handoff to the real root — see The initramfs stage; for how hooks declare their order, see Boot hooks. mkirf reads <src-dir> and writes <out-file>; it never writes back into the source tree, so the directory you inspect is always exactly what packages and you have put there.
What a build does #
Given a source tree, one build runs a fixed sequence:
-
Validate the layout.
<src-dir>must be a directory, must carry an executableinit(the initramfs PID 1 — a symlink is fine as long as it resolves within the tree), and must not already contain a hook sequence —hooks.seqorsystem/prelude/hooks.seq.<n>— whichmkirfgenerates. A missing or non-executableinit, or a stray sequence file, stops the build. -
Walk the tree. Every object beneath
<src-dir>becomes a cpio entry — regular files (with their executable bit preserved), directories, symlinks (target stored verbatim), character/block device nodes, and FIFOs. Sockets, and any other unsupported type, are rejected. Entries are sorted inLC_ALL=Cbyte order, which places every directory before its descendants — the order the kernel's unpacker requires. -
Resolve the hook order. Hooks are the regular files directly under
<src-dir>/usr/libexec/prelude/hooks.d/(packaged) or<src-dir>/lcl/libexec/prelude/hooks.d/(operator-placed), with the operator's copy winning when both hold the same file name. The legacy<src-dir>/hooks/is still read, and each hook found there is reported with the directory it should move to.mkirfparses each hook's# /// hookmetadata block, topologically sorts the resulting capability DAG, and bakes the execution order into generated manifests injected into the image at/system/prelude/hooks.seq.<n>. A missinghooks/directory simply means no hooks — the manifests are still written, empty, because prelude treats a missing sequence as a broken image rather than as "nothing to run".The version is in the file name, and
mkirfwrites every version it can express faithfully — currentlyhooks.seq.1(the flat resolved order) andhooks.seq.2(that order plus each hook's declarations).mkirfships inpeiosutilsand prelude ships in its own package, so an image can pair a newer writer with an older reader; naming the versions separately lets one image satisfy both, which a single file carrying an in-band version cannot. A version is dropped only when a future format carries something it can no longer project honestly.They live under
/system— the tier for derived content, alongsidesystem/retc— rather than/usr, which is the vendor tier no package ships these into, or/var/state, which is for mutable state rather than immutable build output. See Validation and errors below. -
Write atomically. The archive is written to a sibling temp file and
renamed into place, so a failed run never leaves a half-written image behind. On successmkirfprints the path, entry count, and byte size to stderr.
Early (pre-decompression) segments #
A reserved <src-dir>/++/ directory, if present, holds early initramfs segments — content the kernel consumes before it decompresses the main archive, namely CPU microcode and ACPI table overrides. Each immediate child of ++/ is one segment whose own contents map onto the cpio root (++/microcode/kernel/x86/microcode/GenuineIntel.bin becomes kernel/x86/microcode/GenuineIntel.bin); the segment directory name is a human label and never appears in the archive. Every ++/ entry must itself be a directory. The segments are merged into one uncompressed cpio prepended ahead of the gzip-compressed main archive. With no ++/ directory the output is exactly a single gzip member. mkirf stays format-agnostic here: it knows "early segments", never "microcode".
The deterministic-build guarantee #
The same source tree always compiles to the same image, byte for byte. This is what makes "did anything actually change?" a meaningful question and underpins later work such as signed boot artifacts. Determinism comes from normalising everything that would otherwise vary between builds:
- Ownership, inode numbers, and timestamps are all emitted as zero.
- Permission bits are normalised. The source tree is authoritative for file type and, for regular files, executability — nothing else. Read/write permission bits are not Peios's access mechanism, so they are flattened to a constant: directories
0755, symlinks0777, FIFOs and device nodes0644, regular files0755if executable and0644otherwise. - The gzip member carries mtime 0 and no embedded filename (the
gzip -nequivalent), at compression level 9. - The early region is byte-stable, with file payloads padded to a 16-byte boundary by widening the preceding entry's name field with NUL bytes.
Validation and errors #
mkirf will not produce an image from a source tree that cannot boot. A misconfiguration is caught when the image is built — on a running system where the message is easy to read — rather than as a mystery failure at the next boot. The checks:
- Layout.
<src-dir>is not a directory; noinit, orinitis not a regular file, or is not executable; a pre-existing hook sequence; a++entry that is not a directory; two++segments defining the same file. (Exit 1.) - Hooks. A malformed
# /// hookmetadata block (unclosed, a non-comment line inside it, a duplicate or unknown key, a badly-formed array), a dependency cycle, arequiresthat nothing supplies, or a capability that is both provided and contributed to (a capability is either a set of alternatives or a set of contributors, never both). Anafternaming a capability nothing supplies is not an error — that is what distinguishes it fromrequires. (Exit 1.) A hook with no metadata block at all is not an error — it is the escape hatch: it is scheduled last, in name order, andmkirfprints a warning so a forgotten block is visible. - I/O. Any filesystem read, write, or compression failure. (Exit 1.)
- Usage. A semantic invocation error clap cannot express — currently,
--watchwith<out-file>inside<src-dir>(see below). (Exit 2.)
Watch mode #
With --watch, mkirf stays resident: it builds once up front (so the watch never begins from a stale archive), then watches <src-dir> recursively and rebuilds after every change, debounced. This is what lets /boot/initramfs/ behave like an ordinary part of the filesystem — edit a hook and the initramfs is current again — rather than a build artifact an administrator has to remember to regenerate.
Watch mode is a foreground loop that runs until killed; supervising and restarting it is a service manager's job, not mkirf's. A rebuild that fails (say, a hook edited into a dependency cycle) is logged but does not stop the watch, so fixing the offending file recovers on the next change.
<out-file> must not live inside <src-dir>: each rebuild would write the image back into the watched tree and retrigger the watch endlessly. mkirf refuses this invocation up front (exit 2). The --debounce window (default 5 seconds) is the settle time — mkirf waits for the tree to be quiet for that long before rebuilding, so a burst of edits produces one rebuild rather than many.
Options #
| Option | Effect |
|---|---|
--watch | Stay resident and rebuild on every change to <src-dir>, after an initial build. Runs until killed. |
--debounce SECS | With --watch, the settle time before a rebuild. Default 5. A non-numeric value is a usage error. |
--exclude GLOB | Exclude paths (relative to <src-dir>) matching GLOB from the image. Repeatable. * and ? stay within a path segment; ** crosses separators. A matched directory is pruned with its whole subtree. A malformed glob is a usage error. |
The two positionals are both required:
| Positional | Meaning |
|---|---|
<src-dir> | The source tree; its contents map onto / in the initramfs. |
<out-file> | The output cpio.gz archive. |
Exit status #
| Code | Meaning |
|---|---|
0 | The image was built (or --help/--version was printed). |
1 | An operational failure — an invalid layout, an unresolvable hook set, or an I/O/compression error. |
2 | A usage error — a bad option, a missing positional, or --watch with <out-file> inside <src-dir>. |
See also #
- The initramfs stage — what
mkirf's output is for. - Boot hooks — the
# /// hookmetadata block and how order is resolved. - mkuki — wraps
mkirf's image, together with a kernel and command line, into a bootable UEFI unified kernel image.
mkuki
Peios / Using Peios / Peiosutils / Boot images
mkuki builds a UEFI unified kernel image (UKI): it takes a PE/COFF EFI stub and appends the kernel, the initramfs, and the kernel command line to it as named PE sections, producing a single EFI binary that UEFI firmware boots directly. Where mkirf produces the initramfs image, mkuki is the step that wraps that image — together with a kernel and a command line — into one bootable artifact. The two are the two halves of Peios' Dynamic Boot system.
mkuki --kernel PATH --initramfs PATH (--cmdline TEXT | --cmdline-file PATH) --out PATH
[--stub PATH] [--watch] [--debounce SECS]
mkuki --stub-info
The UKI section layout #
A UKI is an ordinary PE/COFF EFI executable — the stub — with three extra sections appended, which the stub reads at boot to find its payloads:
| Section | Content | Source |
|---|---|---|
.cmdline | The kernel command line, NUL-terminated. | --cmdline or --cmdline-file |
.linux | The kernel image. | --kernel |
.initrd | The initramfs cpio image. | --initramfs |
mkuki appends them in exactly that order. Each new section is marked as initialised, read-only data. The tool works at the PE level directly: it parses the stub's DOS/PE headers and section table, computes correctly-aligned virtual addresses and file offsets for the new sections, appends their data, updates the section count and the SizeOfImage/SizeOfHeaders fields, and — if the stub's header has no spare room for three more section entries — grows the header region and relocates the existing sections' file pointers to make space. A stub that is not a valid MZ/PE image, or whose optional header is malformed, is rejected.
The command line #
The command line is taken either literally from --cmdline or read from the file named by --cmdline-file (exactly one is required). Either way, mkuki trims trailing newlines and carriage returns and appends a single NUL terminator. A command line containing an embedded NUL byte is rejected.
The stub #
--stub names the EFI stub to append to. With no --stub, mkuki uses a stub bundled into the binary (the systemd EFI stub). mkuki --stub-info prints that bundled stub's provenance and exits, so you can see exactly what the default is without building anything.
Output and atomicity #
mkuki creates any missing parent directories of --out, writes the assembled image to a sibling temp file, and renames it into place, so an interrupted or failed build never leaves a half-written UKI behind. On success it prints the output path and byte size to stderr. Each of the three payloads must be non-empty; an empty .cmdline, .linux, or .initrd fails the build.
Watch mode #
With --watch, mkuki stays resident: it builds once up front, then rebuilds the UKI whenever an input changes — so the boot image tracks a new kernel, a freshly-repacked initramfs (mkirf's half of Dynamic Boot), or an edited command-line file with no manual step. Like mkirf's watch mode, it is a foreground loop that runs until killed; supervising it is a service manager's job, and a rebuild that fails (for example, a kernel caught mid-copy) is logged rather than fatal, so fixing the input recovers on the next change.
The watched inputs are --kernel, --initramfs, and — only if it is a --cmdline-file, since a literal --cmdline is static — the command-line file. mkuki watches each input's parent directory (non-recursively), not the file itself: this survives the atomic temp-and-rename writes that mkirf and mkuki both perform (which appear as a directory event a stale single-file watch would miss) and catches a versioned kernel being swapped in /boot. Because the watch is non-recursive, a write to an --out path nested deeper under a watched directory does not retrigger it — but an --out sitting directly in a watched input directory would, so mkuki refuses that invocation. The --debounce window (default 5 seconds) is the settle time before a rebuild.
Options #
| Option | Effect |
|---|---|
--kernel PATH | The kernel image; becomes the .linux section. Required (except with --stub-info). |
--initramfs PATH | The initramfs cpio; becomes the .initrd section. Required (except with --stub-info). |
--cmdline TEXT | The kernel command line, given literally; becomes the .cmdline section. Mutually exclusive with --cmdline-file. |
--cmdline-file PATH | Read the kernel command line from PATH instead. Mutually exclusive with --cmdline. |
--out PATH | The output UKI path. Required (except with --stub-info). |
--stub PATH | The PE/COFF EFI stub to append sections to. Defaults to the bundled systemd stub. |
--stub-info | Print the bundled stub's provenance and exit. |
--watch | Stay resident and rebuild the UKI whenever --kernel, --initramfs, or --cmdline-file changes. Runs until killed. |
--debounce SECS | With --watch, the settle time before a rebuild. Default 5. |
Exactly one of --cmdline or --cmdline-file must be given: supplying both, or neither, is a usage error.
Exit status #
| Code | Meaning |
|---|---|
0 | The UKI was built (or --stub-info, --help, or --version was printed). |
1 | A build or watch failure — a missing or unreadable input, an invalid stub, an empty payload, or an I/O error. |
2 | A usage error — a bad or missing option, or an invalid --cmdline/--cmdline-file combination. |
See also #
- mkirf — builds the
.initrdpayloadmkukiwraps. - The initramfs stage — what the bundled initramfs does once firmware boots the UKI.
reg
Peios / Using Peios / Peiosutils / Registry tools
reg is the command-line interface to the live registry — the running LCS store, reached through the registry system calls. Where regman reads shipped documentation and tells you what a key means, reg talks to the kernel and reads or writes what a key is. It is the everyday tool for scripting configuration: querying effective values, writing to layers, masking and hiding, managing security descriptors, watching for change, and taking backups.
reg <command> [options] <key> [value] [data]
reg is the read/write half of the registry toolset and regman is the lookup half. The division is exact:
| To… | Use… |
|---|---|
| Read or change what a value is set to, on this machine, right now | reg |
| Look up what a value means — its type, default, valid range, when it applies | regman |
Consult regman first to learn the legal range for a knob, then use reg set to write a value inside it. reg never checks a value's meaning and regman never touches the live store; they are complementary.
How reg treats access and privilege #
Every reg operation goes through the kernel's access check against the single key it opens — the same access-control path as any other protected object, with no traversal check on the parent keys. reg performs no identity or membership checks of its own: it does not refuse a privileged operation up front. It attempts the operation and reports faithfully whatever the kernel returns. An operation that needs a privilege you lack (creating a link, a positive-precedence layer, a backup or restore, reading a SACL) simply fails with access denied (exit 3) rather than being blocked by the tool. A single reg command may legitimately span privilege tiers.
Addressing model #
Almost every subcommand shares one addressing scheme: a key path positional, and an optional value name positional. Understanding the split is most of understanding reg.
Key paths #
The key is a positional path argument. Both / and \ are accepted as separators — neither character is legal inside an LCS key component, so there is no ambiguity, and you may use whichever your shell quotes most comfortably:
reg get Machine/System/KMES
reg get 'Machine\System\KMES' # identical
A leading separator is optional and ignored (/Machine/X is the same as Machine/X). Paths are compared case-insensitively. The first component is the hive:
| Path | Meaning |
|---|---|
Machine\… | The machine hive — system-wide configuration. |
Users\<SID>\… | A specific principal's hive. |
CurrentUser\… | A kernel alias, rewritten to the caller's own Users\<SID>\ at the syscall boundary. |
On output, reg displays paths with a backslash by default. --sep=/ (or REG_SEP=/) switches display to forward slash for that invocation; this affects display only, never how a path is parsed.
The value-name positional #
The value name is a separate positional argument — it is never folded into the key path. This is deliberate: an LCS value name may itself contain / or \ (which a key component may not), so keeping them apart removes all ambiguity.
reg get Machine/System/KMES BufferCapacity
# └──── key path ─────┘ └── value ──┘
The presence or absence of the value argument selects what the command targets:
- No value argument ⇒ the command targets the key itself — its metadata, its values as a set, its security descriptor, its children.
- A value argument ⇒ the command targets that one named value.
- The default value (the empty-name value) is addressed by the literal token
@in the value-name position, mirroring.regconvention:reg get Machine\App @.
This split applies uniformly to get, set, del, mask, and unmask.
Value literals and types #
On set (and in a batch), the value's data is a single token whose registry type is either forced by a type: prefix or inferred from its shape. The registry value types and their literal syntax:
| Prefix | Registry type | Data syntax |
|---|---|---|
sz: | REG_SZ | UTF-8 string (rest of token, verbatim). |
expand: | REG_EXPAND_SZ | UTF-8 string, %VAR% left unexpanded on disk. |
dword: | REG_DWORD | 42 or 0x2A; must fit u32. |
dword-be: | REG_DWORD_BIG_ENDIAN | 42 or 0x2A; must fit u32. |
qword: | REG_QWORD | 42 or 0x2A; must fit u64. |
multi: | REG_MULTI_SZ | Comma-separated; \, escapes a literal comma. |
hex: / bin: | REG_BINARY | Hex bytes; :, -, and space separators are ignored. |
link: | REG_LINK | An absolute key path (a symlink target). |
none: | REG_NONE | No data (token must be empty after the prefix). |
When the substring before the first : is not a recognised keyword (for example http://host), the token is not treated as typed and inference applies:
| Token shape | Inferred type |
|---|---|
all decimal digits, fits u32 | REG_DWORD |
all decimal digits, fits u64 but not u32 | REG_QWORD |
0x… hex, ≤ 8 significant hex digits | REG_DWORD |
0x… hex, ≤ 16 significant hex digits | REG_QWORD |
| anything else (including empty) | REG_SZ |
Inference is intentionally broad, so any all-digit token becomes a number — a PIN, a zip code, or 007 becomes a REG_DWORD and loses its textual form. To force a string, prefix it with sz: (sz:007 stores the string 007; sz:dword:42 stores the literal string dword:42). Because the coercion is a known trap, reg set always echoes the resolved type on success, so a surprising conversion is never silent even under --quiet.
Global options #
Every subcommand accepts the following. Each behavioural toggle also has an environment-variable equivalent, so it can be set per-invocation (the flag) or once per shell or script (the variable). The flag wins over the variable, which wins over the built-in default.
| Option | Env var | Effect |
|---|---|---|
--json | REG_JSON=1 | Emit structured JSON instead of human text. watch emits JSON-lines. |
-v, --verbose | REG_VERBOSE=1 | More detailed output. |
-q, --quiet | — | Suppress non-essential output. (A surprising type coercion is still reported.) |
--sep=CHAR | REG_SEP | Path display separator: \ (default) or /. |
--help | — | Print help for the command and exit 0. |
--version | — | Print the version and exit 0. |
Mutating subcommands additionally accept:
| Option | Env var | Effect |
|---|---|---|
--layer NAME | REG_LAYER | Target layer for the write (default: the base layer). |
-y, --yes | REG_ASSUME_YES=1 | Skip the confirmation prompt on a destructive command. |
Destructive commands (del -r, restore, layer del) prompt for confirmation when standard input is a terminal, and auto-proceed when it is not — so interactive use is guarded while scripts are never blocked. Set -y/--yes or REG_ASSUME_YES=1 to skip the prompt explicitly.
Reading #
reg get <key> [value] #
Read the effective (resolved) value. With a value, prints that value's data; with no value, lists the key's effective values (the default @ value first, then named values sorted case-insensitively) — a shorthand for ls --values-only.
| Option | Effect |
|---|---|
-L, --layers | Annotate the value with the layer it resolved from and its sequence number. |
--raw | Write the value's raw bytes to standard output verbatim — for piping REG_BINARY data. |
--no-follow | Operate on a symlink key itself rather than following it to its target. |
The single-value default form prints data bare (no quotes; one element per line for REG_MULTI_SZ) so it pipes cleanly. -L shows the winning layer's provenance:
$ reg get Machine\System\KMES BufferCapacity
4194304
$ reg get Machine\App Theme -L
Theme = REG_SZ "dark" (layer: policy, seq 7)
Only the winning value is shown; the full shadowed stack beneath it is not yet exposed by any client ABI (the -L flag will render the whole stack unchanged once that primitive lands). See Layers for what "effective" resolves against.
reg ls <key> #
One-level listing of a key's immediate subkeys and values.
| Option | Effect |
|---|---|
-l, --long | Long form: for subkeys, child and value counts; for values, type and byte size. |
--keys-only | List subkeys only. |
--values-only | List values only. |
$ reg ls Machine\System\KMES -l
BufferCapacity = REG_QWORD 4194304 (8 bytes)
MaxEventSize = REG_DWORD 65536 (4 bytes)
reg tree <key> #
Recursively list the subkey tree rooted at key.
| Option | Effect |
|---|---|
--depth N | Limit recursion to N levels. |
--values | Include each node's values, not just its keys. |
$ reg tree Machine\System --depth 2
reg info <key> #
Print a key's metadata: leaf name, subkey and value counts, last-write time, hive generation, security-descriptor size, and the volatile and symlink flags.
| Option | Effect |
|---|---|
--no-follow | Inspect a symlink key itself rather than its target. |
$ reg info Machine\System\KMES
path Machine\System\KMES
name KMES
subkeys 0
values 4
last_write_ns 1717243800000000000
hive_generation 42
sd_size 164
volatile false
symlink false
Writing #
reg set <key> <value> <data> #
Create or update a value. The value name (or @) and the data token are both required. Writes are tagged with --layer (default: base). See value literals for the data syntax.
| Option | Effect |
|---|---|
--layer NAME | Write into layer NAME instead of the base layer. |
-p, --parents | Create any missing ancestor keys. |
--expected-seq N | Compare-and-swap guard: apply only if the value's current sequence is N; a mismatch exits 6 without writing. |
$ reg set Machine\App Build 4096
set Machine\App Build = REG_DWORD 4096 (layer: base)
$ reg set Machine\App Servers multi:alpha,beta --layer policy
--expected-seq supports lock-free read-modify-write: read the value with -L to learn its sequence, then write back with --expected-seq set to that number; if another writer changed it in between, your write fails cleanly instead of clobbering theirs.
reg new <key> #
Create a key with no values. Reports whether the key was created or already existed.
| Option | Effect |
|---|---|
--layer NAME | Create the key in layer NAME. |
-p, --parents | Create any missing ancestor keys. |
--volatile | Create a volatile (RAM-only) key that does not survive a reboot. |
$ reg new Machine\App\Cache --volatile
created Machine\App\Cache (layer: base)
reg del <key> [value] #
Delete a value, or a key. (Alias: delete.)
With a value, deletes this layer's entry for that value only — lower-layer entries resurface, because a per-layer delete is not a tombstone (use mask for that). With no value, deletes the key, which must be empty unless -r is given.
| Option | Effect |
|---|---|
--layer NAME | Delete from layer NAME. |
-r, --recursive | Delete the key and all its descendants (walks and removes children). |
-y, --yes | Skip the confirmation prompt (recursive deletes prompt on a terminal). |
$ reg del Machine\App Legacy
deleted value Legacy from Machine\App
$ reg del Machine\App\Temp -r
Recursively delete key Machine\App\Temp and all its contents? [y/N] y
deleted Machine\App\Temp (3 keys)
See Deleting keys and values for how a per-layer delete differs from a tombstone.
Masking and hiding #
These commands expose LCS's tombstone and hide primitives, which mask lower-precedence state rather than removing a layer's own entry — the distinction that makes layers revertible. The layer is chosen with --layer (default: base).
reg mask <key> [value] / reg unmask <key> [value] #
mask sets a tombstone: a per-value marker in the target layer that hides that value from all layers below it. unmask clears it.
| Option | Effect |
|---|---|
--layer NAME | The layer that carries the tombstone. |
--all | Blanket tombstone: mask all lower-layer values on the key (no value argument). |
A value is required unless --all is given.
$ reg mask Machine\App ApiKey --layer policy
set tombstone on Machine\App ApiKey (layer: policy)
$ reg mask Machine\App --all --layer policy
set blanket tombstone on Machine\App (layer: policy)
reg hide <key> / reg unhide <key> #
hide installs a HIDDEN path entry in the target layer, masking the key's existence in that layer and below. unhide removes that layer's path entry, letting the key reappear.
| Option | Effect |
|---|---|
--layer NAME | The layer that hides (or unhides) the key. |
$ reg hide Machine\App\Debug --layer policy
hid Machine\App\Debug (layer: policy)
Layers #
reg layer ls #
List layers with name, precedence, enabled state, and (informational) owner SID, ordered by descending precedence.
| Option | Effect |
|---|---|
-l, --long | Long form. |
$ reg layer ls
policy prec=100 enabled owner=S-1-5-32-544
base prec=0 enabled
reg layer new <name> #
Create a layer by writing its metadata key under Machine\System\Registry\Layers\.
| Option | Effect |
|---|---|
--precedence N | Precedence (higher wins). A precedence above 0 requires SeTcbPrivilege; the tool attempts it and reports any denial. |
--owner SID | Record an informational owner SID. |
--disabled | Create the layer disabled. |
$ reg layer new policy --precedence 100 --owner S-1-5-32-544
created layer policy (precedence 100, enabled)
reg layer set <name> #
Modify an existing layer's metadata.
| Option | Effect |
|---|---|
--precedence N | Change the precedence. |
--enable / --disable | Enable or disable the layer. |
--owner SID | Change the informational owner SID. |
reg layer del <name> #
Delete a layer — removes its metadata key; LCS tears down the layer's entries. Prompts for confirmation on a terminal.
$ reg layer del policy
Delete layer policy and all its entries? [y/N] y
deleted layer policy
Managing per-process private layer views is out of scope for reg; see private hives and layers.
Security descriptors #
reg sd <key> #
Show or set a key's security descriptor as SDDL, using the same codec as the sd tool, so its output is consistent. With no scope flags, the default is owner + group + DACL (a SACL requires the privileged ACCESS_SYSTEM_SECURITY right).
| Option | Effect |
|---|---|
--set SDDL | Apply the given SDDL. Without it, sd prints the current descriptor. |
--owner | Scope to the owner. |
--group | Scope to the group. |
--dacl | Scope to the DACL. |
--sacl | Scope to the SACL (needs ACCESS_SYSTEM_SECURITY). |
$ reg sd Machine\App
O:BAG:BAD:(A;;KA;;;BA)(A;;KR;;;WD)
$ reg sd Machine\App --set 'O:BAG:BAD:(A;;KA;;;BA)' --owner --dacl
set security descriptor on Machine\App
A security-descriptor change takes effect on future opens only (existing handles keep the access mask they were granted). Security changes are not layered and are not undone by layer removal — they mutate the key object directly. See Access control on keys.
Symlinks #
reg link <key> <target> #
Create a symlink key pointing at the absolute target key path. This creates a key with the immutable symlink flag and a default REG_LINK value holding the target. Requires KEY_CREATE_LINK and SeTcbPrivilege/Administrator; the tool attempts it and reports any denial.
| Option | Effect |
|---|---|
--layer NAME | Create the link key in layer NAME. |
$ reg link Machine\App\CurrentConfig Machine\App\Config\v3
linked Machine\App\CurrentConfig -> Machine\App\Config\v3 (layer: base)
get and info follow symlinks to their target by default; pass --no-follow to inspect the link key itself. See Advanced: registry links.
Watching #
reg watch <key> #
Arm a change watch and stream one event per change until interrupted (or until --count events). Each event faithfully means "something under the filter changed — re-read"; reg does not decode the change records' contents. With --json, emits one JSON object per line.
| Option | Effect |
|---|---|
--subtree | Watch descendant keys too, not just this key. |
--filter LIST | Comma-separated subset of value, subkey, sd (default: all). |
--count N | Exit after N events. |
$ reg watch Machine\System\KMES --subtree --filter value
changed: Machine\System\KMES
changed: Machine\System\KMES
See Watching for changes for why a service watches instead of polling.
Batch, export, backup, and restore #
Two paths move a subtree around: export/apply are the portable, reviewable path (a diffable text or JSON document); backup/restore are the exact, privileged path (an opaque kernel snapshot).
reg apply <file> #
Apply a batch of operations to one hive in a single transaction — all-or-nothing. All operations must target one hive (a cross-hive batch fails with exit 4). - reads standard input.
| Option | Effect |
|---|---|
--dir DIR | Apply every batch file in DIR (sorted), each as its own transaction. A missing or empty directory applies nothing and succeeds. |
--once-delete | Delete each batch file after it applies successfully (a drain). |
-y, --yes | Skip confirmation. |
The batch format is JSON (canonical and exact) or a line-oriented text format; apply auto-detects (a leading { or [ means JSON). Text-format input is not yet implemented — supply JSON, or generate one with reg export --json. Text export works for review.
$ reg export --json Machine\App - | reg apply -
applied 4 keys
reg export <key> [file] #
Dump a subtree to the batch format. Omit file (or pass -) to write to standard output. Human-readable text by default; --json emits the canonical exact form.
| Option | Effect |
|---|---|
--layer NAME | Export one layer's view (default: the effective state). |
$ reg export Machine\App app-config.reg
reg backup <key> <file> #
Write an opaque, exact, binary kernel snapshot of a key and its subtree to file. Requires SeBackupPrivilege.
$ reg backup Machine\System\KMES kmes.snap
backed up Machine\System\KMES -> kmes.snap
reg restore <key> <file> #
Replace a key and its entire subtree from a snapshot, in one transaction. Requires SeRestorePrivilege. Prompts for confirmation on a terminal.
$ reg restore Machine\System\KMES kmes.snap
Replace key Machine\System\KMES and its entire subtree from kmes.snap? [y/N] y
restored Machine\System\KMES from kmes.snap
See Backup and restore for when to reach for each path.
Output formats #
Default output is terse, human-readable, and grep-friendly. --json switches every command to structured output — a single self-contained JSON document, except watch, which emits JSON-lines (one object per event). In any value listing, the default (@) value is emitted first, then named values sorted case-insensitively.
Type formatting on read:
| Registry type | Human form | JSON form |
|---|---|---|
REG_SZ / REG_EXPAND_SZ | the string | {"type":"sz","data":"…"} |
REG_DWORD / REG_QWORD | decimal | {"type":"dword","data":42} |
REG_MULTI_SZ | one element per line | {"type":"multi","data":["a","b"]} |
REG_BINARY | hex | {"type":"binary","data":"deadbeef"} |
REG_LINK | the target path | {"type":"link","data":"…"} |
Exit status #
| Code | Meaning | Typical cause |
|---|---|---|
0 | Success. | — |
1 | Usage error. | Bad arguments; a required option missing. |
2 | Key or value not found. | ENOENT. |
3 | Access denied. | EACCES, EPERM — the kernel refused the operation. |
4 | Invalid specification. | Bad path, type, or literal; a cross-hive batch; a name too long (EINVAL, EXDEV, ENAMETOOLONG). |
5 | Syscall or source failure. | EIO, ETIMEDOUT, ENOSPC, ENOMEM. |
6 | Compare-and-swap conflict. | EAGAIN — an --expected-seq guard did not match; the value changed under you. |
A denial (3) reports the operation, the target, and, where relevant, the access that was required — in the style of the sd and token tools.
See also #
regman— the registry manual: what a key or value means, as opposed to what it is set to. Consult it before youreg set.- Keys, values, and types — the data model
regaddresses. - Layers — what "effective" resolves against, and what
--layer,mask, andhideoperate on. - Access control on keys — the check every
regoperation goes through.
regman
Peios / Using Peios / Peiosutils / Registry tools
Configuration, not storage established that the registry holds values but not their meaning: it keeps a type tag and some bytes, and never knows that BufferCapacity must be a power of two or what it is for. That knowledge has to live somewhere a person can look it up. It lives in regman, the registry's manual.
regman is the man of the registry. Give it a path and it tells you what a key or value actually is. It is the everyday tool for configuring a Peios system — before you touch a knob, regman is how you find out what it does and what changing it will cost.
regman, in one sentence #
regman <path> [value] documents what a registry key or value means — its type, default, valid values, when a change takes effect, and a prose description — drawn from shipped documentation, never from the live registry.
That last clause is the one to hold onto; there is a section on it below.
Looking up a value #
Give regman a key path and a value name and it prints a knob-card — an identity line, an aligned block of facts, and a description:
$ regman Machine\System\KMES BufferCapacity
Machine\System\KMES BufferCapacity documented by kmes
Type REG_QWORD
Default 4194304 (4 MB)
Valid 65536–268435456 bytes (64 KB–256 MB), power of two
Applies live — ring-buffer swap
Per-CPU ring buffer capacity, in bytes. Must be a power of two; values
that are not are treated as invalid and ignored.
Every registry value is a knob, and every knob has the same few facts worth knowing, so the card is uniform rather than free-form:
| Field | Tells you |
|---|---|
| Type | The value's registry type (REG_QWORD, REG_SZ, …). |
| Default | The value used when nothing is set. |
| Valid | The range, set, or constraint a sensible value must satisfy. |
| Applies | When a change takes effect — live, on restart, or on reboot. |
The Applies field is the one operators reach for most: it is the difference between "edit it and you're done" and "edit it and schedule a reboot". Only the fields that make sense for a given item are shown, and a knob that is being retired carries a prominent deprecation notice at the top of its card.
Looking up a key #
Give regman just a key — no value — and it does double duty as an index: the key's own description, then a list of every value documented under it, one summary line each.
$ regman Machine\System\KMES
Machine\System\KMES documented by kmes
The KMES event subsystem reads its tuning parameters from the values
under this key...
Values
BufferCapacity Per-CPU ring buffer capacity in bytes.
MaxEventSize Max total size of a userspace-emitted event.
MaxNestingDepth Max nesting depth for userspace event payloads.
MaxEmitRatePerProcess Max events per second a single process may emit.
So you can start at a subtree, see what is configurable there, and drill into any single value.
Searching, when you do not know the path #
If you don't know where a setting lives, regman -k searches names and summaries — the registry's apropos:
$ regman -k buffer
Machine\System\KMES BufferCapacity Per-CPU ring buffer capacity in bytes.
regman documents intent, not current state #
This is the boundary that matters most. regman tells you what a setting should be — what it means and which values are legal — not what it is currently set to on this machine. It reads shipped documentation, never the live registry; it does not talk to LCS at all. Ask regman about BufferCapacity and you learn it must be a power of two and defaults to 4 MB; you do not learn that this particular box has it set to 8 MB right now. Reading the current value is a separate, live query against the registry — that is reg's job. regman tells you a knob should be a power of two; reg get tells you what it is set to, and reg set changes it. Reach for regman to decide what to write, and reg to write it.
Put regman next to the other two things from Configuration, not storage and a clean division of labour appears:
| To learn… | Look at… |
|---|---|
| What a setting means, and what is valid | regman — the manual |
| What the value is set to | reg — a live query against the store |
| What the subsystem is actually running on | the event log |
regman is the one you consult first, and the only one that works before you have changed anything — because documentation ships with the software, not with the machine's state. It is the natural complement to reject-or-keep: regman tells you the valid range up front, so you set a good value the first time instead of writing one the owning subsystem will quietly refuse.
Where the documentation comes from #
regman has no built-in database of every setting. Each package ships its own documentation as files dropped into a well-known directory (/usr/share/regman/), one fragment per package documenting that package's whole configuration surface. Install a package and its registry documentation appears; remove the package and it goes away. The package that owns a set of keys is the package that documents them — the only arrangement that stays correct as the system changes.
A consequence worth knowing: regman can only describe settings that some installed package has documented. An undocumented key is invisible to it — regman is exactly as complete as the packages on the machine make it. If two packages happen to document the same item, regman shows both and flags the overlap rather than silently picking a winner.
(For the people who write that documentation, regman fmt and regman lint prepare and check fragments, and regman index keeps lookups fast on large corpora — the lookup is always correct without an index, which is purely an accelerator. These are packaging concerns rather than everyday operator ones.)
Where to go next #
For the idea regman exists to serve — why the registry holds values without holding their meaning, and what reject-or-keep means — read Configuration, not storage.
For the other half of the picture — reading what a value is actually set to, which is a live query against the store rather than a documentation lookup — read LCS and sources.