RH(1)
๐ rh(1) โ rawhide: find files using pretty C expressions ๐
(User Commands)
โจ NAME โจ
rhโ short for rawhide: find files using pretty C expressions. ๐
๐ธ SYNOPSIS ๐ธ
okay bestie, here's the whole usage situation, preserved exactly so nothing gets lost:
usage: rh [options] [path...]
options:
-h --help - Show this help message, then exit
-V --version - Show the version message, then exit
-N - Don't read system-wide config (/etc/rawhide.conf)
-n - Don't read user-specific config (~/.rhrc)
-f- - Read functions and/or expression from stdin
-f fname - Read functions and/or expression from a file
[-e] 'expr' - Read functions and/or expression from the cmdline
traversal options:
-r - Only search one level down (same as -m1 -M1)
-m # - Override the default minimum depth (0)
-M # - Override the default maximum depth (system limit)
-D - Depth-first searching (contents before directory)
-1 - Single filesystem (don't cross filesystem boundaries)
-y - Follow symlinks on the cmdline and in reference files
-Y - Follow symlinks encountered while searching as well
alternative action options:
-x 'cmd %s' - Execute a shell command for each match (racy)
-X 'cmd %S' - Like -x but run from each match's directory (safer)
-U -U -U - Unlink matches (but tell me three times), implies -D
output action options:
-l - Output matching entries like ls -l (but unsorted)
-d - Include device column, implies -l
-i - Include inode column, implies -l
-B - Include block size column, implies -l
-s - Include blocks column, implies -l
-S - Include space column, implies -l
-g - Exclude user/owner column, implies -l
-o - Exclude group column, implies -l
-a - Include atime rather than mtime column, implies -l
-u - Same as -a (like ls(1))
-c - Include ctime rather than mtime column, implies -l
-v - Verbose: All columns, implies -ldiBsSac (unless -xXU0L)
-0 - Output null chars instead of newlines (for xargs -0)
-L format - Output matching entries in a user-supplied format
-j - Output matching entries as JSON (same as -L "%j\n")
path format options:
-Q - Enclose paths in double quotes
-E - Output C-style escapes for control characters
-b - Same as -E (like ls(1))
-q - Output ? for control characters (default if tty)
-p - Append / indicator to directories
-t - Append most type indicators (one of / @ = | >)
-F - Append all type indicators (one of * / @ = | >)
* executable
/ directory
@ symlink
= socket
| fifo
> door (Solaris only)
other column format options:
-H or -HH - Output sizes like 1.2K 34M 5.6G etc., implies -l
-I or -II - Like -H but with units of 1000, not 1024, implies -l
-T - Output mtime/atime/ctime in ISO format, implies -l
-# - Output numeric user/group IDs (not names), implies -l
debug option:
-? spec - Output debug messages: spec can include any of:
cmdline, parser, traversal, exec, all, extra
rh (rawhide) finds files using pretty C expressions.
See the rh(1) and rawhide.conf(5) manual entries for more information.
C operators:
?: || && | ^ & == != < > <= >= << >> + - * / % - ~ !
Rawhide tokens:
"pattern" "pattern".modifier "/path".field "cmd".sh
123 0777 0xffff 1K 2M 3G 1k 2m 3g $user @group $$ @@
[yyyy/mm/dd] [yyyy/mm/dd hh:mm:ss]
Glob pattern notation:
? * [abc] [!abc] [a-c] [!a-c]
?(a|b|c) *(a|b|c) +(a|b|c) @(a|b|c) !(a|b|c)
Ksh extended glob patterns are available here (see fnmatch(3))
Pattern modifiers:
.i .re .rei
.path .ipath .repath .reipath
.link .ilink .relink .reilink
.body .ibody .rebody .reibody
.what .iwhat .rewhat .reiwhat
.mime .imime .remime .reimime
.acl .iacl .reacl .reiacl
.ea .iea .reea .reiea
.sh
Case-insensitive glob matching is available here (i)
Perl-compatible regular expressions are available here (re)
Access control lists are available here (acl)
Extended attributes are available here (ea)
Built-in symbols:
dev major minor ino mode
nlink uid gid rdev rmajor
rminor size blksize blocks atime
mtime ctime attr proj gen
nouser nogroup readable writable executable
strlen depth prune trim exit
now today second minute hour
day week month year IFREG
IFDIR IFLNK IFCHR IFBLK IFSOCK
IFIFO IFDOOR IFMT ISUID ISGID
ISVTX IRWXU IRUSR IWUSR IXUSR
IRWXG IRGRP IWGRP IXGRP IRWXO
IROTH IWOTH IXOTH texists tdev
tmajor tminor tino tmode tnlink
tuid tgid trdev trmajor trminor
tsize tblksize tblocks tatime tmtime
tctime tstrlen
Reference file fields:
.exists .dev .major .minor .ino
.mode .type .perm .nlink .uid
.gid .rdev .rmajor .rminor .size
.blksize .blocks .atime .mtime .ctime
.attr .proj .gen .strlen .inode
.nlinks .user .group .sz .accessed
.modified .changed .attribute .project .generation
.len
System-wide and user-specific functions can be defined here:
/etc/rawhide.conf ~/.rhrc
/etc/rawhide.conf.d/* ~/.rhrc.d/*
๐ฆ INTRODUCTION ๐ฆ
okay so Rawhide (rh) is the moment: it lets you search for files on the command line using expressions and user-defined functions in a mini-language inspired by C. It's giving find(1), but honestly more fun to use. Your search criteria can be super readable and self-explanatory and/or very concise and typeable, and you can create your own lexicon of search terms. The output can include lots of detail, like ls(1). ๐
๐ DESCRIPTION ๐
rh (Rawhide) searches the filesystem, starting at each path you give it, for files that make the given search criteria expression true. If no search paths are given, the current working directory is searched. periodt.
You can feed the search criteria expression from a few places: from the command line with the -e option, from a file with the -f option, or from standard input (stdin) with -f-. If there is no explicit -e option expression, rh looks for an implicit expression among any remaining command line arguments. If no expression is specified, the default search criteria is the expression 1, which matches all filesystem entries. iconic.
An rh expression is a C-like expression that can call user-defined functions. These expressions can contain all of C's conditional, logical, relational, equality, arithmetic, and bit operators. no cap.
Numeric constants can be decimal, octal, or hexadecimal integers. Decimal constants can have scale units (e.g., 10K). cute.
There are built-in symbols that represent each candidate file's inode metadata. These are the fields in the corresponding stat(2) structure (e.g., st_mode, st_uid, st_size, st_mtime, ...). See stat(2) for details. For convenience, the "st_" prefix is omitted from the symbol names (e.g., st_mtime is used as mtime). slay.
Other built-in symbols represent the constants defined by C's <sys/stat.h> header file. These are useful for interpreting the mode in order to identify file types and permissions. The "S_" prefix is omitted from the symbol names (e.g., S_IFMT is used as IFMT). ๐
Other built-in symbols represent various useful values and constants, control flow, more file information, and candidate symlink target inode metadata.
File glob patterns and Perl-compatible regular expressions (regexes) can be used to match files by their name, path, symlink target path, body, file type description, MIME type, access control list, and extended attributes. obsessed.
Search criteria can also include comparisons with the inode metadata of arbitrary reference files, and the exit success status of arbitrary shell commands. yes bestie.
Functions are a means of referring to an expression by name. They allow complex expressions to be composed of simpler ones. They also allow you to create your own lexicon of search terms for finding files.
There is a default standard library of functions to start with. It provides a high-level interface to the built-in symbols mentioned above, and makes rh easy to use. See rawhide.conf(5) for details. ๐ซถ
๐ OPTIONS ๐
-h,--helpโ Display the help message, then exit. The--helpoption must not be used with any other command line options or arguments.- The help message summarizes the command line usage, and presents concise lists of the search criteria language operators, special tokens, glob pattern notation, pattern modifiers, built-in symbols, reference file fields, and the locations of configuration files.
- Some features are not available on all systems: Ksh extended glob patterns, case-insensitive glob matching, Perl-compatible regular expressions (regexes), access control lists, and extended attributes. The help message states which optional features are available on the local system.
- See the SYNOPSIS section above for details.
-V,--versionโ Display the version message, then exit. The--versionoption must not be used with any other command line options or arguments.-Nโ By default,rhfirst reads system-wide configuration from/etc/rawhide.conf(or similar), and then (in lexicographic order) from any files in the/etc/rawhide.conf.ddirectory (or similar) whose names do not start with dot ("."). This option suppresses that behaviour.-nโ By default,rhthen reads user-specific configuration from~/.rhrc, and then (in lexicographic order) from any files in the~/.rhrc.ddirectory whose names do not start with dot ("."). This option suppresses that behaviour.-f-,-f fnameโ okay so after rh has read all the config files, this bad boy makes it read code from the file named by fname. ๐ And plot twist: if there's also a directory whose name is fname with.dslapped on the end, rh goes and reads (in lexicographic order, so alphabetically bestie) any files in there whose names don't start with a dot ("."). If fname is itself a directory, rh reads any files in there the same way. And if fname is-, the code comes from standard input (stdin). You can drop-fmore than once, but using-(stdin) more than once is an error, no cap.
Each file can hold zero or more function definitions and/or a trailing file test expression at the end. If a file does have one of those trailing file test expressions, that's what gets used to match files โ unless another file test expression shows up in a later -f option file, or via -e, or in any of the leftover command line arguments. ๐
-e 'expr'โ reads code straight out of the expr argument itself. Supplying-emore than once is an error, periodt.
The expr argument can contain zero or more function definitions and/or a trailing file test expression. -e gets processed after any -f options, so it can totally use any functions you defined via -f. And if expr contains a file test expression (which, like, it's expected to), it overrides any default file test expression from a configuration file or from an -f option file. ๐
Normally the -e argument is what supplies the file test expression used for the file search. But since a lot of the operators are also shell meta-characters, and rh expressions can have spaces in them, it's strongly recommended that you wrap expr in single quotes ("'"). Trust. ๐ซถ
If you don't supply an explicit file test expression via -e, then rh peeks at whatever command line arguments are left over to sniff out an implicit file test expression. Here's the vibe check it runs:
- If a command line argument is a path that actually exists in the filesystem, it's treated as a filesystem entry to search. โจ
- Otherwise, if it contains characters that are likely to show up in an expression but unlikely to show up in most filesystem paths (i.e.,
"?:|&^=!<>*%$\"\\[]{};\n"), it's treated as a file test expression. - Otherwise, if it looks like a filesystem path (i.e., it has a slash character (
"/") in it and some apparent ancestor directory really does exist in the filesystem), it's treated as a filesystem entry โ one that just happens not to exist. ๐ - Otherwise? File test expression. Simple as that.
Only the first suitable command line argument gets interpreted as a file test expression. Everything else on the command line is all search paths, bestie.
This means it's almost always possible to not even bother typing -e at all. It also means you can put search paths before and/or after the file test expression, like:
$ rh -e 'expr' dir1 dir2
$ rh 'expr' dir1 dir2
$ rh dir1 'expr' dir2
$ rh dir1 dir2 'expr'
You only really need to be explicit with -e when the file test expression might happen to be identical to an existing filesystem entry relative to the current working directory (e.g., touch file; rh -e 'file' path), or (less likely) when the expression starts with a minus sign ("-") and would otherwise get mistaken for a command line option. ๐ญ
You can also need an explicit -e if you want the file test expression to sit to the left of any command line options on (most) systems where all non-option command line arguments have to come after all the options and their arguments (e.g., rh -e 'expr' -X 'cmd %S' dir). That doesn't apply to Linux with GNU glibc though, which is chill and flexible about command line option parsing. ๐
And if no file test expression turns up anywhere? The default file test expression is 1, which matches every filesystem entry. Slay.
๐ Traversal options ๐
-rโ makes rh only report (or act on) the immediate contents of the starting search directories. The starting search directories themselves are excluded ๐ซ, and the contents of any sub-directories of the starting search directories don't get searched either. This is literally the same as-m1 -M1(see next).
This option and -m are mutually exclusive.
This option and -M are mutually exclusive.
-m #โ overrides the default minimum search depth to report (or act on). By default the minimum search depth is zero, meaning the starting search directories get reported (or acted on) if they satisfy the file test expression. ๐ฏ
So like, setting the minimum search depth to 1 suppresses reporting (or acting on) the starting search directories even if they match โ now it only reports (or acts on) the matching entries among those directories' entries and their descendants.
Heads up though: this option does not stop file test evaluation above the minimum search depth. It only stops reporting (or acting on) matching entries. This matters when your search criteria involves the prune, trim, or exit built-ins (see rawhide.conf(5)), because those have control-flow side-effects when they get evaluated. Which is lowkey useful โ it lets you skip sub-directories, or straight-up terminate a search, before anything is reported (or acted on). ๐ฅ
This option and -r are mutually exclusive.
-M #โ overrides the default maximum search depth to examine. By default that max is a very large system-imposed limit (e.g.,1019). ๐ณ
Setting the maximum search depth to 1, for example, stops the search below the immediate children of the starting search directories. And setting it to 0 stops the search below the starting search paths themselves.
This option and -r are mutually exclusive.
-Dโ does a depth-first search. That means directories get examined and reported (or acted on) after their descendants, not before them. ๐ฆ
This option is incompatible with the prune and trim built-ins (see rawhide.conf(5)). When you use -D, prune and trim just will not work โ they won't prevent searching in sub-directories. ๐
Lowkey important: the -U option implies this one (see below).
-1โ limits the search to each starting search directory's filesystem only. So it stops rh from descending into directories that are mountpoints for other filesystems. ๐ง-yโ by default rh does not follow symlinks. This option makes rh follow any symlinks supplied as command line arguments or reference files. But any candidate symlinks it encounters while searching are still not followed. ๐
Note: when a followed symlink is broken/dangling, instead of reporting it as an error, the resultingstat(2)structure fields will be those of the symlink itself. This might or might not be the behaviour you want. It's done for compatibility with the familiar behaviour offind(1). If you'd rather an attempt to follow a broken symlink got reported as an error, set the environment variableRAWHIDE_REPORT_BROKEN_SYMLINKS=1. The resultingstat(2)structure fields will still be those of the symlink itself, and searching will still continue, but you'll get an error message and the eventual exit status will be non-zero to indicate failure. ๐ญ
This option is compatible with the symlink target-related built-ins (see rawhide.conf(5)) and the -L %Y format conversion (see below) โ except for any symlinks on the command line. For those, the symlink target-related built-ins and the -L %Y format conversion will only ever get to see symlinks that are broken. ๐
-Yโ by default rh does not follow symlinks. This option makes rh follow any symlinks supplied as command line arguments or reference files, and any candidate symlinks encountered while searching. ๐
Note: when a followed symlink is broken/dangling, instead of reporting it as an error, the resultingstat(2)structure fields will be those of the symlink itself. This might or might not be the behaviour you want. It's done for compatibility with the familiar behaviour offind(1). If you'd rather an attempt to follow a broken symlink got reported as an error, set the environment variableRAWHIDE_REPORT_BROKEN_SYMLINKS=1. The resultingstat(2)structure fields will still be those of the symlink itself, and searching will still continue, but you'll get an error message and the eventual exit status will be non-zero to indicate failure. ๐
This option is incompatible with the symlink target-related built-ins (see rawhide.conf(5)) and the -L %Y format conversion (see below). The only symlinks they'll ever get to see are broken ones. ๐
๐ฌ Alternative action options ๐ฌ
By default, rh outputs each matching filesystem entry's full path starting from the search directory. These options give you alternative actions instead. They โ plus the -l, -0, -L, and -j options โ are all mutually exclusive. Pick one, bestie, you can't have them all. โจ
-x 'cmd'โ executes the shell command specified by cmd viasystem(3)(i.e., via/bin/sh) for each matching entry. Supplying-xmore than once is an error.
The cmd argument can contain %s, which gets replaced with the matching entry's full path starting from the search directory. It can also contain %S, which gets replaced with the matching entry's base name (or with "/" when the matching entry is the root directory (/), which has no base name, obviously). For example, given the matching file /etc/passwd, %s and %S would be replaced with "/etc/passwd" and "passwd", respectively. To include a literal per cent sign ("%") in the shell command, use %%. And it's an error if % isn't followed by s, S, or %. ๐
Any shell meta-characters in the interpolated path or base name get quoted with preceding backslash characters ("\") to prevent shell command injection, so you do not need to put quote characters around %s or %S. Security said slay. ๐ฅ
For this option, the %s interpolation is more likely to be useful than the %S interpolation. iykyk.
If any command exits with a non-zero exit status, rh itself will keep going, but it'll eventually exit with a non-zero exit status. ๐ญ
This is basically the -exec action from POSIX find(1), and bestie, it suffers from the exact same huge pile of path-based race conditions as -exec ๐ญ. On hosts where malicious local actors have write access to the directory tree you're searching, this is insecure, so like โ don't generally use it, ok? It is much safer to use the -X option instead (see next ๐). Also note that piping the default output to a program like xargs(1) is insecure in exactly the same way. no cap.
Note: if the user's$PATHenvironment variable includes the current working directory, or any other non-absolute paths, they get automatically removed first โจ. This is done for consistency with the-Xoption (see next) and the"cmd".sh"pattern"modifier (seerawhide.conf(5)), where this is needed for security. Which means you can't rely on$PATHto find an executable that's sitting in the current directory โ you'd need an explicit path instead (e.g.,-x './cmd %s'). periodt.
This option, and the -l, -0, -L, -j, -X, and -U options, are all mutually exclusive. one at a time, bestie ๐
- **
-X'cmd'** โ Execute the shell command specified by *cmd* viasystem(3)(i.e., via/bin/sh) for each matching entry. Supplying-X` more than once is an error. ๐จ
It's like the -x option (see above), except the shell command is executed after safely changing the current working directory to the directory containing each matching entry. Which minimizes the number of path-based race conditions. she's careful like that ๐
The cmd argument can contain %s, which gets replaced with the matching entry's full path starting from the search directory. It can also contain %S, which gets replaced with the matching entry's base name (or with / when the matching entry is the root directory (/) which has no base name, obviously ๐). For example, given the matching file /etc/passwd, %s and %S would be replaced with /etc/passwd and passwd, respectively. To include a literal per cent sign (%) in the shell command, use %%. It is an error if % is not followed by s, S, or %.
Any shell meta-characters in the interpolated path or base name are quoted with preceding backslash characters (\) to prevent shell command injection, so there is literally no need to place any quote characters around %s or %S. ๐ก๏ธ
For this option, the %S interpolation is more likely to be useful than the %s interpolation. iykyk.
If any command exits with a non-zero exit status, rh itself will continue, but it will eventually exit with a non-zero exit status. she's patient, but she remembers ๐
This is similar to the -execdir action in GNU find(1), and so does not suffer from the same large number of path-based race conditions as the -exec action in POSIX find(1). It is much safer than the -x option, and should generally be used in preference. ๐
And if the user's $PATH environment variable includes the current working directory, or any other non-absolute paths, that could be dangerous, so they are automatically removed first.
Note: since the shell commands are executed from the directory containing each matching entry, if they do require the matching entry's full path starting from the search directory (i.e.,%s), then it's best if the starting search paths are all absolute paths, so that%sis always an absolute path โจ. Otherwise, the shell command might need to change its current working directory back to the initial working directory. Also note that%ssuffers from many path-based race conditions, which is insecure on hosts with malicious local actors that have write access to the directory tree being searched, and so should not generally be used. โ ๏ธ
This option, and the -l, -0, -L, -j, -x, and -U options, are all mutually exclusive. pick one, babe ๐
-U-U-Uโ Unlink/Remove/Delete matching filesystem entries. ๐๏ธ Due to the destructive nature of this option, and the ease with which a single letter can be mistyped, this option must be supplied three times in order for it to take effect. It is an error to supply the-Uoption once or twice. yes, three. we're serious about this one ๐ค
This option implies the use of the -D option (see above) to ensure that each matching directory's matching entries are removed before it is. Directories can only be removed when they are empty, obviously.
If rh fails to remove any matching entry, it will continue, but it will eventually exit with a non-zero exit status.
This option is incompatible with the prune and trim built-ins (see rawhide.conf(5)). When this option is used, prune and trim will not work. They will not prevent unlinkage/removal/deletion in sub-directories. ๐
When this option is used with the -y or -Y option (see above), and a symlink to a directory is followed, the symlink's ultimate target directory's contents are searched, and any matches found there are removed, but the target directory itself is never removed. It isn't possible to remove a filesystem entry via a symlink to it, bestie โ that's just not a thing ๐ฆ. If the target directory itself matches the search criteria, the symlink to it is removed. Similarly, when a symlink to a non-directory is followed, and the symlink's ultimate target matches the search criteria, the symlink is removed, not the ultimate target. the link gets got, not the target ๐
This option, and the -l, -0, -L, -j, -x, and -X options, are all mutually exclusive.
๐ Output action options ๐
-lโ By default,rhoutputs each matching path on a line by itself. This option includes more details in a format similar to that ofls -l(but unsorted, don't come for her ๐ธ). The details included are the file type, permissions, existence of an access control list and/or extended attributes, number of hard links, user/owner, group, size (or comma-separated rdev major and minor device numbers), modified time, and path. For symlinks, the target path is also included at the end (preceded by->).
Note that, unlike ls -l, for readable directories, the reported size is the number of entries they contain (excluding . and ..). For unreadable directories, it is the usual (undocumented) st_size field of the corresponding stat(2) structure. ๐
If a file has a non-trivial access control list (ACL), this is indicated by a plus sign (+) at the end of the file type and permissions column (e.g., -rw-rw-r--+). If a file has any extended attributes (EA), this is indicated by an at sign (@) (e.g., -rw-rw-r--@). Note that this doesn't include the EAs that are used on Linux for ACLs and selinux(8) contexts, because they are not interesting enough (ACLs are already indicated by +, and selinux(8) contexts are ubiquitous, and they are indicated by . (see below)). If a file has both a (non-trivial) ACL and any (interesting) EAs, this is indicated by an asterisk character (*) (e.g., -rw-rw-r--*). If a file has neither, but it does have a selinux(8) context, this is indicated by a dot character (.) (e.g., -rw-rw-r--.). If a file has none of the above, there's just a space character (" ") at the end of the file type and permissions column. it's giving forensic detail and we're obsessed ๐ฅ
This option, and the -0, -L, -j, -x, -X, and -U options, are all mutually exclusive.
-dโ Include the device column. Implies the-loption.
This is the comma-separated major and minor device numbers of the device/filesystem that the matching file resides on. ๐ฅ๏ธ
This column is first.
-iโ Include the inode number column. Implies the-loption.
This column is after any device column, and before any block size column.
-Bโ Include the block size column. Implies the-loption.
This column is after any inode number column, and before any blocks column.
Note that this is just the preferred block size for efficient I/O on the matching file's filesystem. On some filesystems (e.g., zfs), this is specific to each file, rather than to the whole filesystem. โจ
Note that this is unrelated to the blocks column (see next). don't confuse the girlies ๐
-sโ Include the blocks column. Implies the-loption.
This column is after any block size column, and before any space column.
Note that the number of blocks always refers to standardized 512-byte blocks, even when the filesystem's real block size is something else. math is math ๐ข
-Sโ Include the space column. Implies the-loption.
The space occupied by a file is the number of 512-byte blocks multiplied by 512. This is usually larger than the size in bytes, but it can be smaller in the case of files with holes (and on filesystems with transparent compression). ๐คฏ
This column is after any blocks column, and before the file type and permissions column.
-gโ Exclude the user/owner column. Implies the-loption.-oโ Exclude the group column. Implies the-loption.-aโ Include the accessed time column in place of the modified time column. Implies the-loption.
If the -a/-u and -c options are both supplied, then both columns appear in place of the modified time column (with the accessed time column appearing before the inode changed time column). two for one, slay ๐
-uโ Same as the-aoption (likels(1)). twin behavior, iconic ๐ซถ-cโ Include the inode changed time column in place of the modified time column. Implies the-loption.
If the -a/-u and -c options are both supplied, then both columns appear in place of the modified time column (with the accessed time column appearing before the inode changed time column).
-vโ Turn on verbose mode. ๐ With the-loption, this option includes all possible columns (i.e., device, inode number, block size, number of blocks, space, file type, permissions, existence of an access control list and/or extended attributes, number of hard links, user/owner, group, size (or comma-separated rdev major and minor device numbers), modified time, accessed time, inode changed time, and path). she's showing off at this point and honestly? deserved ๐
With the -x or -X option, this option outputs each command before it is executed. When standard output (stdout) is a terminal, ? is output in place of any control characters to prevent terminal escape injection. ๐ก๏ธ
With the -U option, this option outputs each matching path before it is removed. When standard output (stdout) is a terminal, ? is output in place of any control characters to prevent terminal escape injection.
With the -0 option, this option has no effect. ๐ nothing. zero. nada.
With the -L option, this option makes the %z format conversion on FreeBSD and Solaris output the non-compact form of NFSv4 access control lists (ACLs), rather than the default compact form. ๐
-0โ drops a null character ("\0") after every matching path instead of the usual newline ("\n"). Bestie, this is the one you reach for when you're pairing withxargs -0and your paths are out here containing chaos like newlines. ๐ซถ
โ ๏ธ But listen โ because of a huge pile of path-based race conditions, piping output into something likexargs(1)is insecure on hosts where malicious local actors have write access to the directory tree being searched. So likeโฆ don't make it a habit, okay? Using the-Xoption instead is much safer (scroll up for that one ๐ ).
This option, plus the -l, -L, -j, -x, -X, and -U options, are all mutually exclusive. Pick one, queen. ๐
-L formatโ outputs selected info about matching entries, shaped by a format you supply. It's givingprintf(3)andstrftime(3)energy โจ. Supplying-Lmore than once is an error โ just don't.
Unlike -l and -0, nothing gets appended for you โ no newline, no null. Not one crumb. An empty -L argument (i.e., -L '') produces no output at all. Iconic minimalism, honestly.
This option, plus the -l, -0, -j, -x, -X, and -U options, are all mutually exclusive. ๐
๐ธ the backslash escape sequences you can use ๐ธ
\aโ Alert or Bell (BEL)\bโ Backspace (BS)\cโ Stop processing this format string and flush the output\fโ Form feed (FF)\nโ Newline or Line feed (LF)\rโ Carriage return (CR)\tโ Horizontal tab (HT)\vโ Vertical tab (VT)\0โ Null byte (NUL)\\โ A literal backslash ("\")\NNNโ The byte whose numeric value isNNN(1-3 octal digits)
A backslash followed by any other character is treated as an ordinary character, and both characters get output. No drama, no error. ๐ฆ
๐ the % format conversion specifiers ๐
%%โ A literal per cent sign ("%").%pโ The path, including the starting search directory.
When standard output (stdout) is a terminal, "?" is output in place of any control characters to prevent terminal escape injection. (Yes, this note shows up a lot down here โ it's protecting you every time ๐ก๏ธ)
%Pโ The path, excluding the starting search directory.
When standard output (stdout) is a terminal, "?" is output in place of any control characters to prevent terminal escape injection.
%fโ The base name: the path minus any leading directories and the final slash character ("/"). Special case gem ๐: for the root directory (/), which has no base name, this is"/".
When stdout is a terminal, "?" is output in place of any control characters to prevent terminal escape injection.
%hโ The directory: the path minus the last slash character ("/") and the base name. Special case: for paths in the current working directory (with no slash), this is".". And heads up โ for the root directory (/) and its immediate children, this is the empty string.
When stdout is a terminal, "?" is output in place of any control characters to prevent terminal escape injection.
%lโ The target path of a symlink. For non-symlinks, this is the empty string. ๐
When stdout is a terminal, "?" is output in place of any control characters to prevent terminal escape injection.
%Hโ The starting search directory. ๐
When stdout is a terminal, "?" is output in place of any control characters to prevent terminal escape injection.
%dโ The depth relative to the starting search directory (depth).%Dโ The device number of the device/filesystem that the file resides on (dev). Peep the related%Vand%vconversions (next ones down โฌ๏ธ) for the major and minor device numbers of the device/filesystem the file resides on.%Vโ The major device number of the device/filesystem the file resides on (major, part ofdev).%vโ The minor device number of the device/filesystem the file resides on (minor, part ofdev).%iโ The inode number (ino).%Mโ The file type and permissions in symbolic form (like inls -l) (mode).%yโ The file type (like inls -l, but with"f"for regular files rather than"-").%Yโ The file type (like%y), except it shows the type of symlink targets instead of the symlinks themselves. Symlink-related errors are flagged with"N"for non-existence,"L"for loops, and"?"for any other error.
๐ Note: the -Y option is incompatible with this format conversion. The only symlinks you'll see will be broken ones. Tragic.
%mโ The file permissions in octal (mode & ~IFMT).%nโ The number of hard links (nlink).%uโ The user name (based onuid), or the numeric user ID if the user has no name.%gโ The group name (based ongid), or the numeric group ID if the group has no name.%Uโ The numeric user ID (uid).%Gโ The numeric group ID (gid).%Eโ The device number of the file (rdev). Only meaningful for character devices and block devices, bestie. See the related%Rand%rconversions (next โฌ๏ธ) for the major and minor device numbers of the file.%Rโ The major device number of the file (rmajor, part ofrdev). Only meaningful for character devices and block devices.%rโ The minor device number of the file (rminor, part ofrdev). Only meaningful for character devices and block devices.%sโ For regular files, this is the size in bytes (size). For symlinks (that aren't followed with the-yor-Yoption), this is the length in bytes of the target path. For readable directories, this is the number of entries they contain (excluding.and..). For unreadable directories (and literally everything else), this is the usual (undocumented)st_sizefield of the correspondingstat(2)structure. Whew. ๐ฎโ๐จ%Sโ The file "sparseness". Only meaningful for regular files. Defined as(blocks * 512) / sizewhen the file size is non-zero, or as1otherwise. So: values above 1 mean files that haven't filled up their last block. The value1means files that have filled up their last block, and empty files. Values below 1 mean files with holes (or a filesystem with transparent compression). The value0means files that are not real files on disk (e.g., kernel parameters exposed as virtual files). Math queen ๐งฎ๐%Bโ The preferred block size for efficient I/O on the file's filesystem (blksize). On some filesystems (e.g.,zfs), this is specific to each file, rather than to the whole filesystem. Weird but slay.%bโ The amount of disk space occupied by the file in standardized 512-byte blocks (blocks).%kโ The amount of disk space occupied by the file in units of 1KiB "blocks". Defined as(blocks + blocks % 2) / 2. Note that elsewhere, reported blocks are always 512 bytes, and that real blocks on modern filesystems are often larger (e.g., 4KiB). Don't get it twisted ๐%aโ The accessed time (atime) in the format returned by the Cctime(3)function (excluding its terminating newline).%Akโ The accessed time (atime) in the format specified by k, which is either the at sign ("@") for the number of seconds since the UNIX epoch, or a conversion specifier character for the Cstrftime(3)function. Seestrftime(3)for details. ๐ฐ๏ธ%tโ The modified time (mtime) in the format returned by the Cctime(3)function (excluding its terminating newline).%Tkโ The modified time (mtime) in the format specified by k, which is either the at sign ("@") for the number of seconds since the UNIX epoch, or a conversion specifier character for the Cstrftime(3)function. Seestrftime(3)for details.%cโ The inode changed time (ctime) in the format returned by the Cctime(3)function (excluding its terminating newline).%Ckโ The inode changed time (ctime) in the format specified by k, which is either the at sign ("@") for the number of seconds since the UNIX epoch, or a conversion specifier character for the Cstrftime(3)function. Seestrftime(3)for details.%wโ The file type description (as would be output by thefile(1)utility). Available on systems withlibmagic(3)installed. On other systems, this is the empty string. ๐ช
When stdout is a terminal, "?" is output in place of any control characters to prevent terminal escape injection.
%Wโ The MIME type (including the character set). Available on systems withlibmagic(3)installed. On other systems, this is the empty string.
When stdout is a terminal, "?" is output in place of any control characters to prevent terminal escape injection.
%eโ The Linux ext2-style file attributes, or BSD-style file flags, as a space-separated list of attribute/flag names. Available on Linux systems withlibe2p. Seechattr(1)andlsattr(1)for details. Also available on FreeBSD, OpenBSD, NetBSD, and macOS. Seechflags(1)for details. On other systems, this is the empty string.
The possible Linux ext2-style file attribute names are: secrm, unrm, compr, sync, immutable, append, nodump, noatime, dirty, comprblk, nocompr, encrypt, index, imagic, journal_data, notail, dirsync, topdir, huge_file, extents, verity, ea_inode, nocow, snapfile, dax, snapfile_deleted, snapfile_shrunk, inline_data, projinherit, and casefold.
The possible FreeBSD file flag names are: nodump, uimmutable, uappend, opaque, unounlink, system, sparse, offline, reparse, archive, readonly, hidden, archived, simmutable, sappend, snounlink, and snapshot.
The possible OpenBSD file flag names are: nodump, uimmutable, uappend, opaque, archived, simmutable, and sappend.
The possible NetBSD file flag names are: nodump, uimmutable, uappend, opaque, snapshot, log, and snapinval.
and the possible macOS file flag names are: nodump, uimmutable, uappend, opaque, compressed, tracked, datavault, hidden, archived, simmutable, sappend, restricted, snounlink, firmlink, and dataless. ๐
%Jโ the ext2-style project number. This one's available on Linux systems withlibe2p, and you can peepchattr(1)andlsattr(1)for the deets. On other systems? It's the empty string, bestie. ๐%Iโ the ext2-style version/generation number. Same situation: available on Linux systems withlibe2p, seechattr(1)andlsattr(1)for details, and on other systems it's the empty string. โจ%zโ the access control list (ACL) as a comma-separated list of items. Available on Linux, FreeBSD, macOS, Solaris, and Cygwin. On systems without supported ACLs, this is the empty string.- FreeBSD and Solaris have NFSv4 ACLs with two forms of ACL text. By default, the compact form will be output. With the
-voption, the non-compact form will be output. For "POSIX" ACLs (Linux and Cygwin) and macOS ACLs, the-voption has no effect. ๐ - On Solaris, ACLs are always present by default, even if they are trivially identical to the file permission bits. This can be convenient, but if it seems like noise, it can be silenced (but only on Solaris) by setting the environment variable
RAWHIDE_SOLARIS_ACL_NO_TRIVIAL=1.
- FreeBSD and Solaris have NFSv4 ACLs with two forms of ACL text. By default, the compact form will be output. With the
%xโ the extended attributes (EA) as a comma-separated list. Available on Linux, FreeBSD, macOS, Solaris, and Cygwin. On systems without supported EAs, this is the empty string. ๐ฆ- Heads up: any control characters (i.e., ASCII 0-31, 127) in extended attribute names or values, and any non-ASCII bytes (i.e., 128-255) in the values, will be presented as C-like backslash escape sequences such as
"\n"for the newline character, and"\0"for the null character, or as hexadecimal escape sequences such as"\x1b"for the escape character. Any backslash characters are quoted with a preceding backslash. Note that commas are not quoted. ๐ญ - On FreeBSD, extended attributes in the user namespace are presented with
"user."as a prefix to their actual name, and those in the system namespace are presented with"system."as a name prefix. On FreeBSD, only the root user may see extended attributes in the system namespace. - On most systems with extended attributes, the values are typically only up to a few hundred bytes in size. But on Solaris, extended attributes take the form of regular files in a special extended attributes directory
"hidden"inside each real file. Entire files can be copied into that special directory, and they become extended attributes. So extended attributes could tend to be larger on Solaris. ๐ - The default maximum total size for (encoded) extended attributes is 4KiB on most systems, and 64KiB on Solaris. If this is not enough, extended attributes will be silently truncated. This affects extended attribute searching (
ea) (seerawhide.conf(5)), and the-L %xformat conversion. To prevent truncation, set the environment variableRAWHIDE_EA_SIZEto a positive integer value that is large enough for your needs. Note that the value must be the size in bytes. Scale units are not supported. ๐ - On Solaris, every file's extended attributes directory contains the
SUNWattr_roandSUNWattr_rwextended attributes. By default, they are included for every file that has any other extended attributes. They can be excluded by setting the environment variableRAWHIDE_SOLARIS_EA_NO_SUNWATTR=1. - Also on Solaris, since extended attributes are files (of a sort), they each have their own
stat(2)information. By default, this information is represented as an artificial extended attribute whose name is the name of the corresponding real extended attribute followed by"/stat". These artificial extended attributes can be suppressed by setting the environment variableRAWHIDE_SOLARIS_EA_NO_STATINFO=1. ๐ง
- Heads up: any control characters (i.e., ASCII 0-31, 127) in extended attribute names or values, and any non-ASCII bytes (i.e., 128-255) in the values, will be presented as C-like backslash escape sequences such as
%Zโ theselinux(8)context. Available on Linux systems withselinux(8)enabled. On other systems, this is the empty string.%Xโ the access control list/extended attributes (ACL/EA) indicator (like inrh -l). When a (non-trivial) ACL is present, this is a plus sign ("+"). When any (interesting) EAs are present, this is an at sign ("@"). When both are present, this is an asterisk character ("*"). When neither is present, but there is aselinux(8)context, this is a dot character ("."). When none of the above are present, this is a space character (" "). โญ%jโ all of the file information in JSON format, representing an object with the following possible attributes:path(string) (same as%p)name(string) (same as%f)target(string) (same as%l) (only for symlinks)start(string) (same as%H)depth(integer) (same as%d)dev(integer) (same as%D)major(integer) (same as%V)minor(integer) (same as%v)ino(integer) (same as%i)mode(integer) (like%M, but in the underlying numeric form)modestr(string) (same as%M)type(string) (same as%y)perm(integer) (same as%m, but in decimal)nlink(integer) (same as%n)user(string) (same as%u) (only if a name is available)group(string) (same as%g) (only if a name is available)uid(integer) (same as%U)gid(integer) (same as%G)rdev(integer) (same as%E)rmajor(integer) (same as%R)rminor(integer) (same as%r)size(integer) (same as%s)blksize(integer) (same as%B)blocks(integer) (same as%b)atime(string) (like%a/%A@, but in ISO format)mtime(string) (like%t/%T@, but in ISO format)ctime(string) (like%c/%C@, but in ISO format)atime_unix(integer) (same as%A@)mtime_unix(integer) (same as%T@)ctime_unix(integer) (same as%C@)filetype(string) (same as%w) (only if available)mimetype(string) (same as%W) (only if available)attributes(string) (same as%e) (only if available)project(integer) (same as%J) (only if available)generation(integer) (same as%I) (only if available)access_control_list(string) (like%zwithout the-voption, but not reformatted as a comma-separated list) (only if present)access_control_list_verbose(string) (like%zwith the-voption, but not reformatted as a comma-separated list) (only if present)extended_attributes(string) (like%x, but not reformatted as a comma-separated list) (only if present)selinux_context(string) (same as%Z) (only if present)acl_ea_indicator(string) (same as%X)
Note that any extended attributes are formatted and encoded as described for theeapattern modifier (seerawhide.conf(5)), before being encoded again as a JSON string literal. ๐
Also note that %j should not be used in conjunction with other format conversions, especially %x. If %x appears before %j in the -L format argument, the extended_attributes value will be reformatted as a comma-separated list. But this is unlikely to be a problem. The %j format conversion probably needs to be used by itself if the output is to be interpreted as valid JSON by other software. ๐
Also note that this only works in locales that use UTF-8.
It is an error if % is followed by any other character, or if it is the last character in the format string. ๐จ
All of the ISO C printf(3) conversion flags are available (i.e., "#", "0", "-", " ", and "+"), as well as field width and precision specifiers. These behave differently depending on the type of underlying printf(3) conversion that they are applied to. See printf(3) for details.
The above conversions that output text use an underlying printf(3) %s string conversion. Those that output an integer use an underlying %d or %o integer conversion. The %S (sparseness) conversion uses an underlying %g floating point number conversion. ๐ธ
Note that, when the %u (user) or %g (group) conversions are forced to output a numeric user or group ID because the user or group has no name, it is still output using an underlying %s string conversion, rather than changing to an underlying %d integer conversion. This is to prevent any surprises relating to the use of conversion flags or precisions. Also note that the %A@, %C@, and %T@ conversions output integers, so they use an underlying %d integer conversion, but all other %Ak, %Ck, and %Tk conversions use an underlying %s string conversion (because strftime(3) produces strings). ๐
This option is mostly but not entirely compatible with GNU find(1)'s -printf action. rh doesn't do find(1)'s %F (filesystem type), and find(1) doesn't do rh's %z (access control list), %x (extended attributes), %X (ACL/EA indicator), %w (file type description), %W (MIME type), %e (attributes), %J (project), %I (generation), %V (dev major device number), %v (dev minor device number), %E (rdev device number), %R (rdev major device number), %r (rdev minor device number), %B (block size), or %j (JSON). ๐ซถ
And for most of the format conversions that output an integer, rh and find(1) use different underlying printf(3) conversions (rh uses %d, and find(1) uses %s), so conversion flags or precisions would behave differently. This difference applies to all (non-strftime(3)) conversions that output integers except %m (file permissions in octal) and %d (depth), which rh and find(1) both treat as integers. But if conversion flags and precisions are not used, there is no difference. ๐
And rh's %s (size) conversion outputs the size of readable directories as the number of entries they contain (excluding . and ..), rather than the usual (undocumented) st_size field of the corresponding stat(2) structure. This is arguably compatible, but not identical. This also makes the output of the %S (sparseness) conversion different for readable directories (but it isn't meaningful for directories, so that shouldn't matter). ๐ฆ
-jโ makesrhspit out matching entries as JSON โจ it's literally the same as-L "%j\n"(scroll up bestie, it's up there ๐). And heads up: this one is mutually exclusive with-l,-0,-L,-x,-X, and-Uโ they cannot coexist, it's giving "pick one" energy ๐
๐ Path format options ๐
-Qโ wraps matching paths in double quotes ("). Any double quote or backslash characters (\) inside a path get quoted with a preceding backslash. So your paths come out all cozy and escaped ๐ซถ-Eโ outputs C-style escape sequences instead of any control characters in matching paths. Some of those control chars have cute single-letter backslashed encodings (i.e.,\a\b\t\n\v\f\r, which are ASCII 7 (BEL), 8 (BS), 9 (HT), 10 (LF), 11 (VT), 12 (FF), and 13 (CR), respectively ๐). The rest get output as backslashed octal numbers (e.g.,\033, which is ASCII 27 (ESC)). Any backslash characters (\) in a path get quoted with a preceding backslash. Mutual exclusivity alert ๐จ: this and-qcannot both be used.-bโ literally the same as-E, likels(1)does it. Same vibes, different letter ๐-qโ outputs?in place of any control characters in matching paths. This is the default when standard output (stdout) is a terminal and you haven't supplied-E/-b, and it's there so nobody can do terminal escape injection on you ๐ค protect your terminal bestie. This option and-E/-bare mutually exclusive, obviously.-pโ tacks a/onto matching directory paths so you can tell they're directories. subtle but iconic ๐-tโ outputs most of the type indicators after matching paths (i.e., one of/,@,=,|, or>). Not all of them โ most ๐-Fโ outputs all of the type indicators after matching paths (i.e., one of*,/,@,=,|, or>). Full send โจ
And here's what the type indicators actually mean, no cap:
* executable
/ directory
@ symlink
= socket
| fifo
> door (Solaris only)
๐ธ Other column format options ๐ธ
-Hor-HHโ for the block size, space, and size columns, use "human readable" traditional computer storage units based on 1024 bytes, instead of just raw numbers of bytes. It's like the-hoption in GNUls(1). Implies the-loption. Obsessed honestly ๐- If the size is below
1024, it gets output the usual way as a number of bytes. Otherwise the appropriate scale is picked (i.e.,K,M,G,T,P,E). If the scaled number is less than ten, a decimal place is included. Otherwise, an integer is output. For the block size and space columns, a decimal place isn't included if it's zero. The size is rounded up. That gives you the property that the actual size is never larger than the reported size ๐ - Supplied twice (i.e.,
-HH)? Then instead of always rounding up (likels(1)does), the reported size is rounded half up. More accurate figures than always rounding up, periodt โจ - Btw this option and
-Iare mutually exclusive, pick a lane bestie ๐
- If the size is below
-Ior-IIโ same idea but for the block size, space, and size columns it uses International System of Units (SI) prefixes, based on 1000 bytes, rather than just numbers of bytes. Like the--sioption in GNUls(1). Implies the-loption.- If the size is below
1000, it's output the usual way as a number of bytes. Otherwise the right scale is determined (i.e.,k,m,g,t,p,e). If the scaled number is less than ten, a decimal place is included. Otherwise, an integer is output. For the block size and space columns, a decimal place isn't included if it's zero. The size is rounded up โ so again, the actual size is never larger than the reported size ๐ซถ - Twice (i.e.,
-II)? Then instead of always rounding up (likels(1)), the reported size is rounded half up. More accurate figures than always rounding up ๐ฅ - Like
ls(1), a lower casekrepresents KB. But unlikels(1)(and unlike real SI prefixes), lower case letters represent all the other SI prefixes too ๐ญ this is on purpose, to avoid any ambiguity, iykyk. - This option and
-Hare mutually exclusive โ no mixing, bestie.
- If the size is below
-Tโ for the modified time, accessed time, and inode changed time columns, use ISO date/time format (YYYY-MM-DD HH:MM:SS +HHMM) instead of the default format (MMM DD HH:MM:SS YYYY). Implies the-loption. ISO girlies stay winning ๐-#โ for the user/owner and group columns, use numeric user and group IDs instead of user and group names. Implies the-loption. Numbers only, no names, slay ๐
๐ฆ Debug option ๐ฆ
-?spec โ outputs debug messages to standard error (stderr). The spec argument gets scanned for one or more of these labels:cmdline,parser,traversal,exec,all,extra
The first four labels relate to different aspects of rh. all implies all four of them. extra outputs additional debug messages for parser and/or traversal when they're also included. And there are no debug messages for exec โ it's giving "all dressed up with nowhere to go" ๐
โ ๏ธ ok important one: debug messages are not sanitized against terminal escape injection ๐ญ so it is safest to direct debug output (i.e., stderr) to a file, like rh -? all,extra 2>rh.dbg. Do that. Please. For me. ๐
Also note: if rh has been compiled without support for debug messages, this option will still be accepted, but there will be no debug messages. So you'll get silence and vibes, nothing else ๐ซ
๐ SEARCH CRITERIA LANGUAGE ๐
Go read rawhide.conf(5) for the details on the search criteria language used in system-wide and user-specific configuration files, -f option files, and -e option arguments. It also has details on the standard library that builds on the language and makes rh easy to use. Now is genuinely a good time to read it, bestie โ the rest of this manual entry should make way more sense after. But here's a lil intro anyway ๐
โจ expressions and functions โจ
There are expressions and functions. Expressions look like C expressions. The only data type is integer, that's it, that's the whole vibe. These C operators are available (presented in groups of increasing precedence):
?: Conditional (i.e., condition-expr ? if-expr : else-expr)
|| Logical or
&& Logical and
| Bit or
^ Bit exclusive or
& Bit and
== Equals
!= Not equals
< Less than
> Greater than
<= Less than or equal to
>= Greater than or equal to
<< Bit shift left
>> Bit shift right
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulo (remainder)
- Minus (unary)
~ Bit not (unary)
! Logical not (unary)
Parentheses override operator precedence (e.g., (1 + 2) * 3) โ classic, iconic, does what it says ๐
Integer constants can be decimal, octal (starting with 0), or hexadecimal (starting with 0x). Decimal integers can have scale units (e.g., 1K, 2M, 3G, ... for traditional storage units (KiB, MiB, GiB, ...), and 1k, 2m, 3g, ... for SI-style units (KB, MB, GB, ...)) โ yes the capital/lowercase thing matters, don't come for me ๐ซถ
๐ special tokens ๐
There are special tokens that represent various things:
"pattern" - file glob pattern matches
"pattern".modifier - modified pattern matches
"/path".field - reference files for comparison
"cmd".sh - external shell commands
$user @group - user and group IDs
$$ @@ - current user's user ID and primary group ID
[yyyy/mm/dd] - dates
[yyyy/mm/dd hh:mm:ss] - date/times
See rawhide.conf(5) for all the details โ I cannot stress this enough ๐
๐ง functions, bodies, and files ๐ง
Functions can have parameters. Functions that don't have parameters can be defined and called with or without parentheses. Function bodies can only contain a return statement or an expression โ that's the constraint, no extra drama allowed.
Every source/configuration file, -f option file, and -e option argument can contain zero or more function definitions, optionally followed by a file test expression, which is optionally terminated by a semicolon (;) โจ
๐ built-in symbols ๐
There are built-in symbols that represent the inode metadata (i.e., stat(2) structure fields) of candidate files (e.g., mode, uid, size, mtime, ...), Linux/BSD file attributes/flags (i.e., attr, proj, gen), other useful file information (e.g., nouser, nogroup, readable, writable, ...), control flow (i.e., prune, trim, exit), useful values and constants (e.g., now, today, minute, hour, day, ...), and more constants from C's <sys/stat.h> header file (e.g., IFMT, IFDIR, IRUSR, ...). There are also built-in symbols that represent the inode metadata of candidate symlink targets (e.g., tmode, tuid, tsize, tmtime, ...). So many symbols, all of them serving ๐
๐ the standard library ๐
There's also a standard library of functions in /etc/rawhide.conf (or similar). It contains both readable and concise functions for all sorts of things, like:
- file types (e.g.,
file,f,directory,dir,d,symlink,link,l, ...) - file permissions (e.g.,
user_readable,ur,group_writable,gw,other_executable,ox,setuid,suid,all_readable,allr,all(),any(),none(), ...) - aliases for
stat(2)structure fields and other built-ins (e.g.,inode,user,group,modified,accessed,imayread,ir,imaywrite,iw, ...) - size units (e.g.,
KiB,MiB,GiB,KB,MB,GB, ...) - miscellaneous helper functions (e.g.,
ago(),old(),past(),empty,roots,mine,broken,gmtoday, ...)
On Linux systems, there's an additional library of functions in /etc/rawhide.conf.d/attributes. It contains constants and predicates for ext2-style file attributes (e.g., immutable, append, nodump, nocow, dax, ...) ๐ฅ
On FreeBSD, OpenBSD, NetBSD, and macOS systems, there's an additional library of functions in /etc/rawhide.conf.d/attributes. It contains constants and predicates for BSD file flags (e.g., uimmutable, uappend, nodump, opaque, ...) ๐ฆ
๐ string literals and modifiers ๐
By default, string literals ("pattern") represent a file glob pattern match against the file name. Pattern modifiers ("pattern".modifier) change how string literals get interpreted, so you can choose how to match text (i.e., glob pattern or Perl-compatible regular expression (regex), and case-sensitive or case-insensitive), and which text to match against (i.e., file name, path, symlink target path, access control list, or extended attributes). So many options, all of them valid โจ
There are other string literal suffixes that represent the inode metadata (i.e., stat(2) structure fields) of arbitrary reference files ("/path".field) for comparison purposes. And the sh string literal suffix lets you execute an arbitrary shell command ("cmd".sh) and use its exit success status in the search criteria โ iconic behaviour honestly ๐
See rawhide.conf(5) for all the details. And see below for some examples ๐
โจ EXPRESSION EXAMPLES โจ
Okay bestie, these are examples of rh expressions ๐
. When multiple versions show up, the first one only uses built-in symbols, and the rest usually pull in the standard library from /etc/rawhide.conf (or something similar) too. Peep rawhide.conf(5) for the details ๐ซถ.
Find files owned by the user drew that are writable by other people ๐ธ
(uid == $drew) && (mode & 022) # uid and mode are built-in
(uid == $drew) && (gw | ow) # gw and ow are in /etc/rawhide.conf
Find files owned by root, with the setuid bit set, and world-writable ๐
!uid && (mode & ISUID) && (mode & 02) # uid, mode, ISUID: built-in
roots && setuid && other_writable # the rest: /etc/rawhide.conf
roots && setuid && world_writable
roots && suid && ow
roots && suid && ww
Find executable files bigger than 10KiB that havenโt been executed in the last 24 hours ๐ฐ๏ธ
(mode & 0111) && (size > 10 * 1024) && (atime < now - 24 * hour)
any(0111) && (size > 10 * KiB) && accessed < ago(24 * hours)
anyx && sz > 10K && atime < ago(day)
Find C source files smaller than 4KiB, and other files smaller than 32KiB ๐ง
size < ("*.c" ? 4K : 32K) # size: built-in
size < ("*.c" ? 4 : 32) * KiB # KiB: /etc/rawhide.conf
Find files that are an exact multiple of 1KiB in size ๐
(size % 1024) == 0
!(sz % 1K)
Find files last modified during March, 1982 ๐
mtime >= [1982/3/1] && mtime < [1982/4/1]
modified >= [1982/3/1] && modified < [1982/4/1]
Find files that have been read since they were last written ๐
atime > mtime
accessed > modified
Find files whose names are between 4 and 10 bytes long ๐
strlen >= 4 && strlen <= 10
len >= 4 && len <= 10
Find files at a relative depth of 3 or more below the starting search directory ๐ฆ
depth >= 3
This next one finds *.c files, but it will NOT search inside directories named bin or tmp. If those file names get encountered, the prune built-in is evaluated, so the current path doesnโt match and further searching below the current path is prevented โจ.
("tmp" || "bin") ? prune : "*.c"
("tmp" || "bin") && prune || "*.c"
Find files modified after another file was last modified โฑ๏ธ
mtime > "/otherfile".mtime
modified > "/otherfile".modified
Find files larger than one file and smaller than another file ๐
size > "/somefile".size && size < "/otherfile".size
sz > "/somefile".sz && sz < "/otherfile".sz
Find files with holes โ for filesystems without transparent compression ๐ณ๏ธ
(mode & IFMT) == IFREG && size && blocks && (blocks * 512) < size
file && size && blocks && space < size
Find regular files with multiple hard links ๐
(mode & IFMT) == IFREG && nlink > 1
file && nlinks > 1
f && nlink > 1
Find all hard links to a particular file ๐ซถ
(dev == "/path".dev) && (ino == "/path".ino)
(dev == "/path".dev) && (ino == "".ino) # Implicit 2nd reference
Find devices with the same device driver as /dev/tty ๐ป
rmajor == "/dev/tty".rmajor
Find symlinks whose target paths are relative ๐งต
"[!/]*".link
Find symlinks whose ultimate targets are on a different filesystem ๐
(mode & IFMT) == IFLNK && texists && tdev != dev
symlink && target_exists && target_dev != dev
l && texists && tdev != dev
texists && tdev != dev
Find symlinks whose ultimate targets donโt exist ๐
(mode & IFMT) == IFLNK && !texists
symlink && !target_exists
link && !texists
l && !texists
dangling
broken
Find mountpoints under the current directory โฐ๏ธ
$ rh -1 'dev != ".".dev'
Find directories with no sub-directories โ fast for most filesystems, but not btrfs โก
$ rh 'd && nlink == 2'
Same idea, but it works for btrfs too. Itโs slow-ish, but it demonstrates shell commands ๐ญ
$ rh 'd && "[ `rh -red %S | wc -l` = 0 ]".sh'
$ rh 'd && "[ -z \"`rh -red %S`\" ]".sh'
Find empty readable directories โ fast-ish, and works for btrfs ๐งบ
$ rh 'd && empty'
Find symlinks whose immediate targets are also symlinks ๐ช
$ rh -l 'l && "[ -L \"`rh -L%%l %S`\" ]".sh'
$ rh -l 'l && "[ -L \"`readlink %S`\" ]".sh'
Find all hard links to all regular files that have multiple hard links โ very slow ๐
# rh -e 'f && nlink > 1' \
-X 'rh / "(dev == \"%S\".dev) && (ino == \"\".ino)"; echo' \
/
Same thing, but for a single filesystem only. Shorter, less slow, but still very slow ๐ซ
# rh -1 -e 'f && nlink > 1' -X 'rh -1 / "ino == \"%S\".ino"; echo' /
Find 32-bit ELF executables ๐ง
$ rh 'f && anyx && sz > 10k && "ELF 32-bit*executable*".what'
Find text files with ISO-8859 encoding ๐
$ rh 'f && "*ISO-8859 text".what'
$ rh 'f && "text/*; charset=iso-8859*".mime'
Find files that contain TODO ๐
$ rh 'f && "*TODO*".body'
$ rh 'f && "TODO".rebody'
Find files using a Perl-compatible regular expression (regex) ๐ช
$ rh '"^[a-zA-Z0-9_]+[0-9][0-9][0-9]?\..*[a-cz]$".re'
$ rh '"^\w+\d{2,3}\..*[a-cz]$".re'
See perlre(1), pcre2pattern(3), and pcre2syntax(3) for the deets โจ.
Same regex energy, but with documentation built in ๐ฌ
$ rh '"
^ # Anchor the match to the start of the base name
\w+ # Starts with at least one word character
\d{2,3} # Followed by two or three digits
\. # Followed by a literal dot
.* # Followed by anything (or nothing)
[ a-c z ] # Ends with a, b, c, or z
$ # Anchor the match to the end of the base name
".re'
Case-insensitive search โ anything with abc in the name ๐
$ rh '"*ABC*".i' # Case-insensitive glob of base name
$ rh '"ABC".rei' # Case-insensitive regex of base name
Find files by their full path starting from the search directory โ anything under an abc directory ๐บ๏ธ
$ rh '"*/abc/*".path' # Glob of full path
$ rh '"/abc/".repath' # Regex of full path
$ rh '"*/ABC/*".ipath' # Case-insensitive glob of full path
$ rh '"/ABC/".reipath' # Case-insensitive regex of full path
Find symlinks by their target path โ symlinks to anything under an abc directory ๐ฏ
$ rh -l '"*/abc/*".link' # Glob of symlink target path
$ rh -l '"/abc/".relink' # Regex of symlink target path
$ rh -l '"*/ABC/*".ilink' # Case-insensitive glob of symlink target
$ rh -l '"/ABC/".reilink' # Case-insensitive regex of symlink target
Find files with "POSIX" ACLs (Linux and Cygwin) that grant write access to the user drew ๐ง
$ rh '(uid == $drew) ? "*user::?w?*".acl : "*user:drew:?w?*".acl'
$ rh '(uid == $drew) ? "^user::.w.$".reacl : "^user:drew:.w.$".reacl'
Find files with NFSv4 ACLs (FreeBSD and Solaris) that grant write access to the user drew โ๏ธ
$ rh '(uid == $drew)
? "*owner@:?w????????????:???????:allow*".acl
: "*user:drew:?w????????????:???????:allow*".acl
'
$ rh '(uid == $drew)
? "owner@:.w.{12}:.{7}:allow".reacl
: "user:drew:.w.{12}:.{7}:allow".reacl
'
$ rh '(uid == $drew)
? "owner@:[^:]+/write_data/[^:]+(:[^:]*)?:allow".reacl
: "user:drew:[^:]+/write_data/[^:]+(:[^:]*)?:allow".reacl
'
โ ๏ธ NFSv4 ACL tea: you can search for ACLs using either the compact form or the non-compact form. But be warned โ the permission names in the non-compact form do not always appear in the same order (at least on Solaris). So donโt come for me if theyโre shuffled ๐.
Find files on macOS with ACLs that grant write access to the user drew ๐
$ rh '(uid == $drew) ? uw : "user:[^:]+:drew:\d+:allow:write".reacl'
Find files with non-trivial access control lists (ACL) ๐
$ rh '"*mask::*".acl' # "POSIX" ACLs (Linux, Cygwin)
$ rh '"(user|group):".reacl' # NFSv4 ACLs (FreeBSD, Solaris)
$ rh '"?*".acl' # macOS ACLs
Find files with extended attributes (EA) ๐ท๏ธ
$ rh '"?*".ea'
$ rh '".".reea'
Find files on Linux by their selinux(8) context โ any ๐ง
$ rh '"*security.selinux: *_u:*_r:*_t:s[0-3]*".ea'
$ rh '"^security\.selinux:\ .*_u:.*_r:.*_t:s[0-3]".reea'
Find files on Linux, FreeBSD, OpenBSD, NetBSD, or macOS that are immutable or append-only ๐
$ rh / 'immutable || append'
Find files on Solaris with setuid executable extended attributes โ silly ๐
$ rh / '"*/stat: -rws*".ea'
$ rh / '"/stat:\ -rws".reea'
๐งธ FUNCTION EXAMPLES ๐งธ
Now weโre defining and using functions, bestie. Itโs giving custom little helpers โจ.
This defines a function that returns true if the current candidate file is a directory, and false otherwise:
dir()
{
return (mode & IFMT) == IFDIR;
}
And this one returns whether or not the current candidate file is owned by the current user:
mine()
{
return uid == $$;
}
Then this expression matches directories that are not owned by the user:
dir() && !mine();
Since dir and mine take no arguments, they can be called without parentheses:
dir && !mine;
You can also omit parentheses when defining a function that has no parameters. For example, this defines a function named drews that returns true when the current candidate file is owned by the user drew:
drews
{
return uid == $drew;
}
Functions can have parameters too, obviously ๐
. An alternative to mine and drews could be:
owner(who)
{
return uid == who;
}
Then this expression would be true for any file owned by the users alex or drew:
owner($alex) || owner($drew);
Since functions can only ever contain a return statement, the return keyword and the trailing semicolon (;) are optional.
So the above functions can be defined as:
dir { (mode & IFMT) == IFDIR }
mine { uid == $$ }
drews { uid == $drew }
owner(who) { uid == who }
๐ COMMAND LINE EXAMPLES ๐
okay bestie so here's the thing โ the -e option argument is usually the way you hand over the file test expression. but plot twist: you almost never actually need to type the -e itself ๐
. if no explicit file test expression comes in through the -e option, then any remaining command line arguments get examined to identify any implicit file test expression. and the file test expression and search paths? they can show up in any order, no rules. so these four are literally identical, no cap:
$ rh -e 'expr' dir1 dir2
$ rh 'expr' dir1 dir2
$ rh dir1 'expr' dir2
$ rh dir1 dir2 'expr'
โจ the actual example buffet โจ
- List the current directory in detail (like
ls -lA, but unsorted):
$ rh -rl
- List the current directory in greater detail (all
stat(2)details, and all type indicators) โ it's giving thorough:
$ rh -rvF
- List the current directory in detail, sorted by name (by cheating ๐):
$ rh -lM0 .* *
- Delete old backup files โ bye bye boomer files ๐:
$ rh -UUU '"*.bak" && modified <= ago(month)'
$ rh -UUU '"*.bak" && old(month)'
grep(1)for something only in recent files, iconic behaviour:
$ rh -e 'f && modified >= ago(hour)' -x 'grep -H something %s'
$ rh -e 'f && past(hour)' -x 'grep -H something %s'
- The same, but just list the files where
grep(1)found something (way chiller):
$ rh 'f && modified >= ago(hour) && "grep -q something %S".sh'
$ rh 'f && past(hour) && "grep -q something %S".sh'
- Show all access control lists:
$ rh -L '%p\n%z\n' '"?*".acl'
- Show all extended attributes:
$ rh -L '%p\n%x\n' '"?*".ea'
- Find the block device that the current directory resides on ๐:
$ rh -l /dev 'b && rdev == ".".dev'
heads up though โ this doesn't work for filesystems likedevtmpfsthat don't appear in/dev. sad but true ๐ญ
- Find regular files whose sizes are prime numbers (so silly, i'm obsessed):
$ rh -l '
prime1(n, i) { (i * i > n) ? 1 : !(n % i) ? 0 : prime1(n, i + 2) }
prime(n) { (n < 2) ? 0 : !(n % 2) ? n == 2 : prime1(n, 3) }
file && prime(size)
'
- Sum the sizes of all regular files in the current directory (with
jq(1)) โ three routes, same destination:
$ rh -r -L '%j\n' f | jq .size | jq -s add
$ rh -r -L '%s\n' f | jq -s add
$ rh -rj f | jq .size | jq -s add
๐ some command line shell syntactic sugar to save keystrokes ๐
rq โ rh with automatic "" around the first argument usage: rq pattern [options] [path...] e.g.: rq '*.c' instead of rh '"*.c"'
rql โ rh -l with automatic "" around the first argument usage: rql pattern [options] [path...] e.g.: rql '*.c' instead of rh -l '"*.c"'
rqv โ rh -v with automatic "" around the first argument usage: rqv pattern [options] [path...] e.g.: rqv '*.c' instead of rh -v '"*.c"'
ri โ rh with automatic "".i around the first argument usage: ri pattern [options] [path...] e.g.: ri '*.c' instead of rh '"*.c".i'
ril โ rh -l with automatic "".i around the first argument usage: ril pattern [options] [path...] e.g.: ril '*.c' instead of rh -l '"*.c".i'
riv โ rh -v with automatic "".i around the first argument usage: riv pattern [options] [path...] e.g.: riv '*.c' instead of rh -v '"*.c".i'
re โ rh with automatic "".re around the first argument usage: re pattern [options] [path...] e.g.: re '\.c$' instead of rh '"\.c$".re'
rel โ rh -l with automatic "".re around the first argument usage: rel pattern [options] [path...] e.g.: rel '\.c$' instead of rh -l '"\.c$".re'
rev โ rh -v with automatic "".re around the first argument usage: rev pattern [options] [path...] e.g.: rev '\.c$' instead of rh -v '"\.c$".re'
rei โ rh with automatic "".rei around the first argument usage: rei pattern [options] [path...] e.g.: rei '\.c$' instead of rh '"\.c$".rei'
reil โ rh -l with automatic "".rei around the first argument usage: reil pattern [options] [path...] e.g.: reil '\.c$' instead of rh -l '"\.c$".rei'
reiv โ rh -v with automatic "".rei around the first argument usage: reiv pattern [options] [path...] e.g.: reiv '\.c$' instead of rh -v '"\.c$".rei'
copy-paste the whole starter pack, bestie ๐:
# rq - rh with automatic "" around the first argument
# usage: rq pattern [options] [path...]
# e.g.: rq '*.c' instead of rh '"*.c"'
rq() { rq_pat="$1"; shift && rh -e "\"$rq_pat\"" "$@"; }
# rql - rh -l with automatic "" around the first argument
# usage: rql pattern [options] [path...]
# e.g.: rql '*.c' instead of rh -l '"*.c"'
rql() { rql_pat="$1"; shift && rh -le "\"$rql_pat\"" "$@"; }
# rqv - rh -v with automatic "" around the first argument
# usage: rqv pattern [options] [path...]
# e.g.: rqv '*.c' instead of rh -v '"*.c"'
rqv() { rqv_pat="$1"; shift && rh -ve "\"$rqv_pat\"" "$@"; }
# ri - rh with automatic "".i around the first argument
# usage: ri pattern [options] [path...]
# e.g.: ri '*.c' instead of rh '"*.c".i'
ri() { ri_pat="$1"; shift && rh -e "\"$ri_pat\".i" "$@"; }
# ril - rh -l with automatic "".i around the first argument
# usage: ril pattern [options] [path...]
# e.g.: ril '*.c' instead of rh -l '"*.c".i'
ril() { ril_pat="$1"; shift && rh -le "\"$ril_pat\".i" "$@"; }
# riv - rh -v with automatic "".i around the first argument
# usage: riv pattern [options] [path...]
# e.g.: riv '*.c' instead of rh -v '"*.c".i'
riv() { riv_pat="$1"; shift && rh -ve "\"$riv_pat\".i" "$@"; }
# re - rh with automatic "".re around the first argument
# usage: re pattern [options] [path...]
# e.g.: re '\.c$' instead of rh '"\.c$".re'
re() { re_pat="$1"; shift && rh -e "\"$re_pat\".re" "$@"; }
# rel - rh -l with automatic "".re around the first argument
# usage: rel pattern [options] [path...]
# e.g.: rel '\.c$' instead of rh -l '"\.c$".re'
rel() { rel_pat="$1"; shift && rh -le "\"$rel_pat\".re" "$@"; }
# rev - rh -v with automatic "".re around the first argument
# usage: rev pattern [options] [path...]
# e.g.: rev '\.c$' instead of rh -v '"\.c$".re'
rev() { rev_pat="$1"; shift && rh -ve "\"$rev_pat\".re" "$@"; }
# rei - rh with automatic "".rei around the first argument
# usage: rei pattern [options] [path...]
# e.g.: rei '\.c$' instead of rh '"\.c$".rei'
rei() { rei_pat="$1"; shift && rh -e "\"$rei_pat\".rei" "$@"; }
# reil - rh -l with automatic "".rei around the first argument
# usage: reil pattern [options] [path...]
# e.g.: reil '\.c$' instead of rh -l '"\.c$".rei'
reil() { reil_pat="$1"; shift && rh -le "\"$reil_pat\".rei" "$@"; }
# reiv - rh -v with automatic "".rei around the first argument
# usage: reiv pattern [options] [path...]
# e.g.: reiv '\.c$' instead of rh -v '"\.c$".rei'
reiv() { reiv_pat="$1"; shift && rh -ve "\"$reiv_pat\".rei" "$@"; }
alias rl='rh -rl' # rh -l version of ls -lA (unsorted)
alias rlr='rh -l' # rh -l version of ls -lAR (unsorted)
alias rv='rh -rv' # rh -v version of ls -lA (unsorted)
alias rvr='rh -v' # rh -v version of ls -lAR (unsorted)
alias rj='rh -j'
alias r0='rh -0'
alias r1='rh -1'
alias r1l='rh -1l'
alias r1v='rh -1v'
alias ry='rh -y'
alias ryl='rh -yl'
alias ryv='rh -yv'
alias rY='rh -Y'
alias rYl='rh -Yl'
alias rYv='rh -Yv'
# jqs - (helper) use jq to sort rh -j by path
# usage: jq arguments that don't conflict with -s
# e.g.: rh -j | jqs -r
jqs() { jq -s "$@" 'sort_by(.path) | .[].path'; }
# jqt - (helper) use jq to sort rh -j by mtime, most recent first
# usage: jq arguments that don't conflict with -s
# e.g.: rh -j | jqt -r
jqt() { jq -s "$@" 'sort_by(.mtime) | reverse | .[].path'; }
# jqz - (helper) use jq to sort rh -j by size
# usage: jq arguments that don't conflict with -s
# e.g.: rh -j | jqz -r
jqz() { jq -s "$@" 'sort_by(.size) | .[].path'; }
# rhs - plain rh sorted by path (like ls -1AR)
# usage: rh arguments that don't conflict with -j
# e.g.: rhs f
rhs() { rh -j "$@" | jqs -r; }
# rht - plain rh sorted by mtime, most recent first (like ls -1ARt)
# usage: rh arguments that don't conflict with -j
# e.g.: rht f
rht() { rh -j "$@" | jqt -r; }
# rhz - plain rh sorted by size (like ls -1AR but sorted by size)
# usage: rh arguments that don't conflict with -j
# e.g.: rhz 'size > 1M'
rhz() { rh -j "$@" | jqz -r; }
# rls - rh -rl sorted by path (like ls -lA)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rls f
rls() { eval rh -lM0 `rh -rj "$@" | jqs`; }
# rlt - rh -rl sorted by mtime, most recent first (like ls -lAt)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rlt f
rlt() { eval rh -lM0 `rh -rj "$@" | jqt`; }
# rlz - rh -rl sorted by size (like ls -lA but sorted by size)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rlz 'size > 1M'
rlz() { eval rh -lM0 `rh -rj "$@" | jqz`; }
# rlrs - rh -l sorted by path (like ls -lAR)
# usage: rh arguments that don't conflict with -j
# e.g.: rlrs f
rlrs() { eval rh -lM0 `rh -j "$@" | jqs`; }
# rlrt - rh -l sorted by mtime, most recent first (like ls -lARt)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rlrt f
rlrt() { eval rh -lM0 `rh -j "$@" | jqt`; }
# rlrz - rh -l sorted by size (like ls -lAR but sorted by size)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rlrz 'size > 1M'
rlrz() { eval rh -lM0 `rh -j "$@" | jqz`; }
# rvs - rh -rv sorted by path (like ls -lA)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rvs f
rvs() { eval rh -vM0 `rh -rj "$@" | jqs`; }
# rvt - rh -rv sorted by mtime, most recent first (like ls -lAt)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rvt f
rvt() { eval rh -vM0 `rh -rj "$@" | jqt`; }
# rvz - rh -rv sorted by size (like ls -lA but sorted by size)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rvz 'size > 1M'
rvz() { eval rh -vM0 `rh -rj "$@" | jqz`; }
# rvrs - rh -v sorted by path (like ls -lAR)
# usage: rh arguments that don't conflict with -j
# e.g.: rvrs f
rvrs() { eval rh -vM0 `rh -j "$@" | jqs`; }
# rvrt - rh -v sorted by mtime, most recent first (like ls -lARt)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rvrt f
rvrt() { eval rh -vM0 `rh -j "$@" | jqt`; }
# rvrz - rh -v sorted by size (like ls -lAR but sorted by size)
# usage: rh arguments that don't conflict with -r or -j
# e.g.: rvrz 'size > 1M'
rvrz() { eval rh -vM0 `rh -j "$@" | jqz`; }
like, that's an entire personality in a shell profile fr fr ๐ฆ
๐ FIND(1) COMPARISON EXAMPLES ๐
ok bestie, these next bits are the examples from the GNU find(1) manual entry. iconic behavior, honestly.
find - search for files in a directory hierarchy
Copyright (C) 1990-2022 Free Software Foundation, Inc
License GPLv3+: GNU GPL version 3 or later
https://www.gnu.org/software/findutils
https://www.gnu.org/licenses/gpl.html
Every example is followed by one or more equivalent rh commands, just for comparison purposes. Multiple alternative rh commands typically use different functions from /etc/rawhide.conf (or similar). Check rawhide.conf(5) for the deets.
๐ธ Simple find | xargs approach
Find files named core in or below the directory /tmp and delete them.
$ find /tmp -name core -type f -print | xargs /bin/rm -f
$ rh /tmp '"core" && file' | xargs /bin/rm -f
$ rh /tmp '"core" && f' | xargs /bin/rm -f
๐ฆ Safer find -print0 | xargs -0 approach
Find files named core in or below the directory /tmp and delete them, processing file names in such a way that file or directory names containing single or double quotes, spaces or newlines are correctly handled. yes, this is the glow-up version ๐
$ find /tmp -name core -type f -print0 | xargs -0 /bin/rm -f
$ rh -0 /tmp '"core" && file' | xargs -0 /bin/rm -f
$ rh -0 /tmp '"core" && f' | xargs -0 /bin/rm -f
๐ Executing a command for each file
Run file(1) on every file in or below the current directory.
$ find . -type f -exec file '{}' \;
$ rh -x 'file %s' file
$ rh -x 'file %s' f
โจ Traversing the filesystem just once - for two different actions
Traverse the filesystem just once, listing set-user-ID files and directories into /root/suid.txt and large files into /root/big.txt.
$ find / \
\( -perm -4000 -fprintf /root/suid.txt '%#m %u %p\n' \) , \
\( -size +100M -fprintf /root/big.txt '%-10s %p\n' \)
# rh -L '' / '
(setuid && "rh -M0 -L \"%%#m %%u %%p\n\" %s >> /root/suid.txt".sh) +
(size > 100M && "rh -M0 -L \"%%-10s %%p\n\" %s >> /root/big.txt".sh)
'
๐ง Searching files by age
Search for files in your home directory which have been modified in the last twenty-four hours.
$ find $HOME -mtime 0
$ rh $HOME 'mtime >= now - 24 * hour'
$ rh $HOME 'mtime >= ago(24 * hours)'
$ rh $HOME 'modified >= ago(day)'
$ rh $HOME 'past(day)'
๐ฅ Searching files by permissions
Search for files which are executable but not readable for the current user.
$ find /sbin /usr/sbin -executable \! -readable -print
$ rh /sbin /usr/sbin 'executable && !readable'
$ rh /sbin /usr/sbin 'imayexec && !imayread'
$ rh /sbin /usr/sbin 'ix && !ir'
Search for files which have read and write permission for their owner, and group, but which other users can read but not write to. Files which meet these criteria but have other permission bits set (for example if someone can execute the file) will not be matched.
$ find . -perm 664
$ rh 'perm == 0664'
Search for files which have read and write permission for their owner and group, and which other users can read, without regard to the presence of any extra permission bits (for example the executable bit). This will match a file which has mode 0777, for example.
$ find . -perm -664
$ rh '(perm & 0664) == 0664'
$ rh 'all(0664)'
Search for files which are writable by somebody (their owner, or their group, or anybody else).
$ find . -perm /222
$ rh 'perm & 0222'
$ rh 'any(0222)'
$ rh 'user_writable || group_writable || other_writable'
$ rh 'uw || gw || ow'
$ rh 'uw | gw | ow'
$ rh uw+gw+ow
$ rh any_writable
$ rh anyw
Search for files which are writable by either their owner or their group.
$ find . -perm /220
$ find . -perm /u+w,g+w
$ find . -perm /u=w,g=w
$ rh 'perm & 0220'
$ rh 'any(0220)'
$ rh 'user_writable || group_writable'
$ rh 'uw || gw'
$ rh 'uw | gw'
$ rh uw+gw
Search for files which are writable by both their owner and their group.
$ find . -perm -220
$ find . -perm -g+w,u+w
$ rh '(perm & 0220) == 0220'
$ rh 'all(0220)'
$ rh 'uw && gw'
A more elaborate search on permissions. These two commands both search for files that are readable for everybody (-perm -444 or -perm -a+r), have at least one write bit set (-perm /222 or -perm /a+w) but are not executable for anybody (! -perm /111 or ! -perm /a+x respectively).
$ find . -perm -444 -perm /222 \! -perm /111
$ find . -perm -a+r -perm /a+w \! -perm /a+x
$ rh '(perm & 0444) == 0444 && (perm & 0222) && !(perm & 0111)'
$ rh 'all(0444) && any(0222) && none(0111)'
$ rh '(ur && gr && or) && (uw || gw || ow) && !(ux || gx || ox)'
$ rh 'all_readable && any_writable && none_executable'
$ rh 'allr && anyw && nonex'
๐บ Pruning - omitting files and subdirectories
Copy the contents of /source-dir to /dest-dir, but omit files and directories named .snapshot (and anything in them). It also omits files or directories whose names end in "~", but not their contents.
$ cd /source-dir
$ find . -name .snapshot -prune -o \( \! -name '*~' -print0 \) | \
cpio -pmd0 /dest-dir
$ rh -0 '".snapshot" ? prune : !"*~"' | cpio -pmd0 /dest-dir
$ rh -0 '".snapshot" && prune || !"*~"' | cpio -pmd0 /dest-dir
Given the following directory of projects and their associated SCM administrative directories, perform an efficient search for the projects' roots:
$ find repo/ \
\( -exec test -d '{}/.svn' \; \
-or -exec test -d '{}/.git' \; \
-or -exec test -d '{}/CVS' \; \
\) -print -prune
$ rh repo 'd && "[ -d %S/.svn -o -e %S/.git -o -d %S/CVS ]".sh && trim'
Sample directories:
repo/project1/CVS
repo/gnu/project2/.svn
repo/gnu/project3/.svn
repo/gnu/project3/src/.svn
repo/project4/.git
Sample output:
repo/project1
repo/gnu/project2
repo/gnu/project3
repo/project4
Note: These examples highlight an interesting difference in pruning with rh and find(1). In the first example, the pruned paths themselves are not output. In the second example, they are. Both behaviours are useful. find(1) has a single -prune action for both, and the decision whether or not to output the pruned path itself is determined by whether and where -print (or certain other actions) appears on the command line. It's complicated. For simplicity, rh has separate prune and trim built-ins for these two behaviours. prune prevents the current candidate path from matching. trim doesn't. They both prevent searching below the current candidate path. So prune is used when the current candidate path itself needs to be excluded, and trim is used when it needs to be included. You can think of trim as a light prune. lowkey poetic tbh ๐ซถ
๐ Other useful examples
Search for several file types.
$ find /tmp \( -type f -o -type d -o -type l \)
$ find /tmp -type f,d,l
$ rh /tmp 'file || dir || link'
$ rh /tmp 'f || d || l'
$ rh /tmp 'f | d | l'
$ rh /tmp f+d+l
Search for files with the particular name needle and stop immediately when we find the first one.
$ find / -name needle -print -quit
$ rh / '"needle" ? exit : 0'
$ rh / '"needle" && exit'
$ rh / '"needle" && quit'
Demonstrate the interpretation of the %f and %h format directives of the -printf action for some corner cases. Here is an example including some output.
$ find . .. / /tmp /tmp/TRACE compile compile/64/tests/find \
-maxdepth 0 -printf '[%h][%f]\n'
$ rh -M0 -L '[%h][%f]\n' \
. .. / /tmp /tmp/TRACE compile compile/64/tests/find
Sample output:
[.][.]
[.][..]
[][/]
[][tmp]
[/tmp][TRACE]
[.][compile]
[compile/64/tests][find]
corner-case representation, served fresh โจ๐
CAVEAT ๐ (read this, bestie, fr fr)
okay so first things first: don't expect too much from the search criteria language. It is a very little language. It's giving "small but mighty," not "full programming language," yk? ๐ซถ
A function can only be called if its definition has already been encountered by the parser. So recursive functions are possible, but mutually recursive functions are not. That's just how the parser rolls. ๐
Function parameters (temporarily) share the same namespace as the functions themselves. This means that function parameter names can't be the same as the names of any existing functions or built-in symbols. Name collision = not the vibe. ๐
Locale support is peculiar. Like, genuinely quirky. The only supported locales are those that use UTF-8 or an ASCII-compatible single-byte character encoding like ISO-8859-* (but the -L %j (JSON) conversion is only supported with UTF-8 locales). All non-ASCII characters are considered to be "letters" when parsing the names of functions, parameters, users, and groups. This means that all languages and scripts, and even emojis, can be used in names ๐ญโจ but non-ASCII digits and numbers in other scripts cannot be used in numeric constants. Other multi-byte character encodings are not supported (e.g., UTF-16, GB 18030, Big5, Shift JIS, and EUC-*). This limitation lets rh enjoy most of the benefits of Unicode without needing to expend any time or energy decoding and encoding characters. Iconic tradeoff tbh. ๐
If the user's $PATH environment variable includes the current working directory, or any other non-absolute paths, they are automatically removed. This is important for security when the -X option is used, and when the "cmd".sh "pattern" modifier is used. But it isn't important when they are not used. But it always happens anyway, for consistency. So this affects the -x option as well, whether or not the "cmd".sh "pattern" modifier is also used. This ensures that a change in the search criteria expression won't inadvertently change the behaviour of the -x option command. Consistency slay, basically. ๐ฅ
When following symlinks with the -Y option, it's possible to encounter filesystem cycles. When this happens, rh will output an "error" message to indicate that it is skipping an already encountered directory because of the filesystem cycle, but this won't result in a non-zero exit status, because it's not really an error. If you would prefer that filesystem cycle detection not be reported at all, set the environment variable RAWHIDE_DONT_REPORT_CYCLES=1.
When using the -l option to output multiple columns of extra information for matching entries, the ideal width of each column is not known at the start. Small default widths are used, and columns are widened as necessary. This results in less than perfectly tidy columns. This is the result of wanting to use as little memory as possible, and wanting to avoid columns that look too wide. If you prefer columns that start already wide enough, and you know how wide they need to be, you can override the default initial column widths by setting environment variables whose names start with RAWHIDE_COLUMN_WIDTH_. See the ENVIRONMENT section below for details. ๐
Invalid values in environment variables (see below) are silently ignored. No drama, no warning, justโฆ ignored. ๐
The spaces for the virtual code, data, and stack have fixed sizes. So they could conceivably run out. But it would take megabytes of search criteria source code, or over ten thousand patterns, or hundreds of thousands of nested function calls. These thresholds are reduced if rh is configured with a small or tiny static size at compile time, but it would still be unlikely to be a problem. Like, you'd have to try, bestie. ๐ญ
If you have a pathologically deep directory tree (i.e., thousands of directories deep), you might want to rethink that, or you might need to increase the limit on the number of open files, with something like:
ulimit -n 2048
This is because an open file descriptor is required for each directory level. Respect the file descriptors, they're doing their best. ๐
Filesystems can be mounted with options such as noatime, nodiratime, and relatime, which suppress or limit the updating of accessed times (to improve read performance). The ro mount option also suppresses updates. See mount(8) for details. On Linux, the relatime mount option is the default. The altered semantics affects the atime and tatime built-in symbols, and the atime reference file field (see rawhide.conf(5)).
A file's inode changed time (or status changed time) is not updated when its accessed time is changed. It is only updated for other changes to the inode. This relates to the ctime and tctime built-in symbols, and the ctime reference file field (see rawhide.conf(5)). The way these timestamps just don't gossip to each otherโฆ iconic behaviour honestly. ๐
EXIT STATUS ๐ฆโจ
rh's exit status is zero upon success, or non-zero upon failure. Simple as that.
Possible reasons for failure are, and I'm listing them all because you deserve the full tea: โ
- invalid command line options or arguments
- search criteria syntax errors
- permission/existence errors while searching
- permission/existence errors while unlinking
-xor-Xcommands exiting with a non-zero exit status- failure to change the current working directory
- attempt to use a
stat(2)structure field of a reference file that does not exist or cannot be reached - failure to follow a symlink (but not by default)
- failure to obtain an access control list
- failure to obtain extended attributes
- traversing too deeply
- a starting search path being too long for its filesystem
- failure to allocate memory
-x,-X, or"cmd".shcommands being too large- attempt to divide by zero
- too much code
- too much data (i.e., patterns, reference file paths, and shell commands)
- stack overflow
That's the whole mood board of ways things can go wrong. ๐
ENVIRONMENT ๐๐ธ
The location of the main system-wide configuration file (/etc/rawhide.conf, or similar) can be overridden with the environment variable RAWHIDE_CONFIG. This is only available to non-root users (as it could be dangerous for root). The directory containing any additional system-wide configuration files is derived from it by appending .d.
The location of the main user-specific configuration file (~/.rhrc) can be overridden with the environment variable RAWHIDE_RC. This is only available to non-root users (as it could be dangerous for root). The directory containing any additional user-specific configuration files is derived from it by appending .d.
The following environment variables can be set to override the default initial column widths for the -l option:
RAWHIDE_COLUMN_WIDTH_DEV_MAJORโ device column (major), default1RAWHIDE_COLUMN_WIDTH_DEV_MINORโ device column (minor), default1RAWHIDE_COLUMN_WIDTH_INODEโ inode number column, default6RAWHIDE_COLUMN_WIDTH_BLKSIZEโ block size column, default1RAWHIDE_COLUMN_WIDTH_BLOCKSโ blocks column, default2RAWHIDE_COLUMN_WIDTH_SPACEโ space column, default6RAWHIDE_COLUMN_WIDTH_SPACE_UNITSโ space column (-H/-I), default4RAWHIDE_COLUMN_WIDTH_NLINKโ nlink column, default1RAWHIDE_COLUMN_WIDTH_USERโ user/owner column, default3RAWHIDE_COLUMN_WIDTH_GROUPโ group column, default3RAWHIDE_COLUMN_WIDTH_SIZEโ size column, default6RAWHIDE_COLUMN_WIDTH_SIZE_UNITSโ size column (-H/-I), default4RAWHIDE_COLUMN_WIDTH_RDEV_MAJORโ rdev column (major), default2RAWHIDE_COLUMN_WIDTH_RDEV_MINORโ rdev column (minor), default3
Their values must be integers between 1 and 99, inclusive. No funny business. ๐ข
Setting the environment variable RAWHIDE_REPORT_BROKEN_SYMLINKS=1 causes an error message (and an eventual non-zero exit status), when attempting to follow a symlink whose ultimate target does not exist or cannot be reached. By default, when following symlinks with the -y or -Y option, a broken symlink is not interpreted as an error. The broken symlink is just processed as though the -y or -Y option had not been supplied. This is done for compatibility with the familiar behaviour of find(1).
Setting the environment variable RAWHIDE_DONT_REPORT_CYCLES=1 suppresses the "error" message whenever a filesystem cycle is detected and skipped. This can happen when following symlinks with the -Y option. It's not really an error, and is just reported by default for compatibility with the familiar behaviour of find(1).
Setting the environment variable RAWHIDE_PCRE2_NOT_UTF8_DEFAULT=1 suppresses the assumption that regex patterns, and the file names, paths, symlink target paths, access control lists, and extended attributes that they match against, are encoded as UTF-8. When this environment variable is set, individual regex patterns can still enable UTF-8 interpretation with a leading (*UTF). This UTF-8 assumption is made when the locale uses UTF-8 (i.e., when the $LANG environment variable includes "UTF-8"). When the locale doesn't use UTF-8, and you want rh to assume that everything is UTF-8 anyway, set the environment variable RAWHIDE_PCRE2_UTF8_DEFAULT=1. Choose your own encoding adventure, bestie. ๐ฆ
On Solaris, setting the environment variable RAWHIDE_SOLARIS_ACL_NO_TRIVIAL=1 suppresses trivial access control lists (ACLs). By default on Solaris, ACLs are always present, even if they are trivially identical to the file permission bits. This can be convenient, but if it seems like noise, it can be silenced (but only on Solaris). This affects access control list searching (acl) (see rawhide.conf(5)), and the -L %z format conversion (see above).
On Solaris, setting the environment variable RAWHIDE_SOLARIS_EA_NO_SUNWATTR=1 suppresses the inclusion of the ubiquitous SUNWattr_ro and SUNWattr_rw extended attributes. This affects extended attribute searching (ea) (see rawhide.conf(5)), and the -L %x format conversion (see above).
On Solaris, setting the environment variable RAWHIDE_SOLARIS_EA_NO_STATINFO=1 suppresses the artificial extended attributes that are included by default to represent the stat(2) information relating to real extended attributes, which take the form of regular files in a special extended attributes directory "hidden" inside each real file. This affects extended attribute searching (ea) (see rawhide.conf(5)), and the -L %x format conversion (see above).
The environment variable RAWHIDE_EA_SIZE can be set to a positive integer value to override the default buffer size used for (encoded) extended attributes. Note that the value must be the size in bytes. Scale units are not supported. On most systems, the default buffer size is 4KiB. On Solaris, the default buffer size is 64KiB. This can be used to increase the buffer size if needed to prevent extended attributes from being silently truncated. This affects extended attribute searching (ea) (see rawhide.conf(5)), and the -L %x format conversion (see above). ๐
๐พ FILES ๐พ
okay bestie, these source/configuration files are read by default โ no cap:
/etc/rawhide.confโ main system-wide configuration/etc/rawhide.conf.d/*โ additional system-wide configuration~/.rhrcโ main user-specific configuration~/.rhrc.d/*โ additional user-specific configuration
the location of the system-wide configuration might be somewhere else depending on the operating system preferences (e.g., /usr/local/etc, /opt/local/etc, /usr/pkg/etc). periodt.
the output of rh --help literally states where the system-wide configuration files are on the local system. slay.
the location of the main system-wide configuration file (/etc/rawhide.conf, or similar) can be overridden with the environment variable RAWHIDE_CONFIG. this is only available to non-root users (as it could be dangerous for root). the directory containing any additional system-wide configuration files is derived from it by appending .d.
the location of the main user-specific configuration file (~/.rhrc) can be overridden with the environment variable RAWHIDE_RC. this is only available to non-root users (as it could be dangerous for root). the directory containing any additional user-specific configuration files is derived from it by appending .d.
๐ HISTORY ๐
on the 18th of February 1990, Ken Stauffer of the University of Calgary published the source code to Rawhide VERSION 2 on the Usenet comp.sources.unix newsgroup. it was posted as a multi-part shar(1) archive (as was the custom at the time). the author thinks a previous version must date back to 1982 or earlier.
it was a lovely alternative to find(1) that let you define your own functions for file search criteria in a mini-language inspired by C ("because most Unix users already know C"). the author remembers liking it at the time, and has often wished that it hadn't subsequently vanished into the ether. lowkey tragic.
one day, while looking through a dusty old archive tarball, the author came across the source code in their old ~/News/Comp.sources.unix file. after 32 years, it only took an hour or so to get it compiling and working again. yay! but of course, that wasn't enough. it had a bug or two, and so many security flaws (it actually was 1990, after all), and so many missing features that were needed to make it a viable modern alternative to GNU find(1) for all the author's file-finding needs. so the author spent the next month or so fixing it all up, enhancing it in many fun ways, testing it ruthlessly, and documenting it thoroughly. it now has a lean flexible command line interface, many new capabilities (and even some novel ones), and a standard library of functions to make it really pretty, and easy to use, and easy to remember. iconic behavior, honestly.
๐ง TRIVIA ๐ง
in the 7th Edition UNIX programmer's manual (1979), the find(1) manual entry has a BUGS section which just says:
The syntax is painful.
not me getting read by a 1979 manpage ๐ญ the way that quote ate.
๐ SEE ALSO ๐
iykyk, here's the whole reference crew โ every single one, bestie:
rawhide.conf(5)find(1)xargs(1)ls(1)stat(1)jq(1)stat(2)fnmatch(3)glob(7)perlre(1)pcre2pattern(3)pcre2syntax(3)system(3)sh(1)printf(3)strftime(3)ctime(3)acl(2)oracl(3)oracl(5)xattr(1)orxattr(7)orextattr(2)orfsattr(7)chmod(1)setfacl(1)getfacl(1)setfattr(1)getfattr(1)selinux(8)runat(1)mount(8)chattr(1)lsattr(1)chflags(1)file(1)- The C programming language.
๐ AUTHORS ๐
main character energy from:
- 1990 Ken Stauffer (University of Calgary)
- 2022-2023
raf <raf@raf.org>
final footer vibe check: source/version rawhide-3.3, date 20231013, section RH(1) โ bye bestie, go find some files and slay ๐
โจ๐ฆ
๐ฆ where this page comes from
- opensuse-leap 16.0: rawhide 3.3-bp160.1.12 ยท GPL-3.0-or-later AND BSD-3-Clause
- opensuse-leap 16.0: rawhide 3.3-bp160.1.12 ยท GPL-3.0-or-later AND BSD-3-Clause
- opensuse-leap 16.0: rawhide 3.3-bp160.1.12 ยท GPL-3.0-or-later AND BSD-3-Clause
- opensuse-leap 16.0: rawhide 3.3-bp160.1.12 ยท GPL-3.0-or-later AND BSD-3-Clause
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms