Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Peios

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 #

CommandPurpose
cpCopy files and directories.
mvMove or rename files and directories.
rmRemove files, and with -r, directories.
rmdirRemove directories, but only empty ones.
mkdirCreate directories.
mkfifoCreate named pipes (FIFOs).
mknodCreate special files — device nodes and FIFOs.
touchCreate empty files, or update a file's timestamps.
lnCreate hard links and symbolic links.
link and unlinkThe single-purpose, low-level versions of "make a hard link" and "remove a file".
shredDestroy a file's contents by overwriting them, so they cannot be recovered.
ddCopy data block by block, converting it on the way.
dfReport how much space each file system has, and how much is free.
duReport how much space files and directories use.
truncateShrink or extend a file to an exact size.
mktempCreate 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:

OptionEffect
-r, -R, --recursiveCopy directories, and everything inside them, recursively.
-a, --archiveA 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:

AttributeWhat it carries from the source
ownerThe owner SID.
daclThe whole DACL — every access rule on the file, inherited rules included.
saclThe whole SACL — the audit rules and the integrity label.
daclniThe DACL with inherited rules stripped out — only the rules set explicitly on the source.
saclniThe SACL with inherited rules stripped out.
timestampsThe access and modification times.
linksThe hard-link structure — files hard-linked together in the source stay hard-linked in the copy.
securityThe security.peios.* extended attributes (other than the descriptor itself).
xattrsAll other extended attributes.

You rarely name those individually. These options select sensible sets:

OptionPreserves
-pTimestamps only.
--preserveTimestamps only — the same as -p.
--preserve=LISTExactly the comma-separated attributes you name.
--preserve-allEvery attribute in the table above.
--sdowner, dacl, sacl — the full security descriptor, copied verbatim.
--sd-explicitowner, daclni, saclni — the source's explicitly set rules only.
--no-preserve=LISTTurns 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.

  • --sd copies 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-explicit copies 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.

OptionEffect
-i, --interactiveAsk before overwriting an existing file.
-n, --no-clobberNever 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, --forceIf an existing destination cannot be opened for writing, remove it and try the copy again.
--remove-destinationRemove 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.

OptionEffect
-L, --dereferenceAlways follow symbolic links — copy the file they point to.
-P, --no-dereferenceNever follow symbolic links — copy the link itself.
-HFollow only the symbolic links named directly on the command line, not links found while recursing.
-dCopy 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.

OptionEffect
-l, --linkCreate a hard link to the source instead of copying its data.
-s, --symbolic-linkCreate 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-onlyCreate the destination and copy its attributes, but not its data.

Other options #

OptionEffect
-t, --target-directory=DIRCopy every source into DIR. Useful when the directory is not the last argument.
-T, --no-target-directoryTreat dest as a plain file even if it is a directory.
--parentsRecreate the source's leading directories under the destination.
-x, --one-file-systemDo not cross into a different file system while recursing.
--strip-trailing-slashesStrip any trailing slashes from each source argument before using it.
-v, --verbosePrint each file as it is copied.
--debugExplain in detail how each file was copied. Implies -v.
--progressShow a progress bar.

Exit status #

CodeMeaning
0Every file was copied successfully.
1A 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.

OptionEffect
-i, --interactiveAsk before overwriting an existing file.
-f, --forceDo not prompt before overwriting.
-n, --no-clobberNever 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 #

OptionEffect
-t, --target-directory=DIRMove every source into DIR.
-T, --no-target-directoryTreat dest as a plain file even if it is a directory.
--strip-trailing-slashesStrip any trailing slashes from each source argument.
-v, --verbosePrint each file as it is moved.
--debugExplain in detail how each file was moved. Implies -v.
--progressShow a progress bar.

Exit status #

CodeMeaning
0Every file was moved successfully.
1A 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:

OptionEffect
-r, -R, --recursiveRemove directories and all of their contents, recursively.
-d, --dirRemove 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 #

OptionEffect
-f, --forceNever prompt. Also ignore files that do not exist instead of reporting them.
-iPrompt before every removal.
-IPrompt 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.

OptionEffect
--preserve-rootRefuse to recurse on /. This is the default; you do not need to ask for it.
--no-preserve-rootDisable the failsafe. Required, in full and unabbreviated, if you genuinely intend a recursive operation on /.

Other options #

OptionEffect
-v, --verbosePrint each file as it is removed.
--progressShow 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 #

CodeMeaning
0Every requested removal succeeded (or, with -f, the file did not exist).
1A 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 #

OptionEffect
-p, --parentsRemove 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-emptyDo 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, --verbosePrint a line for each directory as it is removed.

Exit status #

CodeMeaning
0Every directory was removed.
1A 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:

OptionEffect
-p, --parentsCreate any missing parent directories along the way. Also makes mkdir succeed quietly if the directory already exists.
-v, --verbosePrint 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.

FlagArgumentEffect
--ownerSIDMake this principal the owner, instead of you.
--groupSIDSet the object's group.
--labellevelGive the object a mandatory integrity label.
--no-inherit—Do not inherit rules from the parent directory; lock the object to its owner.
--sddlSDDL stringSupply 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 #

CodeMeaning
0Every directory was created.
1A 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 #

CodeMeaning
0Every FIFO was created.
1A 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:

TypeCreates
bA block device — a device addressed in fixed-size blocks, such as a disk.
c or uA character device — a device addressed as a stream of bytes, such as a terminal.
pA 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 #

CodeMeaning
0The special file was created.
1It 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, touch creates it, empty;
  • if the file does exist, touch updates 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.

OptionEffect
-aChange only the access time.
-mChange only the modification time.
--time=WORDChoose which time to change by name: access, atime, or use for the access time; modify or mtime for the modification time.
-d, --date=STRINGUse the time described by STRING instead of the current time.
-t STAMPUse the time given as [[CC]YY]MMDDhhmm[.ss] instead of the current time.
-r, --reference=FILEUse the timestamps of FILE instead of the current time.
-h, --no-dereferenceIf 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:

OptionEffect
-c, --no-createDo 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 #

CodeMeaning
0Every file was created or updated as requested.
1A 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 named link_name.
  • ln target — create a link to target in the current directory, with the same final name.
  • ln target... directory — create a link to each target inside directory.
  • ln -t directory target... — the same, with the directory named first.

Options #

OptionEffect
-s, --symbolicMake symbolic links instead of hard links.
-f, --forceRemove an existing destination file so the link can be created.
-i, --interactiveAsk before removing an existing destination.
-r, --relativeFor a symbolic link, write the target as a path relative to the link's own location, rather than an absolute path.
-n, --no-dereferenceIf 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, --logicalWhen the target is a symbolic link, link to what it points to.
-P, --physicalMake a hard link to a symbolic link itself, rather than to its target.
-t, --target-directory=DIRCreate all links inside DIR.
-T, --no-target-directoryTreat 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=SUFFIXUse SUFFIX for backup file names.
-v, --verbosePrint 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 #

CodeMeaning
0Every link was created.
1A 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:

CodeMeaning
0The operation succeeded.
1The 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 #

OptionEffect
-n, --iterations=NOverwrite 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, --zeroAdd a final pass that writes zeros, so the file does not visibly look shredded afterwards.
-s, --size=NShred N bytes, rather than the whole file. Accepts size suffixes such as K, M, G.
-x, --exactDo 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=FILETake the random bytes for the overwrite passes from FILE.
-v, --verboseShow progress as each pass runs.
-f, --forceOverride 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 #

CodeMeaning
0Every file was shredded successfully.
1A 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 #

OperandMeaning
if=FILERead input from FILE.
of=FILEWrite output to FILE.
bs=BYTESRead and write BYTES at a time. Sets both the input and output block size at once.
ibs=BYTESInput block size (default 512).
obs=BYTESOutput block size (default 512).
cbs=BYTESConversion block size — used by the block and unblock conversions.
count=NStop after N input blocks, rather than reading to the end.
skip=NSkip N input blocks before copying. Also spelled iseek=N.
seek=NSkip N output blocks before writing. Also spelled oseek=N.
conv=LISTApply the comma-separated conversions below.
iflag=LISTComma-separated input flags — how the input is opened and read.
oflag=LISTComma-separated output flags — how the output is opened and written.
status=LEVELHow 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=) #

ValueEffect
ucase / lcaseConvert the data to upper-case / lower-case.
swabSwap every adjacent pair of bytes.
syncPad each input block out to its full size — with zeros, or with spaces alongside block/unblock.
block / unblockConvert between newline-terminated lines and fixed-size cbs records.
sparseWhere an output block is all zeros, seek past it instead of writing it.
exclFail if the output file already exists.
nocreatFail if the output file does not already exist.
notruncDo not truncate the output file when opening it.
noerrorContinue past read errors instead of stopping.
fdatasync / fsyncFlush 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=) #

FlagApplies toEffect
count_bytesinputInterpret count=N as a number of bytes, not blocks.
skip_bytesinputInterpret skip=N as a number of bytes.
fullblockinputWait for a full ibs of data on each read.
seek_bytesoutputInterpret seek=N as a number of bytes.
appendoutputOpen the output in append mode.
directbothUse direct I/O, bypassing the cache.
dsync / syncbothUse synchronised I/O — for data, or for data and metadata.
nonblockbothUse non-blocking I/O.
noatimebothDo not update the file's access time.
nocachebothAsk the system to drop the file's cached pages.
directorybothFail 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 #

CodeMeaning
0The copy completed.
1The 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.

OptionEffect
-h, --human-readableShow sizes with unit suffixes — 456G, 12M — using powers of 1024.
-H, --siLikewise, but using powers of 1000.
-B, --block-size=SIZEShow sizes scaled to SIZE — -BM reports in megabytes.
-kUse 1024-byte blocks.

Choosing what to report #

OptionEffect
-a, --allInclude dummy and otherwise-hidden file systems that df would normally skip.
-l, --localReport only local file systems, skipping network-mounted ones.
-t, --type=TYPEReport only file systems of type TYPE.
-x, --exclude-type=TYPEReport everything except file systems of type TYPE.
--totalAdd a final line giving the grand total across everything reported.

Changing the output #

OptionEffect
-i, --inodesReport 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-typeAdd 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, --portabilityUse a fixed, simple column layout that does not wrap a long device name onto its own line.

Sync behaviour #

OptionEffect
--syncFlush pending writes before reading the usage figures, for the most up-to-date numbers.
--no-syncDo not flush first. This is the default and is faster.

Exit status #

CodeMeaning
0The report was produced.
1A 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 #

OptionEffect
-a, --allList every file, not only directories.
-s, --summarizePrint only a single total for each argument — no per-subdirectory breakdown.
-d, --max-depth=NShow totals only down to N levels below each argument. -d 0 is the same as -s.
-S, --separate-dirsGive each directory's own size, excluding its subdirectories.
-c, --totalAdd a final grand-total line.
--inodesCount inodes used rather than space used.

What "size" means #

