RAWHIDE.CONF(5)

๐Ÿ“ฆ version 3.3, in opensuse-leap 16.0

๐ŸŽ€ RAWHIDE.CONF(5) ๐ŸŽ€

okay bestie, gather round: this is the manual page for /etc/rawhide.conf, aka the config file for rh(1). It is giving โ€œtiny language, big main character energy,โ€ and honestly? once you get the vibe it is so good. ๐Ÿซถ

โœจ NAME โœจ

/etc/rawhide.conf โ€” configuration file for rh(1).

๐Ÿ’– DESCRIPTION ๐Ÿ’–

The /etc/rawhide.conf file (or something like it) is a standard library of functions that every rh(1) user can use. It is the first file rh(1) reads. Then any files in the /etc/rawhide.conf.d directory (or similar) whose names do not start with a dot (.) get read next, in lexicographic order โ€” those can add more system-wide function definitions. ๐Ÿ’…

After that, user-specific configuration is read from ~/.rhrc and ~/.rhrc.d/* files in the same manner.

Then come any files specified by rh(1)'s -f option, followed by the contents of rh(1)'s -e option argument. That -e argument can also contain additional function definitions and a trailing file test expression. All of these share the same syntax. โœจ

btw, the system-wide config might live somewhere else depending on operating system preferences โ€” think /usr/local/etc, /opt/local/etc, or /usr/pkg/etc. The output of rh --help will tell you where the system-wide configuration files are on the local system. ๐Ÿฆ‹

๐ŸŒธ FILE FORMAT ๐ŸŒธ

The following subsections describe the syntax and semantics of rawhide search criteria source/configuration files.

๐ŸŽ€ File structure ๐ŸŽ€

Every rawhide source/configuration file consists of zero or more function definitions, optionally followed by a file test expression, which is optionally terminated by a semicolon (;).

If a source file does contain a trailing file test expression, that expression becomes the default file test expression โ€” unless another file read later also contains a trailing file test expression, in which case that later expression becomes the default file test expression. It is not expected that any system-wide or user-specific configuration files will contain a trailing file test expression, but they can.

Files specified with the -f option are more likely to contain a trailing file test expression, in which case there is no need for the -e option (explicit or implicit). But if the -e option is also supplied, then any file test expression that it specifies takes precedence.

Technically, even the -e option argument does not require a file test expression (because there is a default which matches everything), but it will usually supply one.

๐Ÿ’ฌ Comments ๐Ÿ’ฌ

Search criteria can include explanations in the form of comments. There are three comment styles:

  • /* C-style comments */ โ€” these start with /*, end with */, and can span multiple lines.
  • // C++-style comments โ€” these start with // and end at the end of the line.
  • # Shell-style comments โ€” these start with # and end at the end of the line.

๐Ÿงš Grammar ๐Ÿงš

The rawhide search criteria language is like a tiny subset of the C language's expression syntax, with a few domain-specific additions. All of C's conditional, logical, relational, equality, arithmetic, and bit operators are available. But the only type is integer. There are special tokens to represent various things, some built-in symbols, and user-defined functions.

Here is the grammar of the search criteria language (BNF with a few liberties taken):

         <program> ::=
               <function-list> <expression> ";" EOF
             | <function-list> <expression> EOF
             | <function-list> EOF

         <function-list> ::=
               <function> <function-list>
             | EMPTY

         <function> ::=
             IDENTIFIER <parameters> "{" <return> <expression> <eos> "}"

         <return> ::=
               "return"
             | EMPTY

         <eos> == >
               ";"
             | EMPTY

         <parameters> ::=
               "(" <id-list> ")"
             | "(" ")"
             | EMPTY

         <id-list> ::=
             IDENTIFIER <id-tail>

         <id-tail> ::=
               "," <id-list>
             | EMPTY

         <expression> ::= <or> "?" <expression> ":" <expression> | <or>

         <or> ::= <and> "||" <or> | <and>

         <and> ::= <bitor> "&&" <and> | <bitor>

         <bitor> ::= <bitxor> "|" <bitor> | <bitxor>

         <bitxor> ::= <bitand> "^" <bitxor> | <bitand>

         <bitand> ::= <eq> "&" <bitand> | <eq>

         <eq> ::= <rel> <eq-op> <rel> | <rel>
         <eq-op> ::= "==" | "!="

         <rel> ::= <shift> <rel-op> <shift> | <shift>
         <rel-op> ::= "<" | ">" | "<=" | ">="

         <shift> ::= <add> <shift-op> <shift> | <add>
         <shift-op> ::= "<<" | ">>"

         <add> ::= <mul> <add-op> <add> | <mul>
         <add-op> ::= "+" | "-"

         <mul> ::= <unary> <mul-op> <mul> | <unary>
         <mul-op> ::= "*" | "/" | "%"

         <unary> ::= <unary-op> <unary> | <factor>
         <unary-op> ::= "-" | "~" | "!"

         <factor> ::=
               <number>
             | <user-id>
             | <group-id>
             | "[" <date-time> "]"
             | <pattern>
             | <reference-file>
             | <built-in>
             | <function-call>
             | <parameter>
             | "(" <expression> ")"

         <number> ::=
               DECIMAL
             | DECIMAL NOSPACE <scale>
             | "0" NOSPACE OCTAL
             | "0x" NOSPACE HEXADECIMAL

         <scale> ::=
               "K" | "M" | "G" | "T" | "P" | "E"
             | "k" | "m" | "g" | "t" | "p" | "e"

         <user-id> ::=
               "$" NOSPACE USERNAME
             | "$$"

         <group-id> ::=
               "@" NOSPACE GROUPNAME
             | "@@"

         <date-time> ::=
               DECIMAL "/" DECIMAL "/" DECIMAL
             | DECIMAL "/" DECIMAL "/" DECIMAL DECIMAL
             | DECIMAL "/" DECIMAL "/" DECIMAL DECIMAL ":" DECIMAL
             | DECIMAL "/" DECIMAL "/" DECIMAL DECIMAL ":" DECIMAL ":" DECIMAL

         <pattern> ::=
               STRING
             | STRING NOSPACE "." NOSPACE <pattern-modifier>

         <pattern-modifier> ::=
                        "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"

         <reference-file> ::=
             STRING NOSPACE "." NOSPACE <reference-file-field>

         <reference-file-field> ::=
               "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"

         <built-in> ::=
               "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"

         <function-call> ::=
             IDENTIFIER <arguments>

         <arguments> ::=
               "(" <expr-list> ")"
             | "(" ")"
             | EMPTY

         <expr-list> ::=
             <expression> <expr-tail>

         <expr-tail> ::=
               "," <expr-list>
             | EMPTY

         <parameter> ::=
             IDENTIFIER

๐ŸŒˆ Operators ๐ŸŒˆ

The following 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).

Like in C, the ?: and || and && operators use โ€œshort-circuitโ€ evaluation. ๐ŸŽ€

With the ternary conditional operator (?:), the second operand is only evaluated when the first operand evaluates to true (i.e., non-zero), and the third operand is only evaluated when the first operand evaluates to false (i.e., zero).

With the logical or operator (||), the second operand is only evaluated when the first operand evaluates to false. Otherwise, it is not necessary.

With the logical and operator (&&), the second operand is only evaluated when the first operand evaluates to true. Otherwise, it is not necessary.

This matters when using the prune, trim, or exit built-ins (see below), because they have control flow side-effects when they are evaluated. It is also important when using the texists built-in, the sh "pattern" modifier, and the exists reference file field (see below).

All the other binary operators always evaluate both operands.

๐Ÿ“ Numbers ๐Ÿ“

Numeric constants can be decimal, octal (starting with 0), or hexadecimal (starting with 0x). All numbers are integers. And everything is a number. ๐Ÿ’€

Decimal numbers can have a suffix letter that indicates the scale (e.g., 10K), in units based on 1024 (upper case), or based on 1000 (lower case):

K = Kibibytes (KiB, units of 1024)
M = Mebibytes (MiB, units of 1024^2)
G = Gibibytes (GiB, units of 1024^3)
T = Tebibytes (TiB, units of 1024^4)
P = Pebibytes (PiB, units of 1024^5)
E = Exbibytes (EiB, units of 1024^6)

k = Kilobytes  (KB, units of 1000)
m = Megabytes  (MB, units of 1000^2)
g = Gigabytes  (GB, units of 1000^3)
t = Terabytes  (TB, units of 1000^4)
p = Petabytes  (PB, units of 1000^5)
e = Exabytes   (EB, units of 1000^6)

๐Ÿ’ฌ Strings

ok so string literals live inside double quotes (""") โ€” iconic, simple. Any double quote characters inside your string have to be quoted with a preceding backslash character ("\"). Backslashes can also be quoted with a preceding backslash, which is lowkey the same energy. And any backslashes that don't precede a double quote or another backslash? They're just treated as normal characters and get included in the string. no drama, they're just vibing there.

Strings usually contain a pattern to match against file names (or certain other information associated with files). The value of a pattern string is whether or not the pattern match succeeded. Strings can also contain a shell command to execute โ€” and the value of a shell command string is the exit success status of that command. See the Pattern matching and Pattern modifiers sections below for details. Strings can also contain the path of a reference file to use for comparison purposes โ€” see the Reference file fields section below. versatile queen ๐Ÿ’…


๐Ÿ‘ค User IDs

A token starting with a dollar sign ($) immediately followed by a user name evaluates to that user's user ID. It's assumed that user names consist entirely of letters, digits, and dot (.), dash (-), and underscore characters (_). All non-ASCII characters (UTF-8 or ISO-8859-*) are considered to be "letters" โ€” yes, all of them, she's inclusive like that ๐Ÿซถ

The token $$ evaluates to the current user's user ID. it's giving selfie energy tbh.


๐Ÿ‘ฅ Group IDs

A token starting with an at sign (@) immediately followed by a group name evaluates to that group's group ID. It's assumed that group names consist entirely of letters, digits, and dot (.), dash (-), and underscore characters (_). All non-ASCII characters (UTF-8 or ISO-8859-*) are considered to be "letters". same rules, different symbol, we love consistency โœจ

The token @@ evaluates to the current user's primary group ID.


๐Ÿ“… Dates and date/times

Dates and date/times can be specified between square brackets ([]) โ€” specifically [YYYY/MM/DD] and [YYYY/MM/DD hh:mm:ss]. They're interpreted as being in the local time zone. If the year is specified as a number below 100, then it's interpreted as being a year in the current century. Hours must be in 24-hour time, not 12-hour time โ€” we're not doing AM/PM here bestie.

The seconds component is optional if it is zero (i.e., [YYYY/MM/DD hh:mm]). The minutes component is also optional if both it and the seconds are zero (i.e., [YYYY/MM/DD hh]). The hours component is also optional if it and the minutes and seconds are all zero (i.e., midnight) โ€” then it's just a date. Dates and date/times evaluate to the corresponding number of seconds since the UNIX epoch. iconic math moment ๐Ÿงฎ

โš ๏ธ Note that if the month, day, hours, minutes, or seconds are outside their valid ranges, the date or date/time is normalized โ€” so like, the 40th of October is interpreted as the 9th of November, and the time 25:90:90 is interpreted as 2:31:30 the following day. it's not an error, she just fixes it for you ๐Ÿ˜ญ See mktime(3) for details.

๐Ÿงฎ Functions

Functions can be defined to give names to expressions, and to make those names available for use in subsequent expressions. Function names can't be the same as the names of any existing functions or built-in symbols. (no name-stealing, it's rude โœ‹)

f() { return expr; }

Functions can only contain a return statement or expression. The return keyword and semicolon (;) are optional โ€” so this is equally valid:

f() { expr }

Functions can have parameters. Parameter names can't be the same as the names of any existing functions or built-in symbols either. same rule, bestie.

f(x, y, z) { (x + y) * z }

Functions with no parameters can be defined and called with or without parentheses. queen of flexibility:

f() { expr }
g { f }
g()

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. sad but true ๐Ÿ’€

The names of functions and parameters must start with a letter or underscore character (_), which can be followed by letters, underscores, and digits. All non-ASCII characters (UTF-8 or ISO-8859-*) are considered to be "letters".


๐Ÿ”ฎ Built-in symbols

There are various built-in symbols representing inode metadata (i.e., stat(2) structure fields), constants, and other useful information. these are the main characters of the whole operation, honestly ๐Ÿ‘‘

  • dev โ€” The device number of the device/filesystem that the current candidate file resides on. This is the st_dev field of the corresponding stat(2) structure. See the related major and minor built-ins (next) for extracting the major and minor device numbers from dev.
  • major โ€” The major device number of the device/filesystem that the current candidate file resides on. This is part of the st_dev field of the corresponding stat(2) structure.
  • minor โ€” The minor device number of the device/filesystem that the current candidate file resides on. This is part of the st_dev field of the corresponding stat(2) structure.
  • ino โ€” The inode number of the current candidate file. This is the st_ino field of the corresponding stat(2) structure.
  • mode โ€” The mode of the current candidate file. This is the st_mode field of the corresponding stat(2) structure. This contains the file type and permissions. The standard library provides the type and perm functions (among others) for extracting the file type and permissions from mode.
  • nlink โ€” The number of hard links to the current candidate file. This is the st_nlink field of the corresponding stat(2) structure.
  • uid โ€” The user ID of the owner of the current candidate file. This is the st_uid field of the corresponding stat(2) structure.
  • gid โ€” The group ID of the group of the current candidate file. This is the st_gid field of the corresponding stat(2) structure.
  • rdev โ€” The device number of the current candidate file. This is the st_rdev field of the corresponding stat(2) structure. This is only meaningful for character devices and block devices. See the related rmajor and rminor built-ins (next) for extracting the major and minor device numbers from rdev.
  • rmajor โ€” The major device number of the current candidate file. This is part of the st_rdev field of the corresponding stat(2) structure. This is only meaningful for character devices and block devices.
  • rminor โ€” The minor device number of the current candidate file. This is part of the st_rdev field of the corresponding stat(2) structure. This is only meaningful for character devices and block devices.
  • size โ€” The size in bytes of the current candidate file. This is the st_size field of the corresponding stat(2) structure.
    • For candidate symlinks (that are not followed with the -y or -Y option), this is the length in bytes of the target path.
    • For candidate directories that are readable, this is not the st_size field of the corresponding stat(2) structure. Instead, this is the number of entries it contains (excluding . and ..).
    • For candidate directories that are unreadable (and everything else), this is the usual (undocumented) st_size field of the corresponding stat(2) structure.
  • blksize โ€” The preferred block size for efficient I/O on the current candidate file's filesystem. This is the st_blksize field of the corresponding stat(2) structure. 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 built-in (see next). don't get them confused bestie ๐Ÿ™ˆ
  • blocks โ€” The number of 512-byte blocks occupied by the current candidate file. This is the st_blocks field of the corresponding stat(2) structure.
  • atime โ€” The time that the current candidate file was last accessed. This is the st_atime field of the corresponding stat(2) structure.
  • mtime โ€” The time that the current candidate file's contents were last modified. This is the st_mtime field of the corresponding stat(2) structure.
  • ctime โ€” The time that the current candidate file's inode was last changed. This is the st_ctime field of the corresponding stat(2) structure.
  • attr โ€” On Linux, the current candidate file's ext2-style file attribute bit mask. Not all file systems support all file attributes. See chattr(1) and lsattr(1) for details. See /etc/rawhide.conf.d/attributes for all the related predicate functions (e.g., immutable, append, nodump, nocow, dax, ...).
    • Note: If the current candidate file is a symlink, it is never followed for the purpose of obtaining the file attributes. This is a decision made by libe2p. This is not affected by the -y or -Y options. Also note that symlinks do not have their own file attributes (i.e., attr is zero).
    • On Linux, this is only available on systems with libe2p. On Linux systems without libe2p, this is not a built-in symbol. Instead, this is a place-holder function that returns zero.
    • This is also available on FreeBSD, OpenBSD, NetBSD, and macOS. The attributes/flags that are available vary from system to system. See chflags(1) for details. See /etc/rawhide.conf.d/attributes for all the related predicate functions (e.g., uimmutable, uappend, nodump, opaque, ...).
    • On other systems, this doesn't exist at all. ๐Ÿ’€
  • proj โ€” On Linux, the current candidate file's ext2-style file project number. Not all file systems support all file attributes. See chattr(1) and lsattr(1) for details.
    • Note: If the current candidate file is a symlink, it is always followed for the purpose of obtaining the file project. This is not affected by the -y or -Y options.
    • Note: This is only available on Linux systems with libe2p. On Linux systems without libe2p, this is not a built-in symbol. Instead, this is a place-holder function that returns zero. On other systems, this doesn't exist at all.
  • gen โ€” On Linux, the current candidate file's ext2-style version/generation number. Not all file systems support all file attributes. See chattr(1) and lsattr(1) for details.
    • Note: If the current candidate file is a symlink, it is always followed for the purpose of obtaining the file version/generation. This is not affected by the -y or -Y options.
    • Note: This is only available on Linux systems with libe2p. On Linux systems without libe2p, this is not a built-in symbol. Instead, this is a place-holder function that returns zero. On other systems, this doesn't exist at all.
  • nouser โ€” This is true (i.e., non-zero) when the current candidate file's uid does not correspond to any user in the user database (e.g., the local password file /etc/passwd, NIS, and LDAP).
  • nogroup โ€” This is true (i.e., non-zero) when the current candidate file's gid does not correspond to any group in the group database (e.g., the local group file /etc/group, NIS, and LDAP).

๐Ÿ’– readable

True (like, non-zero true) when the current candidate file is readable by the current user. Bestie, listen: this uses the access(2) system call, not the file permissions. So it's giving "can I actually read this rn", not "what do the mode bits say". ๐Ÿซถ

๐Ÿ’– writable

True (i.e., non-zero) when the current candidate file is writable by the current user. Again โ€” access(2) system call, not the file permissions. Same vibe, different permission. โœจ

๐Ÿ’– executable

True (i.e., non-zero) when the current candidate file is executable by the current user. Yep, access(2) system call, not the file permissions. It's the trifecta bestie ๐Ÿ’…

๐Ÿ’– strlen

This is the length in bytes of the base name of the current candidate file. Heads up though: this is zero for the root directory (/), because root literally has no base name. Not me defending root's name situation ๐Ÿ˜ญ

๐Ÿ’– depth

How deep the current candidate file is, relative to the starting search directory. The starting search directory itself is depth 0, its children are depth 1, and so on down the tree. ๐Ÿฆ‹

๐Ÿ’– prune

This one always evaluates to true (non-zero), and as a side-effect it makes the directory tree being searched get "pruned". Translation: the current candidate file will not match, and if it's a directory, it will not be searched. It's a speed-up move for your search. ๐Ÿ”ฅ

prune needs to be used with the ?: or || or && operator (see above) to conditionally skip part of a directory tree and search the rest.

prune and the -D (depth-first) option are incompatible. When -D is used, prune will not work โ€” it will not prevent searching below the current candidate path. Same deal with the -U (unlink) option, which implies -D. ๐Ÿซ 

Note: this is counter-intuitive, and I need you to trust me here. prune evaluates to true, even though it effectively causes the file test expression as a whole to evaluate to false (i.e., zero), in order to prevent the current candidate file from matching. The reason prune evaluates to true rather than false is to make expressions like (bad && prune || good) possible as a syntactic alternative to (bad ? prune : good). iykyk ๐Ÿ’€
Note: prune and trim are very similar (see next). They both prevent searching below the current candidate path. The only difference between them is whether or not the current candidate path itself is also prevented from matching. Use prune when the current candidate path needs to be excluded. Use trim when it needs to be included. Think of prune as a heavy trim. ๐ŸŽ€

๐Ÿ’– trim

Always evaluates to true (non-zero), and as a side-effect it makes the directory tree being searched get "trimmed". Meaning: if the current candidate file is a directory, it will not be searched (exactly like prune). But unlike prune, the current candidate file itself is not prevented from matching. So when the current candidate file isn't a directory, this does literally nothing. Still a speed-up move tho โœจ

trim needs to be used with the ?: or || or && operator (see above) to conditionally skip part of a directory tree and search the rest.

trim and the -D (depth-first) option are incompatible. When -D is used, trim will not work โ€” it will not prevent searching below the current candidate path. Same applies to -U (unlink), which implies -D. ๐Ÿซ 

Note: trim and prune are very similar (see above). They both prevent searching below the current candidate path. The only difference is whether or not the current candidate path itself is also prevented from matching. Use trim when the current candidate path needs to be included. Use prune when it needs to be excluded. Think of trim as a light prune. ๐Ÿฆ‹

๐Ÿ’– exit

Always evaluates to true (non-zero), and as a side-effect it terminates the search immediately after the current candidate file. This is the "find the first matching file and then dip" builtin. ๐Ÿ‘‘

exit needs to be used with the ?: or || or && operator (see above) to conditionally terminate the search.

exit can be used in combination with prune and/or trim (see above), e.g. bad && prune || good && exit. Slay.

๐Ÿ’– now

The current time (when rh(1) started), in seconds since the UNIX epoch. โฐ

๐Ÿ’– today

The time last midnight (local time), in seconds since the UNIX epoch. ๐ŸŒ™

๐Ÿ’– second

The value 1. That's it. That's the builtin. ๐Ÿ’…

๐Ÿ’– minute

The number of seconds in a minute.

๐Ÿ’– hour

The number of seconds in an hour.

๐Ÿ’– day

The number of seconds in a day.

๐Ÿ’– week

The number of seconds in a week.

๐Ÿ’– month

The average number of seconds in a month. Yes, average. Months are not all the same length, bestie, that's just how it is ๐Ÿ˜ญ

๐Ÿ’– year

The average number of seconds in a year.

๐Ÿ’– IFMT

Same as the S_IFMT bit mask from C's <sys/stat.h> header file. This bit mask is the bits in the mode that represent the file type. So the file type is (mode & IFMT) (see next), and the file permissions are (mode & ~IFMT) (see below). Bit math girlies rise ๐Ÿงš

๐Ÿ’– IFREG, IFDIR, IFLNK, IFCHR, IFBLK, IFSOCK, IFIFO, IFDOOR

Same as the corresponding S_ values from C's <sys/stat.h> header file. These correspond to the different types of filesystem entry โ€” that's regular file, directory, symbolic link, character device, block device, socket, fifo/pipe, and door, respectively, in that exact order. IFDOOR only applies to Solaris. ๐Ÿšช

๐Ÿ’– ISUID, ISGID, ISVTX

Same as the corresponding S_ bit masks from C's <sys/stat.h> header file. These bit masks correspond to the setuid, setgid, and sticky bits in file permissions โ€” i.e. --S------, -----S---, and --------T, respectively (aka 04000, 02000, and 01000). ๐Ÿ’œ

๐Ÿ’– IRWXU, IRUSR, IWUSR, IXUSR

Same as the corresponding S_ bit masks from C's <sys/stat.h> header file. These correspond to the user/owner file permissions โ€” i.e. rwx------, r--------, -w-------, and --x------, respectively (aka 0700, 0400, 0200, and 0100). ๐ŸŒธ

๐Ÿ’– IRWXG, IRGRP, IWGRP, IXGRP

Same as the corresponding S_ bit masks from C's <sys/stat.h> header file. These correspond to the group file permissions โ€” i.e. ---rwx---, ---r-----, ----w----, and -----x---, respectively (aka 0070, 0040, 0020, and 0010). ๐Ÿ“

๐Ÿ’– IRWXO, IROTH, IWOTH, IXOTH

Same as the corresponding S_ bit masks from C's <sys/stat.h> header file. These correspond to the other/world file permissions โ€” i.e. ------rwx, ------r--, -------w-, and --------x, respectively (aka 0007, 0004, 0002, and 0001). ๐ŸŒˆ

๐Ÿ’– texists

True (non-zero) when the current candidate file is a symlink whose ultimate target exists โ€” that's when rh(1) can actually obtain its stat(2) structure information. Important caveat: a false value (zero) might be because of permissions, not because the thing doesn't exist. Don't let it gaslight you ๐Ÿ™ˆ

Use it with the ?: or || or && operator (see above) to conditionally use any of the following stat(2) structure fields relating to symlink targets. Unless the target exists, their values are all zero and meaningless.

Note: the -Y option is incompatible with this built-in. The only symlinks seen will be broken ones, and this built-in will return zero. ๐Ÿ’€
Note: even without the -y or -Y option (so symlinks aren't followed for the purpose of searching), symlinks are followed for the purpose of obtaining the stat(2) structure information of the current candidate symlink's ultimate target โ€” all so the following target-related built-in symbols can work. ๐Ÿซถ

๐Ÿ’– tdev

The device number of the device/filesystem that the current candidate symlink's ultimate target resides on. This is the st_dev field of the corresponding stat(2) structure. See the related tmajor and tminor built-ins (next) for extracting the major and minor device numbers out of tdev. ๐Ÿ”

Only use this when the current candidate file is a symlink and its ultimate target exists (i.e., when texists is true). Otherwise, this is zero.

Note: the -Y option is incompatible with this built-in. The only symlinks seen will be broken ones, and this built-in will return zero.

๐Ÿ’– tmajor

The major device number of the device/filesystem that the current candidate symlink's ultimate target resides on. This is part of the st_dev field of the corresponding stat(2) structure.

Only use this when the current candidate file is a symlink and its ultimate target exists (i.e., when texists is true). Otherwise, this is zero.

Note: the -Y option is incompatible with this built-in. The only symlinks seen will be broken ones, and this built-in will return zero.

๐Ÿ’– tminor

The minor device number of the device/filesystem that the current candidate symlink's ultimate target resides on. This is part of the st_dev field of the corresponding stat(2) structure.

Only use this when the current candidate file is a symlink and its ultimate target exists (i.e., when texists is true). Otherwise, this is zero.

Note: the -Y option is incompatible with this built-in. The only symlinks seen will be broken ones, and this built-in will return zero.

๐Ÿ’– tino

The inode number of the current candidate symlink's ultimate target. This is the st_ino field of the corresponding stat(2) structure. ๐ŸŽ€

Only use this when the current candidate file is a symlink and its ultimate target exists (i.e., when texists is true). Otherwise, this is zero.

Note: the -Y option is incompatible with this built-in. The only symlinks seen will be broken ones, and this built-in will return zero.

๐Ÿ’– tmode

The mode of the current candidate symlink's ultimate target. This is the st_mode field of the corresponding stat(2) structure, and it holds the file type and the permissions. The standard library provides the ttype and tperm functions (among others) for extracting the file type and permissions from tmode. Combo meal energy ๐Ÿ”

Only use this when the current candidate file is a symlink and its ultimate target exists (i.e., when texists is true). Otherwise, this is zero.

Note: the -Y option is incompatible with this built-in. The only symlinks seen will be broken ones, and this built-in will return zero.

The number of hard links to the current candidate symlink's ultimate target. This is the st_nlink field of the corresponding stat(2) structure. ๐Ÿ”—

This one's only meant for when the current candidate file is a symlink and its ultimate target actually exists (i.e. when texists is true). Any other situation? It's zero. ๐Ÿซถ

โš ๏ธ Real talk: the -Y option does not play nice with this built-in. The only symlinks you'll ever see are broken ones, so this built-in just returns zero. Every time. ๐Ÿ˜ญ

๐Ÿ’ซ tuid

The user ID of whoever owns the current candidate symlink's ultimate target โ€” literally the st_uid field of the corresponding stat(2) structure. ๐Ÿ‘‘

Same rule as always: only use it when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise it's zero.

โš ๏ธ And the -Y option is incompatible with this built-in โ€” you'll only see broken symlinks, and it'll return zero.

๐Ÿ’ซ tgid

The group ID of the group of the current candidate symlink's ultimate target. That's the st_gid field of the corresponding stat(2) structure. ๐Ÿซ‚

Only use it when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise: zero, zero, zero.

โš ๏ธ -Y is incompatible with this built-in too โ€” only broken symlinks show up, and it returns zero.

๐Ÿ’ซ trdev

The device number of the current candidate symlink's ultimate target, aka the st_rdev field of the corresponding stat(2) structure. This one only actually means something for character devices and block devices, so keep that in mind. ๐Ÿ”Œ If you want the major and minor device numbers pulled out of trdev, that's what the related trmajor and trminor built-ins (right below) are for.

Still only valid when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise, zero.

โš ๏ธ -Y claps back: incompatible with this built-in, broken symlinks only, returns zero.

๐Ÿ’ซ trmajor

The major device number of the current candidate symlink's ultimate target โ€” it's part of the st_rdev field of the corresponding stat(2) structure. Again: only meaningful for character devices and block devices. ๐ŸŽ›๏ธ

Only usable when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise it's zero.

โš ๏ธ The -Y option is incompatible with this built-in. Broken symlinks only, and zero comes back.

๐Ÿ’ซ trminor

The minor device number of the current candidate symlink's ultimate target. Also part of the st_rdev field of the corresponding stat(2) structure, and also only meaningful for character devices and block devices. ๐ŸŽš๏ธ

Only for when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise: zero.

โš ๏ธ -Y is incompatible with this built-in. You'll only see broken symlinks and you'll get zero. No cap.

๐Ÿ’ซ tsize

The size, in bytes, of the current candidate symlink's ultimate target file โ€” the st_size field of the corresponding stat(2) structure. But bestie, there are layers here: ๐Ÿฐ

  • For an ultimate target that's a readable directory, this is not the st_size field of the corresponding stat(2) structure. Instead it's the number of entries the directory contains (not counting . and ..).
  • For an ultimate target that's an unreadable directory (and literally everything else), it's the usual (undocumented) st_size field of the corresponding stat(2) structure.

Only use it when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise it's zero.

โš ๏ธ -Y is incompatible with this built-in. You'll only see broken symlinks, and it returns zero.

๐Ÿ’ซ tblksize

The preferred block size for efficient I/O on the current candidate symlink's ultimate target's filesystem โ€” the st_blksize field of the corresponding stat(2) structure. ๐Ÿ’พ On some filesystems (e.g. zfs) this is per-file rather than per-whole-filesystem, which is kind of iconic of them tbh.

๐Ÿ“Œ Heads up: this is unrelated to the tblocks built-in (next one down). Don't mix them up, iykyk.

Only used when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise, zero.

โš ๏ธ -Y is incompatible with this built-in โ€” broken symlinks only, and you get zero.

๐Ÿ’ซ tblocks

How many 512-byte blocks the current candidate symlink's ultimate target occupies โ€” the st_blocks field of the corresponding stat(2) structure. ๐Ÿงฑ

Only when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise it's zero.

โš ๏ธ -Y is incompatible with this built-in. Only broken symlinks, only zero.

๐Ÿ’ซ tatime

When the current candidate symlink's ultimate target was last accessed โ€” the st_atime field of the corresponding stat(2) structure. ๐Ÿ‘€

Only when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise, zero.

โš ๏ธ -Y is incompatible with this built-in. Broken symlinks only, and zero.

๐Ÿ’ซ tmtime

When the current candidate symlink's ultimate target's contents were last modified โ€” the st_mtime field of the corresponding stat(2) structure. โœ๏ธ

Only when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise, zero.

โš ๏ธ -Y is incompatible with this built-in. Broken symlinks only, zero only.

๐Ÿ’ซ tctime

When the current candidate symlink's ultimate target's inode was last changed โ€” the st_ctime field of the corresponding stat(2) structure. ๐Ÿ•ฐ๏ธ

Only when the current candidate file is a symlink and its ultimate target exists (i.e. texists is true). Otherwise, zero.

โš ๏ธ -Y is incompatible with this built-in. Broken symlinks only, and zero.

๐Ÿ’ซ tstrlen

The length in bytes of the base name of the current candidate symlink's immediate target path. ๐ŸŽ€ Lowkey important detail: this is zero if the target path is the root directory (/), because root has no base name.

This one's only used when the current candidate file is a symlink โ€” otherwise it's zero. But here's the slay part: the ultimate target does not need to exist. โœจ

โš ๏ธ And unlike its friends above, the -Y option affects this built-in: the only symlinks you'll see are broken ones, but this built-in still returns the correct length. Main character behaviour fr fr.

๐ŸŒˆ Pattern matching ๐ŸŒˆ

By default, string literals ("pattern") hold shell-style file glob patterns that get matched against each candidate file's base name. Want variations? Scroll down to the Pattern modifiers section, bestie. ๐Ÿ‘‡

Pattern strings evaluate to true (i.e. non-zero) if the pattern match succeeds, or false (i.e. zero) otherwise.

The following POSIX glob pattern notation is supported on all platforms:

  • ? โ€” Matches any single character. A leading dot character (.) is not treated as a special character, so for example ?profile would match .profile. This is unlike /bin/sh behaviour, but it's like POSIX find(1) behaviour. ๐Ÿคฏ
  • * โ€” Matches any string of characters, including the empty string. A leading dot character (.) is not treated as a special character here either โ€” for example * would match .profile. Again: unlike /bin/sh, like POSIX find(1).
  • [abc] โ€” Matches any single character in the given set. Ranges work too (e.g. [a-z0-9]). ๐Ÿ”ค
  • [!abc] โ€” Matches any single character not in the given set. Ranges work here too (e.g. [!A-Z]).
๐ŸŒธ And although it's non-standard (i.e., officially undefined), on most systems you can use the more familiar ^ instead of ! to complement the set in character classes (e.g. [^abc]) โ€” but not on Solaris. ๐Ÿ’€

Locale-specific character class notation should also be available on all platforms (i.e. named character classes like [[:lower:]], collating symbols like [[.a-acute.]], and equivalence class expressions like [[=a=]]), but not all locales support all of them. ๐Ÿ—บ๏ธ

The special meanings of ?, * and [ can be removed by quoting them with a preceding backslash character (\).

The following Ksh extended glob pattern notation is supported on platforms with GNU glibc (i.e. Linux):

  • ?(a|b|c) โ€” Matches if zero or one occurrences of any of the sub-patterns match the text.
  • *(a|b|c) โ€” Matches if zero or more occurrences of any of the sub-patterns match the text.
  • +(a|b|c) โ€” Matches if one or more occurrences of any of the sub-patterns match the text.
  • @(a|b|c) โ€” Matches if exactly one occurrence of any of the sub-patterns matches the text.
  • !(a|b|c) โ€” Matches if the text cannot be matched by any of the sub-patterns. ๐Ÿ™…โ€โ™€๏ธ

The special meanings of ?, *, +, @, !, (, | and ) can be removed by quoting them with a preceding backslash character (\).

The output of rh --help states if Ksh extended glob pattern notation is available on the local system. ๐Ÿ–ฅ๏ธ

See fnmatch(3) and glob(7) for more details.


๐ŸŽ€ Pattern modifiers ๐ŸŽ€

Pattern modifiers ("pattern".modifier) are suffixes you tack onto string literals โ€” they change how the string is interpreted and what actually gets done with it. ๐Ÿ’… There must be no spaces around the dot between the string literal and the modifier name.

You can give modifier names in full (e.g. "pattern".path), but a unique prefix is enough (e.g. "pattern".pat, "pattern".pa, or "pattern".p). For every modifier below, both the full name and the shortest unique prefix are shown. ๐ŸŒŸ

The following modifiers exist, but not all are available on all systems.

path (or p)

Apply the glob pattern to each candidate file's full path starting from the search directory, not just its base name. Slash characters ("/") are not treated as special characters. For example, "./sr*sc".path would match ./src/misc.

Note: since a slash character can never appear in a file's base name, if a glob pattern string with no pattern modifier does contain a slash character (e.g., "/"), then instead of attempting (and failing ๐Ÿ’€) to match it against the file's base name, an implicit path pattern modifier is assumed (e.g., "/".path), and it gets matched against the file's full path starting from the search directory instead. iykyk.

Apply the glob pattern to each candidate symlink's target path, rather than its base name. Slash characters ("/") are not treated as special characters. For example, "./sr*sc".path would match ./src/misc.

body (or b)

Apply the glob pattern to each candidate file's contents, rather than its base name. Slash characters ("/") are not treated as special characters.

Note that this is an expensive pattern modifier, because each candidate file is read in its entirety to determine whether or not the pattern matches ๐Ÿ˜ญ. It can be worthwhile preceding this with other predicates to reduce the number and limit the size of candidate files that this is applied to. fr fr, be smart about it.

This probably shouldn't be used with very large files. It would require a lot of memory or swap, and on 32-bit systems, the possible file size is also limited by the available address space. If this is an issue, you can always use grep(1) (or similar) via the sh "pattern" modifier instead.

Also note that this only works with regular files that are readable to the current user.

Also note that the body and ibody glob pattern modifiers shouldn't be used with binary files that might contain null bytes ๐Ÿ’€. For binary files, use the rebody or reibody regular expression pattern modifiers instead (see below).

Note: if the current candidate file is a symlink, its body is unaffected by the -y or -Y options. For the purpose of this predicate, the symlink is always followed.

what (or w)

Apply the glob pattern to each candidate file's file type description (as would be output by the file(1) utility), rather than its base name. Slash characters ("/") are not treated as special characters. This is available on systems with libmagic(3) installed. slay.

Note that this is an expensive pattern modifier because each candidate file is at least partially read to determine its file type. It can be worthwhile preceding this with other predicates to reduce the number of candidate files that this is applied to.

Also note that this only works with files that are readable to the current user.

Also note that any errors encountered result in an error message taking the place of the file type description being matched ๐Ÿ‘€. To see these error messages, you can use the -L option with its %w format conversion.

Note: if the current candidate file is a symlink, its file type description is affected by the -y or -Y options.

mime (or m)

Apply the glob pattern to each candidate file's MIME type (including the character set), rather than its base name. Slash characters ("/") are not treated as special characters. This is available on systems with libmagic(3) installed.

Note that this is an expensive pattern modifier because each candidate file is at least partially read to determine its MIME type. It can be worthwhile preceding this with other predicates to reduce the number of candidate files that this is applied to.

Also note that this only works with files that are readable to the current user.

Also note that any errors encountered result in an error message taking the place of the MIME type being matched. To see these error messages, you can use the -L option with its %W format conversion.

Note: if the current candidate file is a symlink, its MIME type is affected by the -y or -Y options.

i

Apply the glob pattern without case-sensitivity โœจ. This is available on most systems, but maybe not all.

The output of rh --help states if case-insensitive glob matching is available on the local system.

ipath (or ip)

Like the path modifier, but apply the glob pattern without case-sensitivity. This is available on most systems, but maybe not all.

Note: since a slash character can never appear in a file's base name, if a glob pattern string with the i pattern modifier does contain a slash character (e.g., "/".i), then instead of attempting (and failing) to match it against the file's base name, an implicit ipath pattern modifier is assumed (e.g., "/".ipath), and it will be matched against the file's full path starting from the search directory instead.

Like the link modifier, but apply the glob pattern without case-sensitivity. This is available on most systems, but maybe not all.

ibody (or ib)

Like the body modifier, but apply the glob pattern without case-sensitivity.

iwhat (or iw)

Like the what modifier, but apply the glob pattern without case-sensitivity. This is available on most systems with libmagic(3) installed, but maybe not all.

imime (or im)

Like the mime modifier, but apply the glob pattern without case-sensitivity. This is available on most systems with libmagic(3) installed, but maybe not all.


re

Match each candidate file's base name against a Perl-compatible regular expression (regex) instead of a glob pattern ๐Ÿ”ฅ. See perlre(1), pcre2pattern(3), and pcre2syntax(3) for details. This is available on systems that have libpcre2-8 installed.

The output of rh --help states whether or not Perl-compatible regular expressions are available on the local system.

Regex patterns are not automatically anchored to the start and end, and so do not have to match the entire text. no cap.

The following PCRE2 options are always enabled:

  • PCRE2_DOTALL โ€” the . meta-character matches any character, including the newline character. This prevents surprises when files have newline characters in their names. This is like Perl's /s modifier.
  • PCRE2_MULTILINE โ€” the ^ meta-character matches after every internal newline character, not just at the start. The $ meta-character matches before every internal newline character, not just at the end. This is useful for access control list (ACL) and extended attribute (EA) searches (see below) where the text can occupy multiple lines. This is like Perl's /m modifier.
  • PCRE2_EXTENDED โ€” ignore (outside character classes) most whitespace characters and everything from a hash character ("#") until the end of the line. Matching a whitespace or hash character requires quoting it with a preceding backslash character ("\"). Complex regex patterns can be made more readable this way, and it enables comments in regex patterns ๐Ÿ’…. This is like Perl's /x modifier.
  • PCRE2_EXTENDED_MORE โ€” ignore space and tab characters inside character classes. Matching a space or tab in a character class requires quoting it with a preceding backslash character ("\"). Complex character classes can be made more readable this way. This is like Perl's /xx modifier.
  • PCRE2_NO_AUTO_CAPTURE โ€” prevent parentheses from automatically capturing matching text (i.e., always treat (...) as (?:...)). There are no uses for captures in rh(1), and this should make matching ever so slightly smaller and faster, without the explicit ?:. This is like Perl's /n modifier.
Note that the regex pattern and the text being matched are generally assumed to be encoded as UTF-8. If you need to suppress this assumption, set the environment variable RAWHIDE_PCRE2_NOT_UTF8_DEFAULT=1. 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.

repath (or rep)

Like the re modifier, but apply the regex to each candidate file's full path starting from the search directory, not just its base name.

Note: since a slash character can never appear in a file's base name, if a regex pattern string with the re pattern modifier does contain a slash character (e.g., "/".re), then instead of attempting (and failing) to match it against the file's base name, an implicit repath pattern modifier is assumed (e.g., "/".repath), and it will be matched against the file's full path starting from the search directory instead.

Like the re modifier, but apply the regex to each candidate symlink's target path, rather than its base name.

rebody (or reb)

Like the re modifier, but apply the regex to each candidate file's contents, rather than its base name. See body above for more details ๐Ÿ‘†.

rewhat (or rew)

Like the re modifier, but apply the regex to each candidate file's file type description, rather than its base name. This is available on systems that have libpcre2-8 and libmagic installed. See what above for more details.

remime (or rem)

Like the re modifier, but apply the regex to each candidate file's MIME type, rather than its base name. This is available on systems that have libpcre2-8 and libmagic installed. See mime above for more details.

rei

Like the re modifier, but apply the regex without case-sensitivity โœจ.

reipath (or reip)

Like the repath modifier, but apply the regex without case-sensitivity.

Note: since a slash character can never appear in a file's base name, if a regex pattern string with the rei pattern modifier does contain a slash character (e.g., "/".rei), then instead of attempting (and failing) to match it against the file's base name, an implicit reipath pattern modifier is assumed (e.g., "/".reipath), and it will be matched against the file's full path starting from the search directory instead.
  • reilink (or reil) โ€” literally the relink modifier, but the regex gets applied without case-sensitivity. lowercase bestie hours ๐Ÿ’…
  • reibody (or reib) โ€” same deal as rebody, except the regex is applied without case-sensitivity. ๐Ÿซถ
  • reiwhat (or reiw) โ€” the rewhat modifier's chill twin: same regex, no case-sensitivity. โœจ
  • reimime (or reim) โ€” and remime, but the regex is applied without case-sensitivity. that's the whole sentence, slay. ๐ŸŽ€
  • acl (or a) โ€” applies your glob pattern to each candidate file's access control list (ACL) instead of its base name. no cap, it's searching the ACL text itself. ๐Ÿ‘‘

This one's available on systems with "POSIX" ACLs (e.g., Linux, FreeBSD, Solaris (for ufs), and Cygwin), and NFSv4 ACLs (e.g., FreeBSD and Solaris (for zfs)), and macOS with its own Extended ACLs. OpenBSD and NetBSD don't do ACLs at all โ€” not their era. See acl(2) or acl(3) or acl(5) for more details.

The output of rh --help states if access control lists are available on the local system.

"POSIX" ACL text is multiple lines, each one terminated by a newline character. it's giving:

user::rwx
user:name:rwx
group::rwx
group:name:rwx
mask::rwx
other::rwx

NFSv4 ACL text, though, comes in two forms, because of course it does. In the compact form, the items look like these:

    owner@:rwxp--aARWcCos:-------:allow
 user:name:rwxp--aARWcCos:-------:allow
    group@:r-x---a-R-c--s:-------:allow
group:name:r-x---a-R-c--s:-------:allow
 everyone@:r-x---a-R-c--s:-------:allow

In the non-compact form, the items each look something like this:

owner@:read_data/write_data/append_data/read_xattr/write_xattr
  /execute/read_attributes/write_attributes/read_acl/write_acl
  /write_owner/synchronize:allow

Searches can be made for either form. But โš ๏ธ be warned that the permission names in the non-compact form do not always appear in the same order (at least on Solaris). so don't come crying to me when your glob misses. ๐Ÿ˜ญ

On FreeBSD, NFSv4 ACL text has each item on a separate line, terminated by a newline character, and there are leading spaces for vertical alignment before each item. On Solaris, NFSv4 ACL text is a comma-separated list of items with no leading spaces and no newline character. different fonts of the same energy fr fr.

macOS has its own Extended ACLs that look like this:

!#acl 1
user:906E2CAD-E971-12D8-ACB9-000A95DF094B:drew:502:allow:write

An easy way to see what ACLs look like on your system is:

$ rh -L '%p\n%z\n'

But bear in mind that this reformats multiple lines into a comma-separated list. That can be avoided if the locale uses UTF-8 and jq(1) is installed:

$ rh -L %j | jq -r .access_control_list

Note that glob patterns are anchored to the start and end, and so must match the entire text. the whole thing, not just a vibe. ๐Ÿงš

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.

  • iacl (or ia) โ€” like the acl modifier, but the glob pattern is applied without case-sensitivity. This might not be available on all systems. ๐Ÿ™ˆ
  • reacl (or rea) โ€” like the acl modifier, but instead of a glob pattern, it matches each candidate file's access control list (ACL) against a Perl-compatible regular expression (regex). This is available on systems that have libpcre2-8 installed, and supported access control lists. See the re modifier above for more information. โœจ
  • reiacl (or reia) โ€” like the reacl modifier, but the regex is applied without case-sensitivity. ๐Ÿ’œ
  • ea (or e) โ€” applies the glob pattern to each candidate file's extended attributes (EA), rather than its base name. ๐Ÿฆ‹

This is available on Linux, FreeBSD, macOS, Solaris, and Cygwin. OpenBSD and NetBSD don't do EAs. See xattr(1) or xattr(7) or extattr(2) or fsattr(7) for more details.

The output of rh --help states if extended attributes are available on the local system.

The extended attributes text, if any, will be possibly multiple lines, one per extended attribute, each terminated by a newline character. Each extended attribute starts with its name. If it also has a value, then the name is followed by colon and space characters (": "), and then the value. Note that on macOS, some extended attribute names contain a colon character, but they aren't typically followed by a space character. iykyk. ๐Ÿ“

Note that 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. okay? okay. ๐Ÿ’€

This encoding is the text that patterns must be written to match against. But you can still match against any text in extended attribute names, and ASCII-only text in their values, as you normally would. whew. ๐Ÿ˜ญ

Note that glob patterns are anchored to the start and end, and so must match the entire text. no partial credit, bestie. โญ

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. main character privileges only. ๐Ÿ‘‘

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. it's built different. ๐Ÿ”ฅ

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), and the -L %x format conversion. To prevent truncation, set the environment variable RAWHIDE_EA_SIZE to 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. just the number, babes. ๐ŸŽ€

On Solaris, every file's extended attributes directory contains the SUNWattr_ro and SUNWattr_rw extended attributes. By default, they are included for every file that has any other extended attributes. They can be excluded by setting the environment variable RAWHIDE_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 have the following format:

name/stat: -rw-r--r-- nlink user group size mtime atime ctime

The name is the name of the corresponding real extended attribute. The "/stat:" is literal. The nlink is always 1. The user and group are names or numeric IDs. The size is in bytes. The mtime, atime, and ctime are in ISO date/time format ("YYYY-MM-DD HH:MM:SS +HHMM"). ๐ŸŒธ

This makes it possible on Solaris to search for files by the stat(2) information of their extended attributes. But it can only be a text-based search (glob or regex). text-based only, ngl. ๐Ÿ™ˆ

These artificial extended attributes can be suppressed by setting the environment variable RAWHIDE_SOLARIS_EA_NO_STATINFO=1.

  • iea (or ie) โ€” like the ea modifier, but the glob pattern is applied without case-sensitivity. This might not be available on all systems. ๐Ÿ’…
  • reea (or ree) โ€” like the ea modifier, but matches each candidate file's extended attributes (EA) against a Perl-compatible regular expression (regex) instead of a glob pattern. This is available on systems that have libpcre2-8 installed, and supported extended attributes. See the re modifier above for more information. ๐Ÿฆ‹
  • reiea (or reie) โ€” like the reea modifier, but the regex is applied without case-sensitivity. โœจ
  • sh (or s) โ€” okay so this one does NOT treat the string literal as a pattern at all. it treats it as an external shell command to execute via system(3) (i.e., via /bin/sh). It evaluates to the exit success status of the external shell command. If its exit status is zero, then the value is true (i.e., non-zero). Otherwise, it is false (i.e., zero). iconic logic, honestly. ๐Ÿ’…

This enables arbitrary command line utilities to contribute to the search criteria. But โš ๏ธ be warned, this can be slow, because it involves the creation of possibly many other processes. But it makes many things possible. To reduce the number of external shell commands that need to be executed, the sh "pattern" modifier can be used conditionally with the ?: or || or && operator (see above). work smarter, not harder. ๐Ÿงš

The string can contain %s which will be replaced with the matching entry's full path starting from the search directory. It can also contain %S which will be replaced with the matching entry's base name (or with "/" when the matching entry is the root directory (/) which has no base name). 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 no need to place any quote characters around %s or %S. she's safe, she's careful, she ate. ๐Ÿ‘‘

Every shell command is executed after safely changing the current working directory to the directory containing each candidate file. This minimizes the number of path-based race conditions. So the %S interpolation is more likely to be useful than the %s interpolation. ๐Ÿซถ

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. ๐Ÿ’€ no cap, that's a real safety thing, don't @ me.

โš ๏ธ Heads up bestie: since the shell commands run from the directory that holds each matching entry, if they actually need the matching entry's full path counted from the search directory (i.e. %s), then it's so much better if your starting search paths are all absolute paths, so %s is always an absolute path. Otherwise the shell command might have to change its current working directory back to the initial working directory. Also, %s suffers from a whole bunch of path-based race conditions, which is insecure on hosts with malicious local actors who have write access to the directory tree being searched โ€” so like, don't generally use it, fr fr.

๐ŸŽ€ Reference file fields ๐ŸŽ€

Reference file fields ("/path".field) are suffixes you tack onto string literals, and they represent the inode metadata of arbitrary reference files โ€” all for the purpose of comparison. Important: there must be no spaces around the dot between the path string and the field name. None. Zero. Nada. ๐Ÿ’…

If the path is the empty string (e.g., "".field), then it refers to the most recent reference file path the parser encountered. This makes multiple references to the same reference file way more concise (e.g., "/path".exists && mtime > "".mtime). Iconic, honestly.

Here's the full lineup of reference file fields available ๐Ÿ‘‡

  • exists โ€” This is true (i.e., non-zero) when the reference file exists (i.e., when rh(1) can obtain its stat(2) structure information). Watch out though: a false value (i.e., zero) might be caused by permissions, not by the file not existing. ๐Ÿ™ˆ

If there's any chance the reference file does not exist, then exists must be used to determine whether the rest of the reference file fields can even be used. If a reference file doesn't exist and any of its stat(2) structure fields below get used in a file test expression, an error message will be output and rh(1) will terminate with a non-zero exit status. So exists is meant to be used with the ?: or || or && operator (see above) to conditionally use any of the following stat(2) structure fields (e.g., "/path".exists && size > "".size). ๐Ÿซถ

  • dev โ€” The device number of the device/filesystem that the reference file resides on. This is the st_dev field of the corresponding stat(2) structure. Peep the related major and minor reference file fields (next) for extracting the major and minor device numbers from dev.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • major โ€” The major device number of the device/filesystem that the reference file resides on. This is part of the st_dev field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • minor โ€” The minor device number of the device/filesystem that the reference file resides on. This is part of the st_dev field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • ino โ€” The inode number of the reference file. This is the st_ino field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • mode โ€” The mode of the reference file. This is the st_mode field of the corresponding stat(2) structure. It contains the file type and the permissions. See the related type and perm reference file fields (next) for extracting the file type and permissions from mode.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • type โ€” The file type of the reference file (i.e., an alias for mode & IFMT).

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • perm โ€” The file permissions of the reference file (i.e., an alias for mode & ~IFMT).

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • nlink โ€” The number of hard links to the reference file. This is the st_nlink field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • uid โ€” The user ID of the owner of the reference file. This is the st_uid field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • gid โ€” The group ID of the group of the reference file. This is the st_gid field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • rdev โ€” The device number of the reference file. This is the st_rdev field of the corresponding stat(2) structure. This one's only meaningful for character devices and block devices. See the related rmajor and rminor reference file fields (next) for extracting the major and minor device numbers from rdev.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • rmajor โ€” The major device number of the reference file. This is part of the st_rdev field of the corresponding stat(2) structure. Only meaningful for character devices and block devices.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • rminor โ€” The minor device number of the reference file. This is part of the st_rdev field of the corresponding stat(2) structure. Only meaningful for character devices and block devices.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • size โ€” The size in bytes of the reference file. This is the st_size field of the corresponding stat(2) structure. But okay, plot twist, it depends on what you're looking at ๐Ÿ‘€
    • For reference files that are symlinks (that aren't followed with the -y or -Y option), this is the length in bytes of the target path.
    • For reference files that are readable directories, this is not the st_size field of the corresponding stat(2) structure. Instead, it's the number of entries it contains (excluding . and ..).
    • For reference files that are unreadable directories (and everything else), this is the usual (undocumented) st_size field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • blksize โ€” The preferred block size for efficient I/O on the reference file's filesystem. This is the st_blksize field of the corresponding stat(2) structure. 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 reference file field (see next). Don't get them mixed up bestie ๐Ÿ’…

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • blocks โ€” The number of 512-byte blocks occupied by the reference file. This is the st_blocks field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • atime โ€” The time that the reference file was last accessed. This is the st_atime field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • mtime โ€” The time that the reference file's contents were last modified. This is the st_mtime field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • ctime โ€” The time that the reference file's inode was last changed. This is the st_ctime field of the corresponding stat(2) structure.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • attr โ€” On Linux, the reference file's ext2-style file attribute bit mask. Not all file systems support all file attributes. See chattr(1) and lsattr(1) for details. See /etc/rawhide.conf.d/attributes for all the related constants (e.g., ATTR_IMMUTABLE, ATTR_APPEND, โ€ฆ).

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

Note: If the reference file is a symlink, it is never followed for the purpose of obtaining the file attributes. This is a decision made by libe2p. This is not affected by the -y or -Y options. Also note that symlinks don't have their own file attributes (i.e., attr is zero). ๐Ÿ™ˆ

On Linux, this is only available on systems with libe2p. On Linux systems without libe2p, this is not a built-in symbol. Instead, it's a place-holder function that returns zero.

This is also available on FreeBSD, OpenBSD, NetBSD, and macOS. The attributes/flags that are available vary from system to system. See chflags(1) for details. See /etc/rawhide.conf.d/attributes for all the related predicate functions (e.g., uimmutable, uappend, nodump, opaque, โ€ฆ).

On other systems, this doesn't exist at all. ๐Ÿ’€

  • proj โ€” On Linux, the reference file's ext2-style file project number. Not all file systems support all file attributes. See chattr(1) and lsattr(1) for details.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

Note: If the reference file is a symlink, it is always followed for the purpose of obtaining the file project. This is not affected by the -y or -Y options.
Note: This is only available on Linux systems with libe2p. On other systems, this doesn't exist at all.
  • gen โ€” On Linux, the reference file's ext2-style version/generation number. Not all file systems support all file attributes. See chattr(1) and lsattr(1) for details.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

Note: If the reference file is a symlink, it is always followed for the purpose of obtaining the file version/generation. This is not affected by the -y or -Y options.
Note: This is only available on Linux systems with libe2p. On other systems, this doesn't exist at all.
  • strlen โ€” The length in bytes of the base name of the reference file. Note that this is zero if the reference file is the root directory (/), which has no base name. ๐ŸŽ€
  • inode โ€” An alias for ino.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • nlinks โ€” An alias for nlink.

It's an error to use this if the reference file doesn't exist. See exists above. ๐Ÿ’€

  • user โ€” An alias for uid.
  • group โ€” an alias for gid ๐Ÿ’…
โš ๏ธ It is an error to use this if the reference file does not exist. See exists above.
  • sz โ€” an alias for size โœจ
โš ๏ธ It is an error to use this if the reference file does not exist. See exists above.
  • accessed โ€” an alias for atime ๐Ÿ•
โš ๏ธ It is an error to use this if the reference file does not exist. See exists above.
  • modified โ€” an alias for mtime ๐Ÿ•
โš ๏ธ It is an error to use this if the reference file does not exist. See exists above.
  • changed โ€” an alias for ctime ๐Ÿ•
โš ๏ธ It is an error to use this if the reference file does not exist. See exists above.
  • len โ€” an alias for strlen ๐Ÿ“

๐ŸŽ€ EXAMPLES ๐ŸŽ€

okay bestie, this is the moment. this right here is the default /etc/rawhide.conf standard library, in all its glory. it's long, it's iconic, it ate, and you're gonna read it ๐Ÿ’–

# /etc/rawhide.conf: rh system-wide configuration.
# See rh(1) and rawhide.conf(5) for more information.

/*
This file constitutes the rawhide standard library.

On some systems, this file could be somewhere other than /etc
(e.g., /usr/local/etc, /opt/local/etc, /usr/pkg/etc).

You can modify this file, and/or place additional files
in the /etc/rawhide.conf.d directory.
They'll be read after this file, in lexicographic order.
Any files whose names start with dot are skipped.

You can also create your own user-specific rawhide
functions in ~/.rhrc and ~/.rhrc.d/*. They'll be read
by rh in the same manner after these system-wide files.
*/

// File types (canonical, aliases)

type { mode & IFMT }

reg  { type == IFREG }
dir  { type == IFDIR }
blk  { type == IFBLK }
chr  { type == IFCHR }
lnk  { type == IFLNK }
sock { type == IFSOCK }
fifo { type == IFIFO }
door { type == IFDOOR } # Solaris

file      { reg }
directory { dir }
block     { blk }
char      { chr }
device    { blk || chr }
symlink   { lnk }
link      { lnk }
socket    { sock }
namedpipe { fifo }
pipe      { fifo }

f { reg }
d { dir }
b { blk }
c { chr }
l { lnk }
s { sock }
p { fifo }
D { door } # Solaris

// File permissions (canonical, aliases, helpers)

perm { mode & ~IFMT }

ur { perm & IRUSR }
uw { perm & IWUSR }
ux { perm & IXUSR }
gr { perm & IRGRP }
gw { perm & IWGRP }
gx { perm & IXGRP }
or { perm & IROTH }
ow { perm & IWOTH }
ox { perm & IXOTH }
wr { perm & IROTH }
ww { perm & IWOTH }
wx { perm & IXOTH }

user_readable    { ur }
user_writable    { uw }
user_executable  { ux }
group_readable   { gr }
group_writable   { gw }
group_executable { gx }
other_readable   { or }
other_writable   { ow }
other_executable { ox }
world_readable   { wr }
world_writable   { ww }
world_executable { wx }

suid { perm & ISUID }
sgid { perm & ISGID }
svtx { perm & ISVTX }

setuid { suid }
setgid { sgid }
sticky { svtx }

all(bitmask)  { (perm & bitmask) == bitmask }
any(bitmask)  { perm & bitmask }
none(bitmask) { !any(bitmask) }

allr  { all(IRUSR | IRGRP | IROTH) }
allw  { all(IWUSR | IWGRP | IWOTH) }
allx  { all(IXUSR | IXGRP | IXOTH) }
anyr  { any(IRUSR | IRGRP | IROTH) }
anyw  { any(IWUSR | IWGRP | IWOTH) }
anyx  { any(IXUSR | IXGRP | IXOTH) }
noner { !anyr }
nonew { !anyw }
nonex { !anyx }

all_readable    { allr }
all_writable    { allw }
all_executable  { allx }
any_readable    { anyr }
any_writable    { anyw }
any_executable  { anyx }
none_readable   { noner }
none_writable   { nonew }
none_executable { nonex }

// Aliases for built-ins

inode    { ino }
nlinks   { nlink }
user     { uid }
group    { gid }
space    { blocks * 512 }
accessed { atime }
modified { mtime } # Refers to the contents
changed  { ctime } # Refers to the inode
len      { strlen }
sz       { size }

seconds  { second }
minutes  { minute }
hours    { hour }
days     { day }
weeks    { week }
months   { month }
years    { year }
quit     { exit }

imayread  { readable }   # Uses access(2) not perm
imaywrite { writable }   # Uses access(2) not perm
imayexec  { executable } # Uses access(2) not perm

ir { readable }
iw { writable }
ix { executable }

// File types for symlink targets (canonical, aliases)

ttype { tmode & IFMT }

treg  { ttype == IFREG }
tdir  { ttype == IFDIR }
tblk  { ttype == IFBLK }
tchr  { ttype == IFCHR }
tsock { ttype == IFSOCK }
tfifo { ttype == IFIFO }
tdoor { ttype == IFDOOR } # Solaris

tfile      { treg }
tdirectory { tdir }
tblock     { tblk }
tchar      { tchr }
tdevice    { tblk || tchr }
tsocket    { tsock }
tnamedpipe { tfifo }
tpipe      { tfifo }

tf { treg }
td { tdir }
tb { tblk }
tc { tchr }
ts { tsock }
tp { tfifo }
tD { tdoor } # Solaris

target_type { ttype }

target_reg  { treg }
target_dir  { tdir }
target_blk  { tblk }
target_chr  { tchr }
target_sock { tsock }
target_fifo { tfifo }
target_door { tdoor } # Solaris

target_file      { treg }
target_directory { tdir }
target_block     { tblk }
target_char      { tchr }
target_device    { tblk || tchr }
target_socket    { tsock }
target_namedpipe { tfifo }
target_pipe      { tfifo }

target_f { treg }
target_d { tdir }
target_b { tblk }
target_c { tchr }
target_s { tsock }
target_p { tfifo }
target_D { tdoor }

// File permissions for symlink targets (canonical, aliases, helpers)

tperm { tmode & ~IFMT }

tur { tperm & IRUSR }
tuw { tperm & IWUSR }
tux { tperm & IXUSR }
tgr { tperm & IRGRP }
tgw { tperm & IWGRP }
tgx { tperm & IXGRP }
tor { tperm & IROTH }
tow { tperm & IWOTH }
tox { tperm & IXOTH }
twr { tperm & IROTH }
tww { tperm & IWOTH }
twx { tperm & IXOTH }

target_perm { tperm }

target_ur { tur }
target_uw { tuw }
target_ux { tux }
target_gr { tgr }
target_gw { tgw }
target_gx { tgx }
target_or { tor }
target_ow { tow }
target_ox { tox }
target_wr { twr }
target_ww { tww }
target_wx { twx }

target_user_readable    { tur }
target_user_writable    { tuw }
target_user_executable  { tux }
target_group_readable   { tgr }
target_group_writable   { tgw }
target_group_executable { tgx }
target_other_readable   { tor }
target_other_writable   { tow }
target_other_executable { tox }
target_world_readable   { twr }
target_world_writable   { tww }
target_world_executable { twx }

tsuid { tperm & ISUID }
tsgid { tperm & ISGID }
tsvtx { tperm & ISVTX }

tsetuid { tsuid }
tsetgid { tsgid }
tsticky { tsvtx }

target_suid { tsuid }
target_sgid { tsgid }
target_svtx { tsvtx }

target_setuid { tsuid }
target_setgid { tsgid }
target_sticky { tsvtx }

tall(bitmask)  { (tperm & bitmask) == bitmask }
tany(bitmask)  { tperm & bitmask }
tnone(bitmask) { !tany(bitmask) }

tallr  { tall(IRUSR | IRGRP | IROTH) }
tallw  { tall(IWUSR | IWGRP | IWOTH) }
tallx  { tall(IXUSR | IXGRP | IXOTH) }
tanyr  { tany(IRUSR | IRGRP | IROTH) }
tanyw  { tany(IWUSR | IWGRP | IWOTH) }
tanyx  { tany(IXUSR | IXGRP | IXOTH) }
tnoner { !tanyr }
tnonew { !tanyw }
tnonex { !tanyx }

target_all(bitmask)  { tall(bitmask) }
target_any(bitmask)  { tany(bitmask) }
target_none(bitmask) { tnone(bitmask) }

target_allr  { tallr }
target_allw  { tallw }
target_allx  { tallx }
target_anyr  { tanyr }
target_anyw  { tanyw }
target_anyx  { tanyx }
target_noner { tnoner }
target_nonew { tnonew }
target_nonex { tnonex }

target_all_readable    { tallr }
target_all_writable    { tallw }
target_all_executable  { tallx }
target_any_readable    { tanyr }
target_any_writable    { tanyw }
target_any_executable  { tanyx }
target_none_readable   { tnoner }
target_none_writable   { tnonew }
target_none_executable { tnonex }

// Aliases for symlink target built-ins

target_exists  { texists }
target_dev     { tdev }
target_ino     { tino }
target_mode    { tmode }
target_nlink   { tnlink }
target_uid     { tuid }
target_gid     { tgid }
target_rdev    { trdev }
target_size    { tsize }
target_blksize { tblksize }
target_blocks  { tblocks }
target_atime   { tatime }
target_mtime   { tmtime }
target_ctime   { tctime }
target_strlen  { tstrlen }

tinode    { tino }
tnlinks   { tnlink }
tuser     { tuid }
tgroup    { tgid }
tspace    { tblocks * 512 }
taccessed { tatime }
tmodified { tmtime } # Refers to the target's contents
tchanged  { tctime } # Refers to the target's inode
tlen      { tstrlen }
tsz       { tsize }

target_inode    { tinode }
target_nlinks   { tnlinks }
target_user     { tuser }
target_group    { tgroup }
target_space    { tspace }
target_accessed { taccessed }
target_modified { tmodified }
target_changed  { tchanged }
target_len      { tlen }
target_sz       { tsz }

// Aliases for size units

KiB { 1K }
MiB { 1M }
GiB { 1G }
TiB { 1T }
PiB { 1P }
EiB { 1E }

KB { 1k }
MB { 1m }
GB { 1g }
TB { 1t }
PB { 1p }
EB { 1e }

// Miscellaneous helper functions

empty { size == 0 } # Only for files and readable directories
roots { uid == 0 }
mine { uid == $$ }
ours { gid == @@ }
broken { symlink && !texists }
dangling { broken }
gmtoday { now - now % day }

ago(sec)  { now - sec }
old(sec)  { mtime <= ago(sec) }
past(sec) { mtime >= ago(sec) }

min(x, y) { (x < y) ? x : y }
max(x, y) { (x > y) ? x : y }
abs(x)    { (x < 0) ? -x : x }

// Examples

๐Ÿฆ‹ that's the whole standard library bestie, every single alias, every perm helper, every target_* twin, every size unit, every lil miscellaneous function โ€” no cap, it's all there ๐Ÿ’… and the actual examples are literally about to start, so don't you dare close the tab ๐Ÿซถ

// Complex tests can be very readable (and/or very concise)
// rootshell { setuid && roots && (group_writable | other_writable) }
// rootshell { suid && roots && (gw | ow) }
// csrc { "*.[ch]" || "[Mm]akefile" }
// oldaout { "a.out" && f && old(week) }
// oldcore { "^core(\.\d+)?$".re && f && old(week) }
// bad { oldaout || oldcore || "*.BAK" || "*.CKP" || (gw | ow) }
// Note: Read "<" and ">" as before/after, not as less/more than
// recent { modified >= ago(hour) }

// Silly recursive functions are possible (but not mutual recursion)
// sqrt1(n, odd) { (n <= 0) ? 0 : sqrt1(n - odd, odd + 2) + 1 }
// sqrt(n) { sqrt1(n, 1) }
// 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) }
// factorial(x) { (x > 0) ? x * factorial(x - 1) : 1 }
// ack(x, y) { !x ? y+1 : !y ? ack(x-1, 1) : ack(x-1, ack(x, y-1)) }

okay but these are actually so iconic ๐Ÿ’… The manpage literally says complex tests can be very readable (and/or very concise) โ€” so like, both is allowed, bestie. Look at rootshell { setuid && roots && (group_writable | other_writable) } โ€” that's main character energy, so readable. And then the same thing in its little crop-top version: rootshell { suid && roots && (gw | ow) }. The way that brevity ate. ๐Ÿ”ฅ

And there's a note in there you need to clock: read < and > as before/after, not as less/more than. So recent { modified >= ago(hour) } is "modified at-or-after an hour ago", not some number-comparison situation. iykyk. ๐ŸŽ€

Then the silly recursive functions โ€” yes, they're a thing, and the manpage's own phrasing is "Silly recursive functions are possible (but not mutual recursion)". So: recursion slays, mutual recursion is not invited to the group chat. ๐Ÿ’€ And ack(x, y) { !x ? y+1 : !y ? ack(x-1, 1) : ack(x-1, ack(x, y-1)) } โ€” the Ackermann function is just casually sitting in your config file like it pays rent. Obsessed.

(Also notice: those example lines are all sitting behind // in the file. They're there so you can read them, not so they run. ๐ŸŒธ)


Now the brevity era:

The following aliases provide greater brevity for some built-ins. Perhaps they could have gone into the standard library, but you might have better uses for these identifiers.

translation: these names are yours, so don't come crying if you wanted w for something else ๐Ÿซถ

// Brevity (aliases)

i { ino }
n { nlink }
u { uid }
g { gid }
a { atime }
m { mtime }

su { suid }
sg { sgid }

nu { nouser }
ng { nogroup }

r { readable }
w { writable }
x { executable }

q { exit }

sec { second }
mn  { minute }
hr  { hour }
dy  { day }
wk  { week }
mth { month }
yr  { year }

Not q { exit } being one whole letter for exit ๐Ÿ˜ญ literally the shortest goodbye ever. And the time units are all lined up so cute: sec, mn, hr, dy, wk, mth, yr. It's giving organized. โœจ


๐Ÿ–ฅ๏ธ the Linux attribute library ๐Ÿง

On Linux systems with libe2p, this is the /etc/rawhide.conf.d/attributes library:

# /etc/rawhide.conf.d/attributes (Linux)
# See rh(1) and rawhide.conf(5) for more information.

/*
This file defines aliases for the built-in symbols for the
Linux ext2-style file attributes, and the related constants
and predicates. See <e2p/e2p.h> and <ext2fs/ext2_fs.h>.
*/

// Aliases for the Linux ext2-style attribute built-ins

flag       { attr }
flags      { attr }
attrs      { attr }
attribute  { attr }
attributes { attr }
project    { proj }
generation { gen }

// Attribute constants from <ext2fs/ext2_fs.h>

ATTR_SECRM            { 0x00000001 } # Secure deletion
ATTR_UNRM             { 0x00000002 } # Undelete
ATTR_COMPR            { 0x00000004 } # Compress file
ATTR_SYNC             { 0x00000008 } # Synchronous updates
ATTR_IMMUTABLE        { 0x00000010 } # Immutable file
ATTR_APPEND           { 0x00000020 } # Writes to file may only append
ATTR_NODUMP           { 0x00000040 } # Do not dump file
ATTR_NOATIME          { 0x00000080 } # Do not update atime
ATTR_DIRTY            { 0x00000100 }
ATTR_COMPRBLK         { 0x00000200 } # One or more compressed clusters
ATTR_NOCOMPR          { 0x00000400 } # Access raw compressed data
ATTR_ENCRYPT          { 0x00000800 } # Encrypted
ATTR_BTREE            { 0x00001000 } # Btree format directory
ATTR_INDEX            { 0x00001000 } # Hash-indexed directory
ATTR_IMAGIC           { 0x00002000 }
ATTR_JOURNAL_DATA     { 0x00004000 } # File data should be journaled
ATTR_NOTAIL           { 0x00008000 } # File tail should not be merged
ATTR_DIRSYNC          { 0x00010000 } # Synchronous dir modifications
ATTR_TOPDIR           { 0x00020000 } # Top of directory hierarchies
ATTR_HUGE_FILE        { 0x00040000 } # Set to each huge file
ATTR_EXTENTS          { 0x00080000 } # Inode uses extents
ATTR_VERITY           { 0x00100000 } # Verity protected inode
ATTR_EA_INODE         { 0x00200000 } # Inode used for large EA
ATTR_NOCOW            { 0x00800000 } # Do not cow file
ATTR_SNAPFILE         { 0x01000000 } # Inode is a snapshot
ATTR_DAX              { 0x02000000 } # Inode is DAX
ATTR_SNAPFILE_DELETED { 0x04000000 } # Snapshot is being deleted
ATTR_SNAPFILE_SHRUNK  { 0x08000000 } # Snapshot shrink has completed
ATTR_INLINE_DATA      { 0x10000000 } # Inode has inline data
ATTR_PROJINHERIT      { 0x20000000 } # Create with parents projid
ATTR_CASEFOLD         { 0x40000000 } # Casefolded file

// Attribute predicates (canonical, aliases)

secrm            { attr & ATTR_SECRM }
unrm             { attr & ATTR_UNRM }
compr            { attr & ATTR_COMPR }
sync             { attr & ATTR_SYNC }
immutable        { attr & ATTR_IMMUTABLE }
append           { attr & ATTR_APPEND }
nodump           { attr & ATTR_NODUMP }
noatime          { attr & ATTR_NOATIME }
dirty            { attr & ATTR_DIRTY }
comprblk         { attr & ATTR_COMPRBLK }
nocompr          { attr & ATTR_NOCOMPR }
encrypt          { attr & ATTR_ENCRYPT }
btree            { attr & ATTR_BTREE }
index            { attr & ATTR_INDEX }
imagic           { attr & ATTR_IMAGIC }
journal_data     { attr & ATTR_JOURNAL_DATA }
notail           { attr & ATTR_NOTAIL }
dirsync          { attr & ATTR_DIRSYNC }
topdir           { attr & ATTR_TOPDIR }
huge_file        { attr & ATTR_HUGE_FILE }
extents          { attr & ATTR_EXTENTS }
verity           { attr & ATTR_VERITY }
ea_inode         { attr & ATTR_EA_INODE }
nocow            { attr & ATTR_NOCOW }
snapfile         { attr & ATTR_SNAPFILE }
dax              { attr & ATTR_DAX }
snapfile_deleted { attr & ATTR_SNAPFILE_DELETED }
snapfile_shrunk  { attr & ATTR_SNAPFILE_SHRUNK }
inline_data      { attr & ATTR_INLINE_DATA }
projinherit      { attr & ATTR_PROJINHERIT }
casefold         { attr & ATTR_CASEFOLD }

secure_deletion  { secrm }
undelete         { unrm }
compressed       { compr }
synchronous      { sync }
append_only      { append }
nocompression    { nocompr }
encrypted        { encrypt }
indexed          { index }
direct_access    { dax }
inherit_project  { projinherit }

attr_s { secrm }
attr_u { unrm }
attr_c { compr }
attr_S { sync }
attr_i { immutable }
attr_a { append }
attr_d { nodump }
attr_A { noatime }
attr_m { nocompr }
attr_E { encrypt }
attr_I { index }
attr_j { journal_data }
attr_t { notail }
attr_D { dirsync }
attr_T { topdir }
attr_e { extents }
attr_V { verity }
attr_C { nocow }
attr_x { dax }
attr_N { inline_data }
attr_P { projinherit }
attr_F { casefold }

three layers of aliasing, bestie โ€” the canonical predicates, the โœจfancy long namesโœจ like append_only and inherit_project, and then the single-letter attr_* ones for when you're typing with one hand and holding a boba with the other. ๐Ÿง‹

Real talk: don't retype these from memory, copy-paste exists. The hex values are literally character-for-character facts and one flipped digit is a whole different attribute. ๐Ÿ˜ญ

๐Ÿก the FreeBSD attribute library ๐Ÿฆ‹

On FreeBSD systems, this is the /etc/rawhide.conf.d/attributes library. There is a similar file on OpenBSD, NetBSD, and macOS systems:

# /etc/rawhide.conf.d/attributes (FreeBSD)
# See rh(1) and rawhide.conf(5) for more information.

/*
This file defines aliases for the built-in symbols for FreeBSD file
flags, and the related constants and predicates. See <sys/stat.h>.
*/

// Aliases for the FreeBSD file flags built-ins

flag       { attr }
flags      { attr }
attrs      { attr }
attribute  { attr }
attributes { attr }
project    { 0 }
generation { 0 }

// Flag constants from <sys/stat.h>

# Super-user and owner changeable flags

UF_SETTABLE  { 0x0000ffff }  # mask of owner changeable flags
UF_NODUMP    { 0x00000001 }  # do not dump file
UF_IMMUTABLE { 0x00000002 }  # file may not be changed
UF_APPEND    { 0x00000004 }  # writes to file may only append
UF_OPAQUE    { 0x00000008 }  # directory is opaque wrt. union

UF_NOUNLINK  { 0x00000010 }  # file may not be removed or renamed
UF_SYSTEM    { 0x00000080 }  # Windows system file bit
UF_SPARSE    { 0x00000100 }  # sparse file
UF_OFFLINE   { 0x00000200 }  # file is offline
UF_REPARSE   { 0x00000400 }  # Windows reparse point file bit
UF_ARCHIVE   { 0x00000800 }  # file needs to be archived
UF_READONLY  { 0x00001000 }  # Windows readonly file bit
UF_HIDDEN    { 0x00008000 }  # file is hidden [same as macOS]

# Super-user changeable flags

SF_SETTABLE  { 0xffff0000 }  # mask of superuser changeable flags
SF_ARCHIVED  { 0x00010000 }  # file is archived
SF_IMMUTABLE { 0x00020000 }  # file may not be changed
SF_APPEND    { 0x00040000 }  # writes to file may only append
SF_NOUNLINK  { 0x00100000 }  # file may not be removed or renamed
SF_SNAPSHOT  { 0x00200000 }  # snapshot inode

// Flag predicates (canonical, aliases)

arch       { attr & SF_ARCHIVED }
archived   { attr & SF_ARCHIVED }
nodump     { attr & UF_NODUMP }
opaque     { attr & UF_OPAQUE }
sappnd     { attr & SF_APPEND }
sappend    { attr & SF_APPEND }
schg       { attr & UF_IMMUTABLE }
schange    { attr & UF_IMMUTABLE }
simmutable { attr & UF_IMMUTABLE }
snapshot   { attr & SF_SNAPSHOT }
sunlnk     { attr & SF_NOUNLINK }
sunlink    { attr & SF_NOUNLINK }
uappnd     { attr & UF_APPEND }
uappend    { attr & UF_APPEND }
uarch      { attr & UF_ARCHIVE }
uarchive   { attr & UF_ARCHIVE }
uchg       { attr & SF_IMMUTABLE }
uchange    { attr & SF_IMMUTABLE }
uimmutable { attr & SF_IMMUTABLE }
uhidden    { attr & UF_HIDDEN }
hidden     { attr & UF_HIDDEN }
uoffline   { attr & UF_OFFLINE }
offline    { attr & UF_OFFLINE }
urdonly    { attr & UF_READONLY }
rdonly     { attr & UF_READONLY }
readonly   { attr & UF_READONLY }
usparse    { attr & UF_SPARSE }
sparse     { attr & UF_SPARSE }
usystem    { attr & UF_SYSTEM }
system     { attr & UF_SYSTEM }
ureparse   { attr & UF_REPARSE }
reparse    { attr & UF_REPARSE }
uunlnk     { attr & UF_NOUNLINK }
uunlink    { attr & UF_NOUNLINK }

append    { uappend    | sappend }
immutable { uimmutable | simmutable }
nounlink  { uunlink    | sunlink }
unlink    { uunlink    | sunlink }

Not the project { 0 } and generation { 0 } โ€” FreeBSD said "no projid era for me, have a zero ๐Ÿซถ" and honestly, respect. And at the bottom it does the cute thing where append, immutable, nounlink and unlink just OR both the u and s versions together so you don't have to care which one it is. Convenience queen behavior. ๐Ÿ‘‘


See rh(1) for more examples.

๐Ÿ“ FILES ๐Ÿ“

okay so these are the source/config files that get read by default, bestie:

  • /etc/rawhide.conf โ€” the main system-wide configuration
  • /etc/rawhide.conf.d/* โ€” extra system-wide configuration
  • ~/.rhrc โ€” the main user-specific configuration
  • ~/.rhrc.d/* โ€” extra user-specific configuration

plot twist: the system-wide config might live somewhere else depending on your operating systemโ€™s preferences, like /usr/local/etc, /opt/local/etc, or /usr/pkg/etc. very โ€œyour mileage may varyโ€ of it. ๐Ÿ’…

the output of rh --help tells you where the system-wide configuration files are on your local system, so if youโ€™re ever like โ€œwhere did they put it ๐Ÿ˜ญโ€, thatโ€™s the move.

the location of the main system-wide configuration file (/etc/rawhide.conf, or similar) can be overridden with the environment variable RAWHIDE_CONFIG. heads up though: 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.

same energy for the user side: 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 too, as it could be dangerous for root. and the directory containing any additional user-specific configuration files is derived from it by appending .d. consistent queen behavior tbh โœจ


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

go off and read these too, bestie: rh(1), stat(2), mktime(3), fnmatch(3), glob(7), perlre(1), pcre2pattern(3), pcre2syntax(3), access(2), acl(2) or acl(3) or acl(5), xattr(1) or xattr(7) or extattr(2) or fsattr(7), chattr(1), lsattr(1), chflags(1), file(1), jq(1), and The C programming language. the lore is deep ๐ŸŒˆ


๐Ÿ’– AUTHORS ๐Ÿ’–

  • 1990 โ€” Ken Stauffer (University of Calgary)
  • 2022-2023 โ€” raf <raf@raf.org>

and thatโ€™s the whole page, bestie ๐Ÿซถ this lil manual moment is from rawhide-3.3, dated 20231013 โ€” iconic, documented, and done. ๐Ÿ’…โœจ

๐Ÿ“ฆ where this page comes from