OptionEffect
-h, --human-readableSizes with unit suffixes — 4.2G — using powers of 1024.
--siLikewise, using powers of 1000.
-B, --block-size=SIZEScale sizes to SIZE.
-k / -mUse 1024-byte / 1024×1024-byte units.
--apparent-sizeReport 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, --bytesReport plain byte counts. Equivalent to --apparent-size --block-size=1.

Following symbolic links #

By default du does not follow symbolic links.

OptionEffect
-L, --dereferenceFollow all symbolic links and count what they point to.
-D, -H, --dereference-argsFollow only the symbolic links named directly on the command line.
-P, --no-dereferenceFollow no symbolic links. This is the default.
-x, --one-file-systemDo not cross into a different file system while walking.
-l, --count-linksCount a hard-linked file every time it is encountered, instead of once.

Limiting the output #

OptionEffect
--exclude=PATTERNSkip files and directories whose name matches PATTERN.
--exclude-from=FILESkip everything matching any pattern listed in FILE.
-t, --threshold=SIZEShow only entries at least SIZE (or, with a negative SIZE, at most that big).
--files0-from=FTake 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, --nullEnd each output line with a NUL character instead of a newline.
-v, --verboseReport extra detail about what is being processed.

Exit status #

CodeMeaning
0The report was produced.
1A 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:

PrefixMeaning
+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 #

OptionEffect
-s, --size=SIZESet or adjust the size to SIZE, as described above.
-r, --reference=RFILEBase 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-createDo not create a file that does not exist; skip it instead.

Exit status #

CodeMeaning
0Every file was resized.
1A 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 #

OptionEffect
-d, --directoryCreate 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=SUFFIXAppend SUFFIX after the random part of the name — for giving the file an extension.
-u, --dry-runDo 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, --quietPrint no error message if creation fails; rely on the exit status.

Exit status #

CodeMeaning
0The temporary file or directory was created; its name was printed.
1Creation 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 #

GroupSubcommandDoes
InspectshowPrint the descriptor on a path.
checkSimulate an access check against the path.
DACLallowAdd an allow rule for one or more principals.
denyAdd a deny rule for one or more principals.
removeDrop every DACL rule for the named principals.
AuditingauditAdd an audit rule to the SACL.
unauditDrop every SACL rule for the named principals.
OwnershipownerSet the descriptor's owner.
groupSet the descriptor's group.
IntegrityintegritySet the mandatory integrity label.
InheritanceinheritTurn inheritance protection on or off.
resetDrop the file's own rules and re-inherit from the parent.
propagatePush inheritance down to descendants.
WholesalesetReplace the entire descriptor at once.

Naming a principal #

Wherever a subcommand takes a PRINCIPAL, it accepts any of:

FormExampleMeaning
@self@selfThe user SID of the token running sd.
@owner@ownerA placeholder that the access check substitutes with the file's own owner.
A well-known labelEveryone, Administrators, LocalSystemA named built-in principal.
A two-letter aliasWD, BA, SYThe short alias for a well-known principal.
A raw SIDS-1-5-32-544Any 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:

FormExampleMeaning
Single lettersrwx, r, mr read, w write, x execute, d delete, m modify, f full, c change-permissions, o take-ownership.
Wordsread,write, modifyThe same set, spelled out.
Fine-grained namesread-data,append,traverseIndividual low-level rights, for precise rules.
Raw hex0x1F01FFAn 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
FlagEffect
--sddlRender the descriptor as an SDDL string.
--rawRender SIDs in raw S-1-… form only.
--labelRender SIDs as their labels where known.
--allVerbose — decode every flag and show raw masks alongside.
--jsonEmit 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 / flagEffect
PERMSThe access to test for.
--pid PIDCheck against process PID's token instead of your own.
--explainShow 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
FlagEffect
--flags LISTACE inheritance flags — CI container-inherit, OI object-inherit, NP no-propagate, IO inherit-only; none clears them.
--if EXPRMake it a conditional rule, applied only when EXPR is true.
--replaceDrop any existing rules for this principal and kind first, instead of appending.
--recursive, -rApply 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
FlagEffect
--allow-emptyPermit the result to be a present-but-empty DACL, which denies everyone. Without this, sd refuses to produce one.
--recursive, -rApply 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.

FlagEffect
--policy BITSThe label's policy bits, comma-separated: NW no write-up, NR no read-up, NX no execute-up.
--recursive, -rApply 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.

FlagEffect
--strip-inheritedWhen turning protection off, also drop the inherited rules already on the file.
--recursive, -rApply 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 / flagEffect
SDDLThe new descriptor as an SDDL string. - reads it from standard input.
--binary FILEInstead of SDDL, read the raw descriptor bytes from FILE (- for standard input).
--components LISTOverride which parts of the descriptor (owner, group, DACL, SACL) the operation writes.

Common flags #

These apply across the subcommands:

FlagEffect
--recursive, -rApply the change to every descendant of the path.
--no-follow-symlinks, -POperate on a symbolic link itself, not the file it points to.
--jsonEmit JSON instead of human-readable output.

Exit status #

CodeMeaning
0The operation succeeded — or, for sd check, the access would be allowed.
non-zeroThe 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 #

CommandPurpose
lsList the contents of a directory. The everyday command for "what's in here?".
dir and vdirls with a fixed output style. dir lists in columns; vdir lists in long format.
statShow the detailed status of one file — its type, size, timestamps, and raw inode metadata.
pwdPrint the directory you are currently working in.
readlinkPrint where a symbolic link points.
realpathResolve a path to its canonical, absolute form — every symlink followed, every . and .. removed.
basenameStrip the directory part off a path, leaving just the final name.
dirnameThe opposite of basename — strip the final name, leaving the directory.
pathchkCheck whether a path name is valid and portable before you try to create it.
dircolorsProduce 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 -l surfaces 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.
  • stat is 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. stat reports 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. ls leaves hidden entries out unless you ask for them with -a or -A.
  • Entries are sorted by name. Alphabetical order, case-sensitive. Other sort orders are available.
  • The layout adapts to where output goes. When ls is 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:

CharacterType
-Regular file
dDirectory
lSymbolic link
pNamed pipe (FIFO)
sSocket
cCharacter device
bBlock 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.

OptionEffect
-a, --allShow hidden entries too, including . (the directory itself) and .. (its parent).
-A, --almost-allShow hidden entries, but leave out . and ...
-d, --directoryList 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, --recursiveDescend into every subdirectory and list it too.
-B, --ignore-backupsSkip entries whose name ends in ~.
--ignore=PATTERNSkip entries whose name matches the shell pattern. May be given more than once.
--hide=PATTERNLike --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.

OptionSort order
-tBy modification time, newest first.
-SBy size, largest first.
-XBy file extension, alphabetically.
-vBy version: runs of digits in the name sort numerically, so f2 comes before f10.
-UNo sorting at all — entries appear in the order the directory stores them. Fast for very large directories.
-r, --reverseReverse the current sort.
--sort=WORDChoose the sort key by name: none, time, size, extension, version, or width.
--group-directories-firstList 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.

OptionLayout
-CColumns, filled top-to-bottom. The default when writing to a terminal.
-xColumns, filled left-to-right (across the rows) instead.
-1One entry per line. The default when output is not a terminal.
-mAll entries on as few lines as possible, separated by commas.
-l, --longThe long format described above.
--format=WORDChoose the layout by name: across, commas, horizontal, long, single-column, or vertical.
-w, --width=COLSAssume 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.

OptionEffect
-F, --classifyAppend a type symbol to each name: / for a directory, @ for a symlink, | for a FIFO, = for a socket, and * for an executable file.
--file-typeThe same, but without the * on executables.
-pAppend only the / on directories.
--indicator-style=WORDChoose 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.

OptionEffect
-h, --human-readablePrint sizes with a unit suffix — 4.0K, 234M, 56G — using powers of 1024.
--siLike -h, but using powers of 1000, so the suffixes are decimal.
-s, --sizePrint the disk space each entry occupies, in blocks, before its name.
-k, --kibibytesUse 1024-byte blocks for the size figures and directory totals.
--block-size=SIZEScale 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.

OptionTimestamp
-uAccess time — when the file was last read.
-cStatus-change time — when the file's metadata last changed.
--time=WORDChoose the timestamp by name: atime/access, ctime/status, mtime/modification, or birth/creation.
--time-style=STYLEChoose the date format: full-iso, long-iso, iso, locale, or +FORMAT for a custom layout.
--full-timeShorthand 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.

OptionEffect
-L, --dereferenceAlways report on the file a link points to, not the link.
-H, --dereference-command-lineDereference only the links named directly on the command line, not links found inside a listed directory.
--dereference-command-line-symlink-to-dirDereference 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.

OptionEffect
-i, --inodePrint each entry's index number (its inode number).
-Z, --contextPrint each entry's security context. Available only when the build enables it.
-b, --escapePrint non-printable characters in names using C-style backslash escapes.
-q, --hide-control-charsReplace non-printable characters in names with ?. The default when writing to a terminal.
--show-control-charsPrint names verbatim, control characters and all.
-N, --literalPrint names exactly, with no quoting.
-Q, --quote-nameWrap each name in double quotes.
--quoting-style=WORDChoose the quoting scheme: literal, shell, shell-always, shell-escape, c, escape, and others.
-T, --tabsize=COLSAssume tab stops every COLS columns when laying out output.
-fList 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, --diredEmit extra position markers designed for the Emacs dired editing mode.
--zeroEnd each line with a NUL character instead of a newline, and list one entry per line.

Exit status #

CodeMeaning
0Success — everything requested was listed.
1A minor problem — for example, a named file could not be accessed. The rest of the listing still printed.
2A 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:

CommandLayoutEquivalent ls
dirColumns, regardless of where output goes.ls -C
vdirLong 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:

CodeMeaning
0Success.
1A minor problem — a named file could not be accessed.
2A 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.

OptionEffect
-c FORMAT, --format=FORMATPrint FORMAT for each file, substituting % directives. A newline is added after each file.
--printf=FORMATLike --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):

DirectiveSubstitutes
%nFile name.
%NQuoted file name; for a symlink, with the target shown.
%FFile type in words (regular file, directory, …).
%sTotal size, in bytes.
%bNumber of allocated blocks.
%BSize in bytes of each block counted by %b.
%oPreferred I/O transfer block size.
%iInode number.
%hNumber of hard links.
%d / %DDevice number, in decimal / in hexadecimal.
%t / %TFor a device file, the major / minor device type, in hexadecimal.
%mMount point of the file system the file is on.
%fRaw mode value, in hexadecimal.
%x / %XLast access time — human-readable / seconds since the epoch.
%y / %YLast modification time — human-readable / seconds since the epoch.
%z / %ZLast status-change time — human-readable / seconds since the epoch.
%w / %WFile 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:

DirectiveSubstitutes
%nFile name.
%iFile-system ID, in hexadecimal.
%t / %TFile-system type — in hexadecimal / in words.
%lMaximum length of a file name.
%sBlock size, for fast transfers.
%SFundamental block size, used for the block counts.
%bTotal data blocks in the file system.
%fFree blocks.
%aFree blocks available to an ordinary principal.
%cTotal inodes (file nodes).
%dFree inodes.

Options #

OptionEffect
-f, --file-systemReport on the file system containing each file, instead of the file.
-L, --dereferenceFollow symbolic links — report on the link's target rather than the link itself.
-t, --tersePrint the information on a single line, as bare values with no labels. Useful for scripts.

Exit status #

CodeMeaning
0Every file was stated successfully.
1A 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.

OptionPath printed
-P, --physicalThe physical path: every symbolic link resolved to its target.
-L, --logicalThe 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 #

CodeMeaning
0The working directory was printed.
1The 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:

OptionResolvesExistence requirement
-f, --canonicalizeEvery link in the path.Every component except the last must exist.
-e, --canonicalize-existingEvery link in the path.Every component must exist.
-m, --canonicalize-missingEvery 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 #

OptionEffect
-n, --no-newlineDo not print the trailing newline. Ignored, with a warning, when more than one file is given.
-z, --zeroEnd each output line with a NUL character instead of a newline.
-q, --quiet / -s, --silentSuppress most error messages. This is the default.
-v, --verboseReport error messages that the quiet default would suppress.

Exit status #

CodeMeaning
0Every named file was resolved and printed.
1A 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:

OptionExistence requirement
(default)Every component except the last must exist.
-e, --canonicalize-existingEvery component must exist — including the final one.
-m, --canonicalize-missingNo component need exist. The path is canonicalised purely as text where it cannot be walked.

How symbolic links are handled #

OptionEffect
-P, --physicalResolve each symbolic link as it is encountered. This is the default.
-L, --logicalResolve .. components before resolving the symbolic links they follow.
-s, --strip, --no-symlinksDo 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 #

OptionEffect
--relative-to=DIRPrint the result as a path relative to DIR instead of as an absolute path.
--relative-base=DIRPrint an absolute path, unless the result lies inside DIR, in which case print it relative to DIR.
-z, --zeroEnd each output line with a NUL character instead of a newline.
-q, --quietDo 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 #

CodeMeaning
0Every path was resolved and printed.
1A 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.

OptionEffect
-a, --multipleTreat 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=SUFFIXRemove SUFFIX from each name. Implies -a, so it applies to every name given.
-z, --zeroEnd 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 #

CodeMeaning
0Success.
1A 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, so dirname prints . — 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 #

OptionEffect
-z, --zeroEnd each output line with a NUL character instead of a newline.

Exit status #

CodeMeaning
0Success.
1A 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.

OptionAdds
-pCheck against a fixed, conservative set of length limits instead of the local file system's, and flag characters that are not broadly portable.
-PReject an empty name, and reject any component that begins with -.
--portabilityApply 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 #

CodeMeaning
0Every name passed every requested check.
1At 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.

OptionOutput syntax
-b, --sh, --bourne-shellBourne-shell-family syntax.
-c, --csh, --c-shellC-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.

OptionEffect
-p, --print-databasePrint the built-in colour database in its editable source form, instead of shell commands.
--print-ls-colorsPrint 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 #

CodeMeaning
0Success.
1A 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

CommandPurpose
catPrint one or more files straight through — and join several into one.
tacPrint a file with its lines in reverse — last line first.
moreShow a file one screen at a time, pausing between screens.
odDump a file's raw bytes in octal, hex, decimal, or character form.

Viewing part of a file

CommandPurpose
headPrint the first lines (or bytes) of a file.
tailPrint the last lines of a file — and, with -f, keep printing as the file grows.
nlPrint a file with its lines numbered.

Cutting and combining

CommandPurpose
cutPull selected columns — byte ranges or delimited fields — out of each line.
pasteJoin files side by side, line for line.
joinJoin two files on a shared field, like a database join.
commCompare two sorted files and report which lines they share.

Splitting a file into files

CommandPurpose
splitBreak a file into equal-sized pieces.
csplitBreak 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 #

OptionEffect
-n, --numberNumber every output line.
-b, --number-nonblankNumber 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.

OptionEffect
-E, --show-endsPrint a $ at the end of each line, so trailing spaces become visible.
-T, --show-tabsPrint tab characters as ^I instead of as whitespace.
-v, --show-nonprintingPrint control and other non-printing characters using ^ and M- notation (leaving newline and tab as they are).
-eShorthand for -vE.
-tShorthand for -vT.
-A, --show-allShorthand for -vET — show ends, tabs, and all non-printing characters at once.

Adjusting spacing #

OptionEffect
-s, --squeeze-blankCollapse runs of blank lines into a single blank line.

Exit status #

CodeMeaning
0Every file was printed.
1A 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.

OptionEffect
-s, --separator=STRINGUse STRING as the separator between records, instead of a newline.
-r, --regexTreat the separator as a regular expression rather than a literal string.
-b, --beforeExpect 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 #

CodeMeaning
0Every file was printed.
1A 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 #

OptionEffect
-n, --lines=NUMPrint the first NUM lines instead of 10.
-c, --bytes=NUMPrint 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:

OptionEffect
-q, --quietNever print the file-name headers.
-v, --verboseAlways print the header, even for a single file.

Other options #

OptionEffect
-z, --zero-terminatedTreat the NUL character as the line delimiter instead of newline.

Exit status #

CodeMeaning
0Every file was printed.
1A 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 #

OptionEffect
-n, --lines=NUMPrint the last NUM lines instead of 10.
-c, --bytes=NUMPrint 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.

OptionEffect
-f, --followKeep the file open and print new data as it is added.
-FFollow 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.
--retryKeep trying to open a file that is not yet accessible.
--pid=PIDWhile following, stop once process PID exits.
-s, --sleep-interval=NWait 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.

OptionEffect
-q, --quietNever print the file-name headers.
-v, --verboseAlways print the header, even for a single file.

Other options #

OptionEffect
-z, --zero-terminatedTreat the NUL character as the line delimiter instead of newline.

Exit status #

CodeMeaning
0Success.
1A 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:

KeyAction
SpaceShow the next screenful.
ReturnShow the next line.
qQuit.
/Search forward for a string.
hShow the built-in help.

more moves forward through a file. It is the simple, always-available pager — enough for reading something through once.

Options #

OptionEffect
-n, --lines=NUMShow NUM lines per screenful instead of filling the terminal. --number=NUM is the same.
-F, --from-line=NUMBegin displaying at line NUM.
-P, --pattern=STRINGSearch for STRING and begin displaying at the first match.
-e, --exit-on-eofExit automatically at the end of the file, rather than waiting.
-s, --squeezeCollapse runs of blank lines into one.
-u, --plainSuppress underlining in the displayed text.
-p, --print-overClear the screen and print the next page, instead of scrolling.
-c, --clean-printRedraw each page in place, cleaning line ends, instead of scrolling.
-d, --silentWhen an unrecognised key is pressed, show a short hint instead of ringing the terminal bell.
-l, --logicalDo not pause when a line contains a form-feed character.
-f, --no-pauseCount logical lines rather than screen lines when deciding where to pause.

Exit status #

CodeMeaning
0The file was displayed.
1A 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 STYLENumbers…
-b aevery line.
-b tonly non-empty lines. This is the default.
-b nno lines.
-b pBREonly lines that match the basic regular expression BRE.
OptionEffect
-b, --body-numbering=STYLEThe numbering rule, as above.
-l, --join-blank-lines=NCount a run of N blank lines as one numbered line.

How the number looks #

OptionEffect
-n, --number-format=FORMATln = left-justified; rn = right-justified; rz = right-justified with leading zeros.
-w, --number-width=NUse N columns for the number.
-s, --number-separator=STRINGPut STRING between the number and the line text.
-v, --starting-line-number=NStart counting from N.
-i, --line-increment=NIncrease 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.

OptionEffect
-h, --header-numbering=STYLENumbering style for header sections.
-f, --footer-numbering=STYLENumbering style for footer sections.
-d, --section-delimiter=CCThe characters that mark a section boundary in the input.
-p, --no-renumberDo 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 #

CodeMeaning
0Success.
1A 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:

OptionA column is…
-b, --bytesa single byte.
-c, --charactersa single character.
-f, --fieldsa 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.

SequenceSelects
3column 3 only.
2,5,9columns 2, 5, and 9.
5-7columns 5 through 7.
3-column 3 to the end of the line.
-4the start of the line through column 4.
1,4-6,9any mixture of the above.
OptionEffect
--complementInvert the selection — keep every column except those named.

Field mode: the delimiter #

In field mode, cut needs to know what separates the fields.

OptionEffect
-d, --delimiter=CHARThe character that separates fields. The default is a tab.
-wSeparate fields on runs of whitespace (spaces and tabs) instead of a single character. Cannot be combined with -d.
-s, --only-delimitedPrint only lines that actually contain the delimiter; drop lines that have no fields to cut.
--output-delimiter=STRINGPut STRING between the kept fields in the output, instead of repeating the input delimiter — handy for converting one delimited format to another.

Other options #

OptionEffect
-z, --zero-terminatedTreat the NUL character as the line delimiter, so a "line" may itself contain newlines.

Exit status #

CodeMeaning
0Success.
1A 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 #

OptionEffect
-s, --serialPaste 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 #

OptionEffect
-d, --delimiters=LISTUse the characters in LIST as separators instead of a tab. When LIST has more than one character, paste cycles through them.
-z, --zero-terminatedTreat 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 #

CodeMeaning
0Success.
1A 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.

OptionEffect
-1 FIELDJoin on FIELD of file 1.
-2 FIELDJoin on FIELD of file 2.
-j FIELDJoin on FIELD of both files — shorthand for -1 FIELD -2 FIELD.
-t CHARUse 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:

OptionEffect
-a FILENUMAlso print unpaired lines from file FILENUM (1 or 2).
-v FILENUMPrint only the unpaired lines from file FILENUM, and suppress the joined output.
-e EMPTYFill in any missing field with the string EMPTY.

Shaping the output #

OptionEffect
-o FORMATBuild each output line from the field list FORMAT, rather than the default layout.
-i, --ignore-caseIgnore letter case when comparing join fields.
--headerTreat the first line of each file as column headers — print them, paired, without trying to match them.
-z, --zero-terminatedTreat the NUL character as the line delimiter.

Exit status #

CodeMeaning
0Success.
1A 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.

OptionEffect
-1Suppress column 1 — hide lines unique to file1.
-2Suppress column 2 — hide lines unique to file2.
-3Suppress 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 #

OptionEffect
--output-delimiter=STRSeparate the columns with STR instead of spaces.
--totalPrint a final summary line with the count in each column.
-z, --zero-terminatedTreat the NUL character as the line delimiter.

Exit status #

CodeMeaning
0Success.
1A 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:

OptionEach piece holds…
-l, --lines=NN lines. This is the default behaviour, with N = 1000.
-b, --bytes=SIZESIZE bytes.
-C, --line-bytes=SIZEup to SIZE bytes, but only whole lines — never a line split across two pieces.
-n, --number=CHUNKSthe 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:

FormEffect
NSplit into N pieces by size.
l/NSplit into N pieces without splitting any line across pieces.
r/NLike l/N, but distribute lines round-robin across the pieces.
K/NWrite only the Kth of N pieces, to standard output.

Naming the pieces #

OptionEffect
prefix (operand)The leading part of each output name. Default x.
-a, --suffix-length=NUse 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=SUFFIXAppend a fixed SUFFIX to every output name — for giving the pieces an extension.

Other options #

OptionEffect
-e, --elide-empty-filesWith -n, do not write out pieces that would be empty.
-t, --separator=SEPUse SEP as the line separator instead of newline.
--verbosePrint a line as each output file is opened.

Exit status #

CodeMeaning
0The file was split.
1A 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.

PatternCut 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/-Nan 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 #

OptionEffect
-f, --prefix=PREFIXUse PREFIX instead of xx at the start of each output name.
-n, --digits=NUse N digits in the numeric part of the name instead of 2.
-b, --suffix-format=FORMATUse a custom printf-style FORMAT for the numeric suffix.

Other options #

OptionEffect
-k, --keep-filesDo not delete the output files if csplit fails partway.
-z, --elide-empty-filesDo not write out pieces that would be empty.
--suppress-matchedDrop the lines that the patterns matched, rather than keeping them in a piece.
-s, --quietDo not print the byte size of each piece.

Exit status #

CodeMeaning
0The file was split.
1A 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 -A to 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).

OptionRenders each unit as
-aNamed characters, ignoring the high-order bit (control characters shown by name, e.g. nul, sp, del).
-bOctal, one byte at a time.
-cPrintable ASCII / UTF-8 characters, with backslash escapes (\n, \t, …) for the rest.
-dUnsigned decimal, 2-byte units.
-DUnsigned decimal, 4-byte units.
-oOctal, 2-byte units.
-OOctal, 4-byte units.
-sSigned decimal, 2-byte units.
-iSigned decimal, 4-byte units.
-lSigned decimal, 8-byte units.
-I, -LSigned decimal, 8-byte units.
-x, -hHexadecimal, 2-byte units.
-X, -HHexadecimal, 4-byte units.
-fFloating point, single precision (32-bit).
-e, -FFloating 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 letterMeaning
aPrintable 7-bit ASCII, named (like -a).
cUTF-8 characters, with octal escapes for undefined bytes (like -c).
dSigned decimal.
uUnsigned decimal.
oOctal.
xHexadecimal.
fFloating 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:

SizeApplies toBytes per unit
1 / Cinteger types1
2 / Sinteger types2
4 / Iinteger types4
8 / Linteger types8
4 / Ffloating point4 (single precision)
8 / Dfloating point8 (double precision)
16 / Lfloating point16 (extended / long double)
2 / Hfloating point2 (IEEE half precision)
2 / Bfloating point2 (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 #

OptionEffect
-A RADIX, --address-radix=RADIXPrint 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:

OptionEffect
-j BYTES, --skip-bytes=BYTESSkip BYTES bytes of input before starting to dump. When several files are given, the skip runs across the concatenated stream.
-N BYTES, --read-bytes=BYTESDump 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.

OptionEffect
-v, --output-duplicatesTurn the collapsing off — print every line, including repeats, with no * markers.

Output width #

OptionEffect
-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 #

OptionEffect
-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 #

CodeMeaning
0The input was dumped successfully.
1A 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

CommandPurpose
sortSort lines — alphabetically, numerically, and many other ways.
shufThe opposite of sorting: put lines into a random order.
tsortTopological sort — order items so that each comes after the things it depends on.

Filtering lines

CommandPurpose
uniqCollapse or report adjacent repeated lines.

Substituting characters

CommandPurpose
trTranslate, squeeze, or delete individual characters.

Whitespace

CommandPurpose
expandConvert tabs into spaces.
unexpandConvert runs of spaces back into tabs.

Reflowing and paginating

CommandPurpose
fmtReflow paragraphs to a target width.
foldHard-wrap long lines at a fixed width.
prPaginate and columnate text for printing.

Indexing and counting

CommandPurpose
ptxProduce a permuted index of the words in a file.
wcCount 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:

OptionCompares lines as…
-n, --numeric-sortnumbers. 9 sorts before 10.
-g, --general-numeric-sortnumbers, including scientific notation. Slower than -n; use it only when -n is not enough.
-h, --human-numeric-sorthuman-readable sizes — 2K before 1M before 3G.
-M, --month-sortmonth names — JAN before FEB.
-V, --version-sortversion numbers — 1.2.10 after 1.2.9.
-R, --random-sorta random order (a shuffle that groups equal lines together).

And these adjust what is compared:

OptionEffect
-f, --ignore-caseTreat lower and upper case as the same.
-d, --dictionary-orderConsider only letters, digits, and blanks.
-i, --ignore-nonprintingIgnore non-printing characters.
-b, --ignore-leading-blanksIgnore blanks at the start of a line (or field).

Sort order options #

OptionEffect
-r, --reverseReverse the result.
-u, --uniqueOutput only the first line of each run of equal lines — sort and de-duplicate in one step.
-s, --stableKeep 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
OptionEffect
-k, --key=KEYDEFSort on the key KEYDEF. May be given more than once; keys are tried in order.
-t, --field-separator=SEPUse SEP to split fields, instead of the whitespace default.

Checking and merging #

OptionEffect
-c, --checkDo not sort — just check whether the input is already sorted, and report the first line that is out of order.
-C, --check=silentCheck, but report only through the exit status.
-m, --mergeTreat the inputs as already-sorted files and merge them, which is faster than a full sort.

Output and resources #

OptionEffect
-o, --output=FILEWrite to FILE instead of standard output. FILE may be one of the inputs.
-z, --zero-terminatedTreat the NUL character as the line delimiter.
--parallel=NUse N threads.
-S, --buffer-size=SIZESet the size of the in-memory sort buffer.
-T, --temporary-directory=DIRPut temporary files in DIR rather than the default location.
--files0-from=FTake the list of input files from F, NUL-separated.
--debugUnderline the part of each line that was actually used as the sort key — invaluable when a -k key is not behaving.

Exit status #

CodeMeaning
0The lines were sorted — or, with -c/-C, the input was already sorted.
1With -c/-C, the input was not sorted.
2An 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:

OptionEffect
-c, --countPrefix each line with the number of times it occurred.
-d, --repeatedPrint only lines that were repeated — one copy of each.
-DPrint all copies of every repeated line, not just one.
-u, --uniquePrint 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" #

OptionEffect
-i, --ignore-caseTreat lines differing only in letter case as the same.
-f, --skip-fields=NIgnore the first N fields when comparing.
-s, --skip-chars=NIgnore the first N characters when comparing.
-w, --check-chars=NCompare at most N characters of each line.

Other options #

OptionEffect
-z, --zero-terminatedTreat the NUL character as the line delimiter.

Exit status #

CodeMeaning
0Success.
1A 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 set1 is replaced by the character at the same position in set2. tr 'abc' 'xyz' turns every a into x, b into y, c into z.
  • Delete (-d) — give one set. Every character in set1 is 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:

NotationMeans
a-zA 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, \\, \NNNEscapes — newline, tab, backslash, an octal byte.

Options #

OptionEffect
-d, --deleteDelete the characters in set1 instead of translating.
-s, --squeeze-repeatsCollapse each run of a repeated character listed in the last set into one.
-c, -C, --complementOperate on the complement of set1 — every character it does not list.
-t, --truncate-set1Before translating, shorten set1 to the length of set2, rather than reusing set2's last character to cover the surplus.

Exit status #

CodeMeaning
0Success.
1A 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 #

OptionEffect
-t, --tabs=NSet tab stops every N columns instead of the default 8.
-t, --tabs=LISTGiven a comma-separated list, place tab stops at those explicit column positions.
-i, --initialConvert 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 #

CodeMeaning
0Success.
1A 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 #

OptionEffect
-t, --tabs=NSet 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, --allConvert all runs of spaces that reach a tab stop, not only the leading indentation.
--first-onlyConvert only the leading run of blanks on each line. Overrides -a.

Exit status #

CodeMeaning
0Success.
1A 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 #

OptionEffect
-w, --width=WIDTHFill lines up to at most WIDTH columns. The default is 75.
-g, --goal=WIDTHAim for WIDTH columns — the width fmt targets, while -w is the hard maximum. Defaults to about 93% of the maximum.

Controlling the reflow #

OptionEffect
-s, --split-onlyOnly split lines that are too long; never join short lines together.
-u, --uniform-spacingPut exactly one space between words and two between sentences.
-c, --crown-marginPreserve the indentation of a paragraph's first two lines; indent the rest to match the second.
-t, --tagged-paragraphLike -c, but the first line must be indented differently from the second, or the two are treated as separate paragraphs.
-q, --quickBreak lines faster, accepting a more ragged right edge.

Selecting which lines to format #

OptionEffect
-p, --prefix=PREFIXReformat only lines that begin with PREFIX; reattach PREFIX afterwards. Useful for reflowing comment blocks in source code.
-m, --preserve-headersDetect and preserve mail-style header lines rather than reflowing them.
--tab-width=NTreat a tab as N columns when measuring line length. Tabs are kept in the output; this affects measurement only.

Exit status #

CodeMeaning
0Success.
1A 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 #

OptionEffect
-w, --width=WIDTHWrap at WIDTH columns instead of 80.
-s, --spacesBreak at a space within the width where one exists, rather than mid-word — a gentler wrap.
-b, --bytesCount bytes rather than display columns. Control characters such as tab and newline then count as ordinary bytes.
-c, --charactersCount character positions rather than display columns.

Exit status #

CodeMeaning
0Success.
1A 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.

OptionEffect
-COLUMNProduce COLUMN columns per page (e.g. -3 for three). Text fills down the first column, then the next.
-a, --acrossFill the columns across — line 1 in column 1, line 2 in column 2, and so on — rather than down.
-m, --mergePrint several files side by side, one per column.
-w, --width=WIDTHSet the page width, for multi-column output.

The header and the page #

OptionEffect
-h, --header=TEXTUse TEXT in the header instead of the file name.
-t, --omit-headerPrint neither the header nor the trailer.
-T, --omit-paginationDrop the header, the trailer, and all pagination — just the text.
-l, --length=LINESSet the page length to LINES instead of 66.
-o, --indent=NIndent every line by N spaces.
-D, --date-format=FORMATFormat the header's date with FORMAT.
-F, -f, --form-feedSeparate pages with a form-feed character rather than blank lines.

Selecting and numbering #

OptionEffect
--pages=FIRST[:LAST]Print only pages FIRST through LAST.
-n, --number-lines[=SEP[WIDTH]]Number every line.
-N, --first-line-number=NBegin line numbering at N.
-d, --double-spaceDouble-space the output.

Other options #

OptionEffect
-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-linesMerge full lines, turning off width truncation.
-r, --no-file-warningsDo not warn when a file cannot be opened.

Exit status #

CodeMeaning
0Success.
1A 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 #

OptionEffect
-W, --word-regexp=REGEXPTreat anything matching REGEXP as a keyword.
-b, --break-file=FILETake the set of word-break characters from FILE.
-i, --ignore-file=FILERead a list of words to exclude from FILE.
-o, --only-file=FILEIndex only the words listed in FILE.
-f, --ignore-caseFold case together when sorting.

Output format #

OptionEffect
-w, --width=NSet the output width, in columns.
-g, --gap-size=NSet the gap, in columns, between the output fields.
-A, --auto-referenceGenerate a reference (file name and line number) for each entry automatically.
-r, --referencesTreat the first field of each input line as a reference for that line.
-R, --right-side-refsPut references on the right, and exclude them from the width count.
-F, --flag-truncation=STRINGUse STRING to mark where a line was truncated.
-O, --format=roffEmit the index as roff typesetting directives.
-T, --format=texEmit the index as TeX typesetting directives.
-M, --macro-name=NAMEUse NAME as the macro name in roff/TeX output.
-G, --traditionalBehave like the older System V ptx, without the extended features.

Exit status #

CodeMeaning
0Success.
1A 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 #

CodeMeaning
0A valid order was produced.
1The 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:

OptionCounts
-l, --linesThe number of lines.
-w, --wordsThe number of words — runs of non-whitespace.
-c, --bytesThe number of bytes.
-m, --charsThe number of characters. This differs from -c for multi-byte text, where one character is several bytes.
-L, --max-line-lengthThe length of the longest line.
$ wc -l report.txt        # just the line count
214 report.txt

Other options #

OptionEffect
--files0-from=FTake the list of files to count from F, as NUL-terminated names. - reads the list from standard input.
--total=WHENControl 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 #

CodeMeaning
0Every file was counted.
1A 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:

OptionInput is…
(a file, or stdin)the lines of the file.
-e, --echothe command-line arguments, each treated as one line.
-i, --input-range=LO-HIthe 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 #

OptionEffect
-n, --head-count=COUNTOutput at most COUNT lines — a random sample rather than the whole shuffle.
-r, --repeatAllow lines to be repeated, so output can be longer than the input. Pair with -n to set how many.
-o, --output=FILEWrite to FILE instead of standard output.
-z, --zero-terminatedTreat 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:

OptionEffect
--random-seed=STRINGSeed the shuffle with STRING. The same seed gives the same shuffle.
--random-source=FILETake the random bytes from FILE. The same file gives the same shuffle. Cannot be combined with --random-seed.

Exit status #

CodeMeaning
0Success.
1A 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

CommandPurpose
echoPrint its arguments as a line of text.
printfPrint text from a format string, with precise control over layout.
yesPrint a line over and over, without stopping.
seqPrint a sequence of numbers.
teeCopy standard input to standard output and to each named file.

The environment

CommandPurpose
envRun a command with a modified environment — or print the current one.
printenvPrint environment variables.

Numbers

CommandPurpose
numfmtConvert numbers to and from human-readable forms (1.5G, 1000000).
factorPrint the prime factors of a number.

Evaluating

CommandPurpose
exprEvaluate an arithmetic, string, or comparison expression.
testEvaluate a condition — a file check or a comparison — as a true/false result.

Exit status

CommandPurpose
true and falseDo 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 #

OptionEffect
-nDo not print the trailing newline.
-eInterpret backslash escape sequences in the arguments (see below).
-EDo not interpret escape sequences. This is the default.

Escape sequences #

With -e, echo recognises these backslash sequences and prints the character they stand for:

SequenceCharacter
\\A literal backslash.
\nNewline.
\tHorizontal tab.
\rCarriage return.
\bBackspace.
\fForm feed.
\vVertical tab.
\aAlert (the terminal bell).
\eEscape.
\0NNNThe byte with octal value NNN.
\xHHThe byte with hexadecimal value HH.
\cStop — 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 #

CodeMeaning
0The output was written.
1The 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:

SequenceCharacter
\\Backslash.
\n \t \rNewline, tab, carriage return.
\b \f \v \aBackspace, form feed, vertical tab, alert.
\eEscape.
\NNNThe byte with octal value NNN.
\xHHThe byte with hexadecimal value HH.
\uHHHH, \UHHHHHHHHA Unicode character by hexadecimal code point.
%%A literal %.

Substitution fields #

A field begins with % and is replaced by the next argument, formatted accordingly.

FieldFormats the argument as…
%sa string.
%ba string, with backslash escapes in it interpreted.
%qa string, quoted so it is safe to reuse as shell input.
%ca single character.
%d, %ia signed integer.
%uan unsigned integer.
%x, %Xan unsigned integer in hexadecimal (lower- or upper-case).
%oan unsigned integer in octal.
%f, %Fa decimal floating-point number.
%e, %Ea number in scientific notation.
%g, %Gwhichever 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 #

CodeMeaning
0Success.
1A 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 #

CodeMeaning
0The output was closed normally.
1A 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 to last.
  • seq first last — count from first up to last.
  • seq first increment last — count from first to last, stepping by increment.

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 #

OptionEffect
-s, --separator=STRINGSeparate the numbers with STRING instead of a newline.
-t, --terminator=STRINGEnd the output with STRING instead of a newline.
-w, --equal-widthPad the numbers with leading zeros so they all have the same width.
-f, --format=FORMATFormat 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 #

CodeMeaning
0The sequence was printed.
1An 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 #

OptionEffect
-i, --ignore-environmentStart from an empty environment, not the inherited one — so the command sees only what you assign. A bare - argument means the same.
-u, --unset=NAMERemove NAME from the environment.
--file=FILERead 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 #

OptionEffect
-C, --chdir=DIRChange to DIR before running the command.
--argv0=NAMERun the command but present NAME as its zeroth argument.
-S, --split-string=SSplit 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:

OptionEffect
--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-handlingList the signal-handling changes the other options requested.

Other options #

OptionEffect
-0, --nullWhen printing the environment, end each line with a NUL character instead of a newline. Not valid together with a command.
-v, --debugPrint 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:

CodeMeaning
(command's own)The command ran; this is its exit status.
125env itself failed — for example, a bad option.
126The command was found but could not be run.
127The 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 #

OptionEffect
-0, --nullEnd 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 #

CodeMeaning
0Every named variable was found and printed.
1A named variable was not set.
2A 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 #

OptionEffect
--from=UNITParse input numbers that carry a UNIT suffix, scaling them up to plain digits.
--to=UNITScale output numbers down and add a UNIT suffix.

A UNIT is one of:

UnitSuffixes mean
noneNo suffixes — a suffix is an error. The default.
siPowers of 1000: 1K = 1000, 1M = 1000000.
iecPowers of 1024: 1K = 1024, 1M = 1048576.
iec-iPowers of 1024 with a two-letter suffix: 1Ki = 1024, 1Mi = 1048576.
autoAccept 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:

OptionEffect
--field=FIELDSConvert only the numbers in these fields. FIELDS uses the same range syntax as cut — 3, 2-5, 2-.
-d, --delimiter=XUse X to separate fields instead of whitespace.
--header[=N]Pass the first N lines through unconverted, as a header.

Shaping the output #

OptionEffect
--format=FORMATFormat the number with a printf-style floating-point FORMAT, controlling width, padding, and precision.
--padding=NPad the output to N columns — positive right-aligns, negative left-aligns.
--groupingGroup digits according to the locale (for example, 1,000,000).
--round=METHODChoose the rounding method used when scaling.
--suffix=SUFFIXAppend SUFFIX to each result, and accept it on input.
--from-unit=N / --to-unit=NSet the unit size the input or output is counted in.

Other options #

OptionEffect
--invalid=MODEChoose what to do with input that is not a valid number: abort, fail, warn, or ignore.
-z, --zero-terminatedTreat the NUL character as the line delimiter.
--debugPrint warnings about questionable input.

Exit status #

CodeMeaning
0Every number was converted.
2An 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 #

OperatorResult
ARG1 + ARG2Sum.
ARG1 - ARG2Difference.
ARG1 * ARG2Product.
ARG1 / ARG2Integer quotient.
ARG1 % ARG2Remainder.

Comparison #

A comparison yields 1 for true and 0 for false. It is arithmetic when both sides are numbers, and lexical otherwise.

OperatorTrue when…
ARG1 = ARG2the two are equal.
ARG1 != ARG2they are unequal.
ARG1 < ARG2ARG1 is less.
ARG1 <= ARG2ARG1 is less or equal.
ARG1 > ARG2ARG1 is greater.
ARG1 >= ARG2ARG1 is greater or equal.

Logic #

OperatorResult
ARG1 | ARG2ARG1 if it is neither null nor 0; otherwise ARG2.
ARG1 & ARG2ARG1 if neither argument is null or 0; otherwise 0.

String operations #

FormResult
length STRINGThe number of characters in STRING.
substr STRING POS LENGTHLENGTH characters of STRING, counting POS from 1.
index STRING CHARSThe position of the first of any of CHARS in STRING, or 0.
STRING : REGEXPMatch REGEXP anchored at the start of STRING. Returns the matched length, or the text captured by \(…\).
match STRING REGEXPThe 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:

CodeMeaning
0The result was neither null nor 0.
1The result was null or 0.
2The expression was syntactically invalid.
3An 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 #

OptionEffect
-h, --exponentsWrite a repeated factor in exponent form — p^e — instead of repeating it.
$ factor -h 360
360: 2^3 3^2 5

Exit status #

CodeMeaning
0Every number was factored.
1An 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 #

TestTrue when the file…
-e FILEexists.
-f FILEexists and is a regular file.
-d FILEexists and is a directory.
-L FILE, -h FILEexists and is a symbolic link.
-p FILEexists and is a named pipe (FIFO).
-S FILEexists and is a socket.
-b FILEexists and is a block device.
-c FILEexists and is a character device.
-s FILEexists and is not empty.

Access #

These three tests ask whether you may actually use the file:

TestTrue when…
-r FILEyou may read the file.
-w FILEyou may write the file.
-x FILEyou 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 #

TestTrue when…
FILE1 -nt FILE2FILE1 is newer than FILE2.
FILE1 -ot FILE2FILE1 is older than FILE2.
FILE1 -ef FILE2both name the same file (same device and inode).

Other file tests #

TestTrue when the file…
-t FDfile descriptor FD is open on a terminal.
-N FILEhas been modified since it was last read.
-O FILE, -G FILEcarries an owner-id / group-id field matching the caller's.
-g FILE, -u FILE, -k FILEhas 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 #

TestTrue when…
-n STRINGSTRING is not empty. A bare STRING means the same.
-z STRINGSTRING is empty.
STRING1 = STRING2the strings are equal.
STRING1 != STRING2the strings are unequal.
STRING1 < STRING2STRING1 sorts before STRING2.
STRING1 > STRING2STRING1 sorts after STRING2.

Integer comparisons #

TestTrue when…
A -eq BA equals B.
A -ne BA does not equal B.
A -lt BA is less than B.
A -le BA is less than or equal to B.
A -gt BA is greater than B.
A -ge BA is greater than or equal to B.

Combining conditions #

FormResult
! EXPRESSIONThe negation.
( EXPRESSION )Grouping — escape the parentheses so the shell does not take them.
EXPR1 -a EXPR2True when both are true.
EXPR1 -o EXPR2True 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 #

CodeMeaning
0The expression was true.
1The expression was false.
2The 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 true is the standard way to write a loop that runs until something inside it breaks out.
  • A deliberate placeholder. Setting a configurable command to true makes that step do nothing and "succeed"; false makes 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, true and false provide it.

They are the simplest commands there are — and the overview's note on exit status is the whole idea behind them.

Exit status #

CodeMeaning
0Returned by true, always.
1Returned 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:

ModeBehaviour
warnWarn on a write error to any output, including a broken pipe, and continue with the remaining outputs.
warn-nopipeWarn 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.
exitExit on a write error to any output, including a broken pipe.
exit-nopipeExit on a write error that is not a pipe error. Broken-pipe errors are ignored.

Options #

OptionEffect
-a, --appendAppend to each named file rather than truncating it.
-i, --ignore-interruptsIgnore the interrupt signal.
-pIgnore 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 #

CodeMeaning
0The input was copied to standard output and every named file without error.
1A 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

CommandPurpose
checksum commandsmd5sum, sha1sum, sha256sum, and the rest — one command per hash algorithm.
cksumThe general checksum tool — any of the algorithms, selected by an option.
sumA small, legacy block-checksum.

Encoding

CommandPurpose
base32 and base64Encode binary data as text using the base32 or base64 alphabet, and decode it back.
basencThe 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:

CommandAlgorithmDigest size
md5sumMD5128-bit
sha1sumSHA-1160-bit
sha224sumSHA-224224-bit
sha256sumSHA-256256-bit
sha384sumSHA-384384-bit
sha512sumSHA-512512-bit
b2sumBLAKE2bup 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.

OptionEffect
-c, --checkRead checksums from the given files and verify them.
--ignore-missingIn check mode, do not fail over files that are listed but absent.
--quietIn check mode, print nothing for files that pass — only failures.
--statusIn check mode, print nothing at all; report only through the exit status.
-w, --warnWarn about improperly formatted lines in the checksum file.
--strictIn check mode, fail if any checksum line is malformed.

Output format #

OptionEffect
--tagProduce a tagged line — SHA256 (file) = hash — that records which algorithm was used.
--untaggedProduce the plain hash file line. This is the default.
-z, --zeroEnd each output line with a NUL character instead of a newline.
-b, --binaryNote the file as read in binary mode.
-t, --textNote 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 #

CodeMeaning
0The checksums were computed, or — in check mode — every file verified.
1In check mode, a file failed verification.
2An 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 #

OptionEffect
-a, --algorithm=NAMEUse the named algorithm.

NAME may be:

NameAlgorithm
crcThe default CRC.
crc32bA CRC-32 variant.
md5MD5 — as md5sum.
sha1SHA-1 — as sha1sum.
sha224, sha256, sha384, sha512The SHA-2 family — as the matching sha*sum.
sha3SHA-3.
blake2bBLAKE2b — as b2sum.
sm3The SM3 hash.
sysv, bsdThe 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:

OptionEffect
-c, --checkRead checksums and verify the files against them.
--ignore-missingDo not fail over listed files that are absent.
--quietPrint nothing for files that pass.
--statusPrint nothing; report only through the exit status.
-w, --warnWarn about malformed checksum lines.
--strictFail on a malformed checksum line.

Output format #

OptionEffect
--tagProduce a tagged ALGORITHM (file) = hash line. The default for most algorithms.
--untaggedProduce a plain hash file line.
--rawOutput the raw binary digest, with nothing else.
--base64Print the digest in base64 rather than hexadecimal.
-l, --length=BITSFor an algorithm that supports it, produce a shorter digest.
-z, --zeroEnd each output line with a NUL character.

Exit status #

CodeMeaning
0Success — or, in check mode, every file verified.
1In check mode, a file failed verification.
2An 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:

OptionAlgorithm
-rThe BSD checksum, counted in 1 KiB blocks. This is the default.
-s, --sysvThe 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 #

CodeMeaning
0The checksum was computed.
1A 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.

OptionEffect
-d, --decodeDecode instead of encode.
-i, --ignore-garbageWhen decoding, skip any characters that are not part of the alphabet, rather than failing on them.
-w, --wrap=COLSWhen 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 #

CodeMeaning
0Success.
1A 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 #

OptionEncoding
--base64Standard base64 — the same as the base64 command.
--base64urlBase64 with a file- and URL-safe alphabet.
--base32Standard base32 — the same as the base32 command.
--base32hexBase32 with the extended-hex alphabet.
--base16Hexadecimal.
--base2msbfA bit string, most-significant bit first.
--base2lsbfA bit string, least-significant bit first.
--z85A compact, ASCII85-style encoding. When encoding, the input length must be a multiple of 4; when decoding, a multiple of 5.
--base58Base58 — an alphabet chosen so its characters are not visually confusable.

Options #

By default basenc encodes. These options apply to whichever encoding you chose:

OptionEffect
-d, --decodeDecode instead of encode.
-i, --ignore-garbageWhen decoding, skip characters that are not part of the alphabet.
-w, --wrap=COLSWhen 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 #

CodeMeaning
0Success.
1No 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

CommandPurpose
datePrint — or set — the system date and time.
sleepPause for a given length of time.
uptimeShow how long the system has been running.

Running a command under a constraint

CommandPurpose
timeoutRun a command, and kill it if it runs too long.
nohupRun a command so it survives the terminal closing.
niceRun a command at an adjusted scheduling priority.
stdbufRun a command with altered stream buffering.
chrootRun a command with a different directory as its root.

Signalling processes

CommandPurpose
killSend a signal to a process.

System identity and capacity

CommandPurpose
unamePrint system information — the OS, the kernel, the machine.
archPrint the machine's hardware architecture.
hostnameDisplay — or set — the system's host name.
hostidPrint the host's numeric identifier.
nprocPrint how many processor cores are available.

Who you are

CommandPurpose
whoamiPrint the name of the user a process is running as.
idPrint the user and group identity of a process, or of a named user.
groupsPrint the groups a user belongs to.
lognamePrint 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

CommandPurpose
syncFlush cached writes out to persistent storage.
ttyPrint the name of the terminal on standard input.
sttyDisplay 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:

SequenceIs replaced by
%Y %m %dYear, month, day.
%H %M %SHour, minute, second.
%FThe full date — same as %Y-%m-%d.
%TThe time — same as %H:%M:%S.
%A %aWeekday name, full and abbreviated.
%B %bMonth name, full and abbreviated.
%jDay of the year.
%sSeconds since 1970-01-01 UTC.
%z %ZNumeric and named time zone.
%%A literal %.

The full set is long; date --help lists every sequence with an example.

Displaying a different moment #

OptionEffect
-d, --date=STRINGShow the time described by STRING — "yesterday", "next Friday", "@1615432800" — instead of now.
-r, --reference=FILEShow FILE's last modification time.
-f, --file=DATEFILELike -d, but once for each line of DATEFILE.
-u, --universalWork in Coordinated Universal Time (UTC) rather than the local zone.

Standard formats #

OptionOutput
-I, --iso-8601[=FMT]ISO 8601 — FMT is date, hours, minutes, seconds, or ns.
-R, --rfc-emailThe format used in email headers.
--rfc-3339=FMTRFC 3339 — FMT is date, seconds, or ns.

Setting the clock #

OptionEffect
-s, --set=STRINGSet 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 #

CodeMeaning
0The date was printed or set.
1An 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:

SuffixUnit
sSeconds. This is the default if no suffix is given.
mMinutes.
hHours.
dDays.

Given more than one argument, sleep waits for the sum of them — sleep 1m 30s pauses for ninety seconds.

Exit status #

CodeMeaning
0The full time elapsed.
1A 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.

OptionEffect
-s, --signal=SIGNALSend SIGNAL instead of TERM when the limit is reached.
-k, --kill-after=DURATIONIf the command is still running DURATION after the first signal, send it a KILL signal — which cannot be ignored.
--preserve-statusExit with the command's own status even when it timed out.
--foregroundAllow the command to keep reading from the terminal; do not time out the command's own children.
-v, --verboseReport 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:

CodeMeaning
(command's own)The command finished within the limit.
124The command timed out.
125timeout itself failed.
126The command was found but could not be run.
127The command was not found.
137The 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.out in the current directory. If that file cannot be opened there, nohup falls back to nohup.out in 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.

OptionEffect
--sddl=SDDLCreate 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:

CodeMeaning
(command's own)The command ran; this is its exit status.
125nohup 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.

OptionEffect
-n, --adjustment=NAdd 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:

CodeMeaning
(command's own)The command ran; this is its exit status.
0No command was given; the niceness was printed.
125nice itself failed — for example, a bad adjustment value.
126The command was found but could not be run.
127The 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:

SignalMeaning
TERMTerminate. The default — a request to stop cleanly.
KILLStop immediately. Cannot be caught or ignored, so it cannot be refused — but the process gets no chance to clean up. The last resort.
HUPHang up. Conventionally tells a long-running service to reload its configuration.
INTInterrupt — the signal a terminal sends on Ctrl-C.
STOP / CONTSuspend the process / resume a suspended one.
OptionEffect
-s, --signal=SIGNALSend SIGNAL instead of TERM.
-SIGNALA shorthand — -KILL or -9 both send KILL.
-l, --listList the signal names.
-L, --tableList 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 #

CodeMeaning
0Every signal was sent.
1A 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 #

OptionPrints
-sThe kernel name.
-oThe operating-system name — Peios.
-nThe node name — the name this system is known by on the network.
-rThe kernel release.
-vThe kernel version.
-mThe machine's hardware name (its architecture).
-pThe processor type.
-iThe hardware platform.
-aEverything — 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 #

CodeMeaning
0The requested information was printed.
1The 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 #

CodeMeaning
0The architecture was printed.
1The 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:

OptionPrints
-f, --fqdnThe fully qualified domain name — the full host.domain form. This is the default.
-s, --shortThe short host name only — the part before the first dot.
-d, --domainThe DNS domain part only.
-i, --ip-addressThe 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 #

CodeMeaning
0The host name was displayed or set.
1A 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 #

CodeMeaning
0The 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 #

OptionEffect
--allPrint the number of cores the whole system has, ignoring any restriction on the current process.
--ignore=NSubtract 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 #

CodeMeaning
0The count was printed.
1An 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 #

OptionEffect
-p, --prettyShow just the uptime, in a readable phrase — up 6 days, 3 hours, 21 minutes.
-s, --sinceShow the date and time the system started, rather than how long ago that was.

Exit status #

CodeMeaning
0The uptime was printed.
1The 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:

OptionEffect
file...Flush only the given files, rather than everything.
-d, --dataFor the given files, flush only the file data, not metadata that does not need it.
-f, --file-systemFlush the entire file system that contains each given file.

Exit status #

CodeMeaning
0The flush completed.
1A 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 #

OptionEffect
-s, --silentPrint nothing; report the answer only through the exit status.

Exit status #

CodeMeaning
0Standard input is a terminal.
1Standard input is not a terminal.
2A 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 #

OptionEffect
-a, --allPrint every setting, in a readable layout.
-g, --savePrint 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:

SettingEffect
echo / -echoShow / hide typed characters — -echo is how a password prompt hides input.
rows N / cols NTell the terminal its size.
A numberSet the connection speed (baud rate).

stty has a large catalog of settings; stty -a shows them all with their current values.

Choosing the terminal #

OptionEffect
-F, --file=DEVICEOperate on DEVICE instead of the terminal on standard input.

Exit status #

CodeMeaning
0The settings were printed or changed.
1A 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 #

OptionStream
-i, --input=MODEStandard input.
-o, --output=MODEStandard output.
-e, --error=MODEStandard error.

MODE is one of:

MODEBuffering
0Unbuffered — every write goes out immediately.
LLine-buffered — output is flushed at the end of each line. Not valid for input.
a sizeFully 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:

CodeMeaning
(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 #

OptionEffect
--skip-chdirDo 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:

CodeMeaning
125chroot itself failed — for example, newroot is not a directory.
126The command was found but could not be run.
127The 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 #

CodeMeaning
0The name was printed.
1The 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 #

OptionEffect
-u, --userPrint only the effective user ID.
-g, --groupPrint only the effective group ID.
-G, --groupsPrint only the group IDs, space-separated.
-n, --nameWith -u, -g or -G, print names instead of numbers.
-r, --realWith -u, -g or -G, print the real ID rather than the effective one.
-z, --zeroSeparate entries with NUL rather than whitespace. Not allowed in the default format.
-Z, --contextPrint only the security context.
-pA readable, multi-line form.
-PPrint as a password-file entry.
-aIgnored; accepted for compatibility.
-ANot 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 -G reads 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 any Administrators-style aliases.
  • id -G jack looks 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 #

CodeMeaning
0The identity was printed.
1A 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, Batch and Service are 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 in Interactive at 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 #

CodeMeaning
0The groups were printed.
1A 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 #

CodeMeaning
0The login name was printed.
1No 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.

FlagTarget
--selfYour own token. The default.
--realYour primary token specifically, rather than the effective one — relevant when your thread is impersonating.
--pid PIDThe primary token of process PID.
--tid TIDThe impersonation token of thread TID (used with --pid).
--peer SOCK_FDThe 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.

FlagEffect
--shortA one-line summary.
--allEvery query class — the fullest dump.

Field accessors #

Each of these prints one part of a token, for when you want just that piece:

SubcommandPrints
userThe user SID — who the token is.
ownerThe default owner SID.
groupThe primary group SID.
groupsThe group list.
privsThe privileges, with their enabled state.
capsThe capabilities.
claimsThe user and device claims.
integrityThe integrity level.
logonThe logon type and logon SID.
sourceWhat minted the token.
originThe originating session for a derived token.
statsToken statistics — IDs, timestamps, the modification counter.
default-daclThe 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:

FormChanges
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 SDDLReplace the token's default DACL. Also --owner-idx / --group-idx.
adjust session IDReplace the token's session id.

restrict #

token restrict produces a restricted token — a more limited variant of a token.

FlagEffect
--drop-privs MASK|NAMESPrivileges 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 #

SubcommandEffect
impersonateBegin impersonating the target token on the calling thread. With a trailing -- command …, run that command under the impersonating token.
revertDrop any active impersonation on the calling thread.

See Impersonation for the model these drive.

Creating tokens #

SubcommandEffect
create SPECCreate a token from a binary token-spec (SPEC is a file, or - for standard input).
install SPECCreate 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 #

FlagEffect
--rawRender SIDs in raw S-1-… form only.
--labelRender SIDs as their labels where known, falling back to raw.
--jsonEmit 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 #

CodeMeaning
0The operation succeeded.
1A usage error.
non-zeroThe 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
FlagMeaning
--logon-type TYPEThe kind of logon: interactive, network, batch, service, network-cleartext, or new-credentials.
--auth-package STRThe name of the authentication package that vouched for the logon.
--user-sid SIDThe 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.

Note

The tool builds the kernel's binary session spec from these fields for you. The underlying wire format is unchanged; only the command-line surface is typed.

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
FlagMeaning
--pid PIDThe process to act on.
--mitigations MASKThe 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 #

FlagEffect
--jsonEmit JSON instead of human-readable output. Accepted by every subcommand.

Exit status #

CodeMeaning
0The operation succeeded.
1A 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 TARGET pair. Supply both, or use --source / --target.
  • A lone target is valid only for -o remount and 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.

VerbHow to request itWhat it does
New mountmount [-t T] SRC TGTAttach a fresh instance of the filesystem at SRC onto TGT.
Bind-B / --bind, or -o bindMake an existing subtree visible at a second location.
Recursive bind-R / --rbind, or -o rbindBind a subtree together with every mount underneath it.
Move-M / --move, or -o moveRelocate an existing mount to a new mount point.
Move beneath--beneath SRC TGTAttach SRC beneath the mount currently at TGT (the kernel enforces several constraints; violations surface as exit 32).
Remount-o remount[,...] TGTChange the options of an existing mount (see Remounting).
Propagation--make-* / --make-r*, or the -o tokens belowChange how mount/unmount events propagate across a subtree.
Listmount / mount -lPrint 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 tokenRecursive flagRecursive -o tokenMeaning
--make-sharedshared--make-rsharedrsharedEvents propagate to and from peer mounts.
--make-slaveslave--make-rslaverslaveEvents propagate in from the master but not back out.
--make-privateprivate--make-rprivaterprivateNo propagation either way.
--make-unbindableunbindable--make-runbindablerunbindablePrivate, 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 #

FormResolution
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 fileUsed 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 fileAttached 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).

OptionEffect
ro / rwRead-only / read-write.
suid / nosuidHonour / ignore set-user-ID bits.
dev / nodevAllow / disallow device nodes.
exec / noexecAllow / disallow execution.
atime / noatimeUpdate / never update access times.
relatime / norelatimeRelative-atime updates on or off.
strictatime / nostrictatimeStrict-atime updates on or off.
diratime / nodiratimeDirectory access-time updates on or off.
nosymfollowDo 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 #

OptionEffect
sync / asyncSynchronous / asynchronous writes.
dirsyncSynchronous directory updates.
lazytime / nolazytimeLazy on-disk timestamp updates on or off.
iversion / noiversionInode version counting on or off.
silent / loudSuppress or emit certain kernel messages.
mand / nomandObsolete (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=:

mount -t stratafs none /bin -o 'strata=/lcl/bin+create:/usr/bin+ro'

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 #

OptionEffect
X-mount.mkdir[=mode]Create the target directory if missing (default mode 0755; the alias of -m).
X-mount.subdir=DIRAttach 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.noloopSuppress the implicit loop device for a regular-file source.
X-mount.auto-fstypes=LISTConstrain 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:

OptionPolicy class
-o policy=deny-missingfacs_deny_missing — a file with no SD is unreachable.
-o policy=synth-ephemeralfacs_synthesize_ephemeral — a missing SD is synthesised in memory only.
-o policy=synth-persistfacs_synthesize_persistent — a missing SD is synthesised and written back.
--synth-sddl SDDLProvide 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 #

FlagEffect
-t, --types TYPEFilesystem type, or auto.
-o, --options LISTMount options (above); repeatable.
-r, --ro, --read-onlyMount read-only (-o ro).
-w, --rw, --read-writeMount read-write and forbid the automatic read-only fallback on a write-protected device (-o rw).
--source SRC / --target DIRName the operands explicitly.
--target-prefix DIRPrepend DIR/ to the target.
-B, --bind / -R, --rbind / -M, --move / --beneathThe structural verbs.
--make-*, --make-r*Propagation changes.
--exclusiveForce 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 UUIDSelect the source by filesystem label / UUID.
-c, --no-canonicalizeDo not canonicalise paths.
-f, --fakeDry run: parse, resolve and plan everything but skip the mount syscalls and the policy step.
-v, --verboseNarrate resolved values and each syscall; drain the kernel fs_context log. Repeatable but -vv is the same as -v.
-l, --show-labelsIn list mode, append each filesystem's label.
--onlyonceSkip the mount if it is already present (a driver-aware check against live mount state, not a naive string match).
-N, --namespace NSOperate 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-onlyDo not invoke a mount.<type> helper.
-n, --no-mtabAccepted and ignored (Peios has no mtab).
--synth-sddl SDDLKACS synth-policy template SD (above).
-h, --help / -V, --versionStandard.

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 #

CodeMeaning
0Success.
1Incorrect 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".
2System error (out of memory, no free loop device, cannot fork).
4Internal error (an invariant failure).
8Interrupted (SIGINT), after signal-safe cleanup.
32Mount failure — a syscall in the flow failed, a label/UUID had no match, a probe was undetermined, or a --beneath/move/--exclusive kernel refusal.
126An 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, umount refuses with an error naming the candidates — resolve it by naming a specific mount point, or use -A to 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 / --recursive unmounts the target and everything mounted underneath it, including any over-mount stack at a single point, ordered deepest-first.
  • -A / --all-targets unmounts 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 -R together 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 #

FlagEffect
-l, --lazyDetach the filesystem now and clean up references later (MNT_DETACH).
-f, --forceForce the unmount (MNT_FORCE), e.g. for an unreachable server.
-R, --recursiveUnmount the target and everything under it (see above).
-A, --all-targetsUnmount every mount point of the given source (see above).
-d, --detach-loopAfter 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-onlyIf the unmount fails, remount the filesystem read-only instead (via mount_setattr). Mutually exclusive with -R.
-g, --gracefulExit 0 when the target is absent or not mounted (rather than failing). Applied unconditionally.
-q, --quietSuppress "not mounted" messages.
-c, --no-canonicalizeDo not canonicalise paths; selects UMOUNT_NOFOLLOW. Applied unconditionally.
-v, --verboseSay what is being unmounted. Repeatable.
-N, --namespace NSEnter 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-mtabAccepted and ignored (no mtab on Peios).
-i, --internal-onlyDo not invoke a umount.<type> helper (none ship).
--fakeDry run: resolve operands and report, but skip the unmount syscalls.
-h, --help / -V, --versionStandard.

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 #

CodeMeaning
0Success (or a -g no-op on an absent target).
1Incorrect invocation or a permission denial — including -R with -r, a missing operand, an ambiguous source, an embedded NUL, or an authorisation failure.
2System error (e.g. cannot read the mount table).
8Interrupted (SIGINT).
32Unmount failed, or the target is not mounted (unless -g).
64Several source/target arguments were given and at least one succeeded while another failed.
126An 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/block unpatched, 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 — OWNER and MODE, read the same way ls -l reads them.
  • The /dev/disk/by-* symlink farm — ID-LINK. Populated by the device manager once it has run; there is deliberately no /run/udev/data parser, 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.

FlagMode
(default)Indented tree.
-l, --listFlat list — the same columns, no tree glyphs.
-J, --jsonJSON. Flag columns render as bare booleans and MOUNTPOINTS as an array; children nest under a children key.
-P, --pairsKEY="value" pairs, one device per line.
-r, --rawRaw, 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):

FlagColumn set
-o, --output LISTExactly the comma-separated columns named (case-insensitive; an unknown name is a usage error).
-O, --output-allEvery available column.
-f, --fsFilesystem view: NAME, FSTYPE, FSVER, LABEL, UUID, MOUNTPOINTS.
-m, --permsPermissions 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 #

FlagEffect
-a, --allInclude empty (zero-size) devices, which are hidden by default.
-d, --nodepsDo not print a device's holders or slaves (drop the children).
-I, --include LISTShow only devices with these major numbers (comma-separated).
-e, --exclude LISTExclude devices by major number. The default is 1 (RAM disks); an explicit -e replaces that default, and -a clears exclusions entirely.
-s, --inversePrint dependencies in inverse order (holders above the devices they depend on).
-M, --mergeCollapse a subtree shared by several parents (a multipath device) to a single occurrence.
-E, --dedup COLUMNDrop rows whose COLUMN value duplicates an earlier one.
-x, --sort COLUMNSort siblings by COLUMN (numeric columns sort by value, not text).

Formatting #

FlagEffect
-b, --bytesPrint SIZE as an exact byte count instead of a human-readable value.
-p, --pathsPrint full /dev paths in the NAME column.
-n, --noheadingsOmit the header row.
-i, --asciiDraw the tree with ASCII characters instead of box-drawing glyphs.
-y, --shellRender column keys shell-safe (MAJ:MIN becomes MAJ_MIN).
-w, --width NUMTruncate each table row to NUM columns wide.
--sysroot DIRRead sysfs, the mount table and /dev from DIR instead of / (chiefly for testing against a fixture).
-h, --help / -V, --versionStandard.

Exit status #

CodeMeaning
0Success.
1Incorrect 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:

  1. Validate the layout. <src-dir> must be a directory, must carry an executable init (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.seq or system/prelude/hooks.seq.<n> — which mkirf generates. A missing or non-executable init, or a stray sequence file, stops the build.

  2. 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 in LC_ALL=C byte order, which places every directory before its descendants — the order the kernel's unpacker requires.

  3. 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. mkirf parses each hook's # /// hook metadata 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 missing hooks/ 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 mkirf writes every version it can express faithfully — currently hooks.seq.1 (the flat resolved order) and hooks.seq.2 (that order plus each hook's declarations). mkirf ships in peiosutils and 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, alongside system/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.

  4. 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 success mkirf prints 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, symlinks 0777, FIFOs and device nodes 0644, regular files 0755 if executable and 0644 otherwise.
  • The gzip member carries mtime 0 and no embedded filename (the gzip -n equivalent), 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; no init, or init is 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 # /// hook metadata block (unclosed, a non-comment line inside it, a duplicate or unknown key, a badly-formed array), a dependency cycle, a requires that 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). An after naming a capability nothing supplies is not an error — that is what distinguishes it from requires. (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, and mkirf prints 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, --watch with <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 #

OptionEffect
--watchStay resident and rebuild on every change to <src-dir>, after an initial build. Runs until killed.
--debounce SECSWith --watch, the settle time before a rebuild. Default 5. A non-numeric value is a usage error.
--exclude GLOBExclude 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:

PositionalMeaning
<src-dir>The source tree; its contents map onto / in the initramfs.
<out-file>The output cpio.gz archive.

Exit status #

CodeMeaning
0The image was built (or --help/--version was printed).
1An operational failure — an invalid layout, an unresolvable hook set, or an I/O/compression error.
2A 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 # /// hook metadata 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:

SectionContentSource
.cmdlineThe kernel command line, NUL-terminated.--cmdline or --cmdline-file
.linuxThe kernel image.--kernel
.initrdThe 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.

Note

mkuki does not sign the image. Producing the unified binary and signing it for Secure Boot are separate steps; mkuki has no signing options. Because a UKI bundles the kernel, initramfs, and command line into one PE image, that image is what a later Secure Boot milestone signs and the firmware verifies as a whole — but the signing itself is out of mkuki's scope.

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 #

OptionEffect
--kernel PATHThe kernel image; becomes the .linux section. Required (except with --stub-info).
--initramfs PATHThe initramfs cpio; becomes the .initrd section. Required (except with --stub-info).
--cmdline TEXTThe kernel command line, given literally; becomes the .cmdline section. Mutually exclusive with --cmdline-file.
--cmdline-file PATHRead the kernel command line from PATH instead. Mutually exclusive with --cmdline.
--out PATHThe output UKI path. Required (except with --stub-info).
--stub PATHThe PE/COFF EFI stub to append sections to. Defaults to the bundled systemd stub.
--stub-infoPrint the bundled stub's provenance and exit.
--watchStay resident and rebuild the UKI whenever --kernel, --initramfs, or --cmdline-file changes. Runs until killed.
--debounce SECSWith --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 #

CodeMeaning
0The UKI was built (or --stub-info, --help, or --version was printed).
1A build or watch failure — a missing or unreadable input, an invalid stub, an empty payload, or an I/O error.
2A usage error — a bad or missing option, or an invalid --cmdline/--cmdline-file combination.

See also #

  • mkirf — builds the .initrd payload mkuki wraps.
  • 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 nowreg
Look up what a value means — its type, default, valid range, when it appliesregman

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:

PathMeaning
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 .reg convention: 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:

PrefixRegistry typeData syntax
sz:REG_SZUTF-8 string (rest of token, verbatim).
expand:REG_EXPAND_SZUTF-8 string, %VAR% left unexpanded on disk.
dword:REG_DWORD42 or 0x2A; must fit u32.
dword-be:REG_DWORD_BIG_ENDIAN42 or 0x2A; must fit u32.
qword:REG_QWORD42 or 0x2A; must fit u64.
multi:REG_MULTI_SZComma-separated; \, escapes a literal comma.
hex: / bin:REG_BINARYHex bytes; :, -, and space separators are ignored.
link:REG_LINKAn absolute key path (a symlink target).
none:REG_NONENo 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 shapeInferred type
all decimal digits, fits u32REG_DWORD
all decimal digits, fits u64 but not u32REG_QWORD
0x… hex, ≤ 8 significant hex digitsREG_DWORD
0x… hex, ≤ 16 significant hex digitsREG_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.

OptionEnv varEffect
--jsonREG_JSON=1Emit structured JSON instead of human text. watch emits JSON-lines.
-v, --verboseREG_VERBOSE=1More detailed output.
-q, --quiet—Suppress non-essential output. (A surprising type coercion is still reported.)
--sep=CHARREG_SEPPath display separator: \ (default) or /.
--help—Print help for the command and exit 0.
--version—Print the version and exit 0.

Mutating subcommands additionally accept:

OptionEnv varEffect
--layer NAMEREG_LAYERTarget layer for the write (default: the base layer).
-y, --yesREG_ASSUME_YES=1Skip 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.

OptionEffect
-L, --layersAnnotate the value with the layer it resolved from and its sequence number.
--rawWrite the value's raw bytes to standard output verbatim — for piping REG_BINARY data.
--no-followOperate 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.

OptionEffect
-l, --longLong form: for subkeys, child and value counts; for values, type and byte size.
--keys-onlyList subkeys only.
--values-onlyList 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.

OptionEffect
--depth NLimit recursion to N levels.
--valuesInclude 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.

OptionEffect
--no-followInspect 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.

OptionEffect
--layer NAMEWrite into layer NAME instead of the base layer.
-p, --parentsCreate any missing ancestor keys.
--expected-seq NCompare-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.

OptionEffect
--layer NAMECreate the key in layer NAME.
-p, --parentsCreate any missing ancestor keys.
--volatileCreate 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.

OptionEffect
--layer NAMEDelete from layer NAME.
-r, --recursiveDelete the key and all its descendants (walks and removes children).
-y, --yesSkip 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.

OptionEffect
--layer NAMEThe layer that carries the tombstone.
--allBlanket 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.

OptionEffect
--layer NAMEThe 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.

OptionEffect
-l, --longLong 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\.

OptionEffect
--precedence NPrecedence (higher wins). A precedence above 0 requires SeTcbPrivilege; the tool attempts it and reports any denial.
--owner SIDRecord an informational owner SID.
--disabledCreate 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.

OptionEffect
--precedence NChange the precedence.
--enable / --disableEnable or disable the layer.
--owner SIDChange 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).

OptionEffect
--set SDDLApply the given SDDL. Without it, sd prints the current descriptor.
--ownerScope to the owner.
--groupScope to the group.
--daclScope to the DACL.
--saclScope 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.

OptionEffect
--layer NAMECreate 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.

OptionEffect
--subtreeWatch descendant keys too, not just this key.
--filter LISTComma-separated subset of value, subkey, sd (default: all).
--count NExit 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.

OptionEffect
--dir DIRApply every batch file in DIR (sorted), each as its own transaction. A missing or empty directory applies nothing and succeeds.
--once-deleteDelete each batch file after it applies successfully (a drain).
-y, --yesSkip 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.

OptionEffect
--layer NAMEExport 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 typeHuman formJSON form
REG_SZ / REG_EXPAND_SZthe string{"type":"sz","data":"…"}
REG_DWORD / REG_QWORDdecimal{"type":"dword","data":42}
REG_MULTI_SZone element per line{"type":"multi","data":["a","b"]}
REG_BINARYhex{"type":"binary","data":"deadbeef"}
REG_LINKthe target path{"type":"link","data":"…"}

Exit status #

CodeMeaningTypical cause
0Success.—
1Usage error.Bad arguments; a required option missing.
2Key or value not found.ENOENT.
3Access denied.EACCES, EPERM — the kernel refused the operation.
4Invalid specification.Bad path, type, or literal; a cross-hive batch; a name too long (EINVAL, EXDEV, ENAMETOOLONG).
5Syscall or source failure.EIO, ETIMEDOUT, ENOSPC, ENOMEM.
6Compare-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 you reg set.
  • Keys, values, and types — the data model reg addresses.
  • Layers — what "effective" resolves against, and what --layer, mask, and hide operate on.
  • Access control on keys — the check every reg operation 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:

FieldTells you
TypeThe value's registry type (REG_QWORD, REG_SZ, …).
DefaultThe value used when nothing is set.
ValidThe range, set, or constraint a sensible value must satisfy.
AppliesWhen 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 validregman — the manual
What the value is set toreg — a live query against the store
What the subsystem is actually running onthe 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.

Peios Learn — documentation for the Peios project.

Built with Trail.