YASH(1)

YASH(1) โ€” the manual page for yash ๐ŸŽ€

okay bestie, welcome to the yash manual, section 1 of the manual pages. buckle up, this one's a whole journey and we're doing it together ๐Ÿ’–


โœจ NAME โœจ

yash โ€” a POSIX-compliant command line shell ๐Ÿš


โœจ SYNOPSIS โœจ

yash [options...] [--] [operands...]

๐ŸŒธ INTRODUCTION ๐ŸŒธ

Yet another shell (yash) is a command line shell for UNIX-like operating systems, no cap. It conforms to the POSIX.1-2024 standard (for the most parts, bestie, most), and it is literally aiming to be more precise than the other shells out there that claim to be POSIX-compliant. the audacity of those other shells fr fr ๐Ÿ˜ญ. and on top of all that correctness?? it also has a bunch of features for interactive use, like command history and command line editing with completion. it's giving main character energy honestly ๐Ÿ‘‘

okay so legal stuff but make it cute:

  • This program can be freely modified and redistributed under the terms of the GNU General Public License (Version 2).
  • Use of this program is all at your own risk. There is no warranty and the author is not responsible for any consequences caused by use of this program. (bestie. be careful. that's the whole vibe ๐Ÿ’€)
  • This manual can be freely modified and redistributed under the terms of Creative Commons Attribution-ShareAlike 2.1 Japan.

Yash is developed and maintained by ๆธก้‚Š่ฃ•่ฒด (WATANABE Yuki) aka Magicant. The source code repository and website are hosted by GitHub. iconic behavior, we love a maintainer ๐Ÿซถ


๐Ÿ’… INVOCATION ๐Ÿ’…

When it's invoked as a program, yash performs the predefined initialization steps and then repeatedly reads and executes commands. The command line arguments you give it at invocation time decide how the shell initializes itself and how it executes commands. so like, first impressions matter โœจ

๐Ÿฆ‹ Command line arguments ๐Ÿฆ‹

The syntax of command line arguments for yash conforms to POSIX, periodt. As defined in POSIX, arguments are separated into options and operands. For a more detailed explanation about options and operands, see Command argument syntax. All options must come before operands. The interpretation of operands depends on which options you specified. no mixing, bestie, the order is the order ๐Ÿšซ

When you specify the -c (--cmdline) option, you must give at least one operand. The shell interprets and executes the first operand as a command string. The second operand, if any, is used to initialize the 0 special parameter. The other operands, if any, are used to initialize the positional parameters. When the -c (--cmdline) option is specified, the shell does not read any file or the standard input (unless the dot built-in is used). okay so like, it's got trust issues, it only runs what you hand it ๐Ÿ™ˆ

If you specify the -s (--stdin) option, the shell reads the standard input, interprets the input as commands, and executes them. All the operands given are used to initialize the positional parameters. The 0 special parameter is initialized to the name the shell is invoked as. ๐Ÿ’ซ

If you specify neither the -c (--cmdline) nor the -s (--stdin) option, the shell reads a file, interprets the file contents as commands, and executes them. The first operand specifies the pathname of the file. The remaining operands are used to initialize the positional parameters. If you do not give any operands, the shell reads the standard input as if the -s (--stdin) option is specified. it just defaults, slay ๐Ÿ’…

You cannot use both the -c (--cmdline) and -s (--stdin) options at a time. pick a lane bestie ๐Ÿšจ

If you specify either the --help or the -V (--version) option, the shell never performs the usual initialization or command execution. Instead, it just prints brief usage (for --help) or version information (for -V and --version). And if the -V (--version) option is accompanied by the -v (--verbose) option, the shell also prints a list of the available optional features as well. โœจ little treat, as a bonus โœจ

If you specify the -i (--interactive) option, the shell goes into the interactive mode. If you specify the +i (++interactive) option, conversely, the shell never goes into the interactive mode. The interactive mode is automatically enabled if all the following conditions are met:

  • The +i (++interactive) option is not specified.
  • The -s (--stdin) option is specified, explicitly or implicitly.
  • The standard input is connected to a terminal.
  • The standard error is connected to a terminal.

If you specify the -l (--login) option, the shell behaves as a login shell. ๐ŸŽ€

The --noprofile, --norcfile, --profile, and --rcfile options determine how the shell is initialized (see below for details). ๐Ÿ‘€

In addition to the options described above, you can also specify options that can be specified to the set built-in. yes bestie, they all just vibe together ๐Ÿ’–

If the first operand is - and the options and the operands are not separated by --, the first operand is ignored. sneaky little dash. ignored. bye ๐Ÿ‘‹

๐ŸŒˆ Initialization of yash ๐ŸŒˆ

Yash initializes itself as follows:

  1. Yash first parses the name it was invoked as. If the name starts with -, the shell behaves as a login shell. If the name is sh (including names such as /bin/sh), the shell goes into the POSIXly-correct mode.
  2. If no operands are given and the standard input and standard error are both connected to a terminal, the shell goes into the interactive mode unless the +i (++interactive) option is specified.
  3. Job control is automatically enabled in an interactive shell unless the +m (++monitor) option is specified.
  4. Yash reads and executes commands from the following files (unless the real and effective user IDs of the shell process are different or the real and effective group IDs of the shell process are different):
    1. If it is behaving as a login shell, the shell reads the file specified by the --profile=filename option unless the --noprofile option is specified or the shell is in the POSIXly-correct mode.

If the --profile=filename option is not specified, the shell reads $XDG_CONFIG_HOME/yash/profile or ~/.yash_profile as a fallback.

  1. If in the interactive mode, the shell reads the file specified by the --rcfile=filename option unless the --norcfile option is specified.

If the --rcfile=filename option is not specified, the shell instead reads the following files:

  • If not in the POSIXly-correct mode, the shell reads $XDG_CONFIG_HOME/yash/rc or ~/.yashrc as a fallback. If it cannot be read, the shell searches YASH_LOADPATH for a file named initialization/default.
  • If in the POSIXly-correct mode, the shell performs parameter expansion on the value of the ENV environment variable and treats the expansion result as the name of the file to read.

If the XDG_CONFIG_HOME environment variable is not set, ~/.config is used as the default location. ๐Ÿ’ซ

Note ๐ŸŽ€ Yash never automatically reads /etc/profile, /etc/yashrc, nor ~/.profile. not once. not ever. don't come for her ๐Ÿšซ

๐Ÿงš SYNTAX ๐Ÿงš

The shell reads, parses, and executes command line by line. If there is more than one command on a line, all the commands are parsed before executed. If a command is continued to next lines, the shell reads more enough lines to complete the command. On a syntax error, the shell neither reads nor executes any more commands. like, we're done here, goodbye ๐Ÿ˜ญ

โญ Tokens and keywords โญ

A command is composed of one or more tokens. In the shell syntax, a token is a word that is part of a command. Normally, tokens are separated by whitespaces, that is, the space or tab character. Whitespaces inside a command substitution or a parameter expansion, however, do not separate tokens. iykyk ๐Ÿ‘‘

The following symbols have special meanings in the shell syntax and in most cases separate tokens:

; & | < > ( ) [newline]

The following symbols do not separate tokens, but they have syntactic meanings:

$ ` \ " ' * ? [ # ~ = %

The following tokens are treated as keywords depending on the context in which they appear:

! { } [[ case do done elif else esac fi
for function if in then until while

A token is treated as a keyword when:

  • it is the first token of a command,
  • it follows another keyword (except case, for, and in), or
  • it is a non-first token of a command and is supposed to be a keyword to compose a composite command.

If a token begins with #, then the # and any following characters up to the end of the line are treated as a comment, which is completely ignored in syntax parsing. ghosted. gone. never happened ๐Ÿ’€

๐Ÿ“ Quotations ๐Ÿ“

If you want whitespaces, separator characters, or keywords described above to be treated as normal characters, you must quote the characters using appropriate quotation marks. Quotation marks are not treated as normal characters unless they are themselves quoted. You can use the following three quotation marks:

  • A backslash (\) quotes a character that immediately follows.

The only exception about a backslash is the case where a backslash is followed by a newline. In this case, the two characters are treated as a line continuation rather than a newline being quoted. The two characters are removed from the input and the two lines surrounding the line continuation are concatenated into a single line. โœจ seamless โœจ

  • A pair of single-quotation marks (') quote any characters between them. Note that newlines can be quoted using single-quotations. Single-quotations cannot quote a single-quotation mark itself unless the mark is escaped in a dollar-single-quoted string. it's giving "no u" ๐Ÿ˜ญ
  • Double-quotation marks (") are like single-quotations, but they have a few exceptions: Parameter expansion, command substitution, and arithmetic expansion are interpreted as usual even between double-quotations. A backslash between double-quotations is treated as a quotation mark only when it is followed by $, `, ", \, or a newline; other backslashes are treated as normal characters. selective, and honestly? valid ๐Ÿ’…

๐Ÿง Dollar-single-quotes ๐Ÿง

Dollar-single-quotes are a special form of quotation introduced by $' and terminated by '. This is similar to normal single-quotation marks, but the following backslash escapes are recognized in the quoted string:

  • \", \', \\ โ€” Represents literal ", ', and \, respectively. Note that a single-quotation mark escaped by a backslash does not terminate the dollar-single-quoted string.
  • \a, \b, \e, \f, \n, \r, \t, \v โ€” Represents the alert (bell), backspace, escape, form feed, newline, carriage return, horizontal tab, and vertical tab characters, respectively.
  • \c โ€” Represents the control character corresponding to a character that follows. For example, \cA represents the SOH character, which can be entered by typing Ctrl-A. The null character (Ctrl-@, \c@) is not supported. To specify the FS character (Ctrl-\), the following backslash must also be escaped as in \c\\ rather than \c\. so specific, so particular, we love her ๐ŸŽ€
  • \x โ€” Represents the character whose value is specified by hexadecimal digits that follow. For example, \x20 represents the character that has the value 20 in hexadecimal. There must be at least one digit that follows \x and at most two digits are recognized. hex girl summer ๐ŸŒธ
  • \0, \1, \2, ..., \377 โ€” this one's the character whose value is the octal digits you typed. Gotta have at least one digit, max three digits get recognized, and bestie the value must fit in 8 bits, no exceptions ๐Ÿ’…

Any other use of backslashes? Yeah that's an error, and it gets converted into the ? character. Rude but true.

โš ๏ธ Note โš ๏ธ The value from a hexadecimal or octal digit escape is interpreted as a wide character codepoint. This behavior does not conform to POSIX โ€” POSIX requires that value be treated as a raw byte. So like, if you're being POSIX-strict, this is not it. ๐Ÿ™ˆ

๐ŸŽ€ Aliases ๐ŸŽ€

Tokens that compose a command are subject to alias substitution, periodt. If a token matches the name of an alias that's already been defined, it gets swapped for the alias's value before the command is parsed. Pre-parse glow-up โœจ

Tokens that contain quotations are not alias-substituted, because an alias name literally cannot contain quotation marks. Keywords and command separator characters? Also not alias-substituted. They're above it all, they don't play that game ๐Ÿ’…

There are two kinds of aliases, and yes we're ranking them:

  • Normal aliases โ€” can only substitute the first token of a command.
  • Global aliases โ€” can substitute any part of a command. These are a yash extension that is not defined in POSIX. Iconic but chaotic ๐Ÿฆ‹

And here's the fun bit: if a token gets alias-substituted with the value of a normal alias that ends with a whitespace, then the next token is exceptionally subject to alias substitution for normal aliases too. Chain reaction energy ๐Ÿ‘‘

The results of alias substitution are again subject to alias substitution for other aliases โ€” but not for the aliases that have already been applied. No infinite loops here bestie, we have standards ๐Ÿซถ

You can define aliases using the alias built-in, and remove them using the unalias built-in. Simple as. โœจ

๐ŸŒธ Simple commands ๐ŸŒธ

A command that does not start with a keyword token is a simple command. Simple commands are executed as defined in Execution of simple commands.

If the first token and any number of the following tokens of a simple command have the form name=value, they get interpreted as variable assignments. A variable name must consist of one or more alphabets, digits and/or underlines (_), and must not start with a digit. (We love a naming convention with boundaries ๐ŸŽ€)

The first token that is not a variable assignment is considered the command name, and all the following tokens โ€” whether or not they have the form name=value โ€” are command arguments. Like, once the command name shows up, everything after it is just along for the ride ๐Ÿš—๐Ÿ’จ

A variable assignment of the form var=(tokens) is interpreted as assignment to an array. You can write any number of tokens between a pair of parentheses. And the tokens can be separated by not only spaces and tabs but also newlines. Multi-line array assignment, main character energy โœจ

๐Ÿ’œ Pipelines ๐Ÿ’œ

A pipeline is a sequence of one or more simple commands, compound commands, and/or function definitions that are separated by |.

If a pipeline has more than one subcommand, it's executed by running each subcommand of the pipeline in a subshell simultaneously. The standard output of each subcommand except the last one is redirected to the standard input of the next subcommand. The standard input of the first subcommand and the standard output of the last subcommand are not redirected. Relay race but make it Unix ๐Ÿƒโ™€๏ธ๐Ÿƒโ™€๏ธ

The exit status of the pipeline is that of the last subcommand โ€” unless the pipe-fail option is enabled, in which case the exit status of the pipeline is that of the last subcommand that exits with a non-zero exit status. And if all the subcommands exit with an exit status of zero, then the pipeline's exit status is also zero. Balanced. Slay. ๐Ÿ’…

A pipeline can be prefixed by !, in which case the exit status of the pipeline is reversed: the exit status of the pipeline is 1 if that of the last subcommand is 0, and 0 otherwise. Gaslight gatekeep girlboss โœจ

Korn shell treats a word of the form !(...) as an extended pathname expansion pattern that is not defined in POSIX. In the POSIXly-correct mode, the tokens ! and ( must be separated by one or more white spaces. No cap.

Note ๐Ÿฆ‹ When the execution of a pipeline finishes, at least the execution of the last subcommand has finished, since the exit status of the last subcommand defines that of the whole pipeline. The execution of other subcommands, however, may not have finished by then. On the other hand, the execution of the pipeline may not finish soon after that of the last subcommand finished, because the shell may choose to wait for the execution of other subcommands to finish. So likeโ€ฆ it's giving ambiguous timing. That's just how it is bestie ๐Ÿ˜ญ
Note ๐Ÿฆ‹ The POSIX standard allows executing any of the subcommands in the current shell rather than subshells, though yash does not do so. yash said no thank you ๐Ÿ’…

๐Ÿ“ And/or lists ๐Ÿ“

An and/or list is a sequence of one or more pipelines separated by && or ||.

An and/or list is executed by executing some of the pipelines conditionally. The first pipeline is always executed. The other pipelines are either executed or not, according to the exit status of the previous pipelines. Conditional queen ๐Ÿ‘‘

  • If two pipelines are separated by && and the exit status of the first pipeline is zero, the second pipeline is executed.
  • If two pipelines are separated by || and the exit status of the first pipeline is not zero, the second pipeline is executed.
  • In other cases, the execution of the and/or list ends: the second and any remaining pipelines are not executed. That's a wrap ๐ŸŽฌ

The exit status of an and/or list is that of the last pipeline that was executed.

Normally, an and/or list must be terminated by a semicolon, ampersand, or newline. See Command separators and asynchronous commands. (Yes we're going there next, keep up ๐Ÿซถ)

๐ŸŒˆ Command separators and asynchronous commands ๐ŸŒˆ

The whole input to the shell must be composed of any number of and/or lists separated by a semicolon or ampersand. A terminating semicolon can be omitted if it is followed by ;;, ), or a newline. Otherwise, an and/or list must be terminated by a semicolon or ampersand. Rules are rules โœจ

If an and/or list is terminated by a semicolon, it's executed synchronously: the shell waits for the and/or list to finish before executing the next and/or list. Patient. Respectful. ๐Ÿ’…

If an and/or list is terminated by an ampersand, it's executed asynchronously: after the execution of the and/or list is started, the next and/or list is executed immediately. An asynchronous and/or list is always executed in a subshell, and its exit status is zero. Go off, do your own thing, I'm not waiting ๐Ÿฆ‹

If the shell is not doing job control, the standard input of an asynchronous and/or list is automatically redirected to /dev/null. And the signal handlers of the and/or list for the SIGINT and SIGQUIT signals are set to "ignore" the signal, so that the execution of the and/or list cannot be stopped by those signals. Untouchable. Literally ๐Ÿ˜Œ

When the execution of an asynchronous and/or list is started, the shell remembers its process ID. If the shell is interactive, the job number and process ID of the asynchronous list are printed to the standard error. You can obtain the process ID by referencing the ! special parameter. You can also examine the current state and exit status of the asynchronous list by using the jobs and wait built-ins. Bookkeeping besties ๐ŸŽ€

๐Ÿ‘‘ Compound commands ๐Ÿ‘‘

Compound commands provide you with programmatic control of shell command execution. This is where it gets good โœจ

๐Ÿ’– Grouping ๐Ÿ’–

A grouping is a list of commands that is treated as a simple command.

Normal grouping syntax

{ command...; }

Subshell grouping syntax

(command...)

The { and } tokens are keywords, which must be separated from other tokens. The ( and ) tokens, however, are special separators that need not be separated. They're chill like that ๐Ÿ’…

In the normal grouping syntax, the commands in a grouping are executed in the current shell. In the subshell grouping syntax, the commands are executed in a new subshell. Pick your vibe ๐Ÿฆ‹

In the POSIXly-correct mode, a grouping must contain at least one command. If the shell is not in the POSIXly-correct mode, a grouping may contain no commands. Empty grouping rights, but only outside POSIX mode ๐ŸŒธ

The exit status of a grouping is that of the last command in the grouping. If the grouping contains no commands, its exit status is that of the last executed command before the grouping. Yes, it inherits. Spooky but true ๐Ÿ‘ป

โญ If command โญ

The if command performs a conditional branch. The blueprint of every decision you've ever made in shell, honestly ๐Ÿ˜ญ

Basic if command syntax

if condition...; then body...; fi

Syntax with the else clause

if condition...; then body...; else body...; fi

Syntax with the elif clause

if condition...; then body...; elif condition...; then body...;
fi

Syntax with the elif clause

if condition...; then body...; elif condition...; then body...;
else body...; fi

For all the syntaxes, the execution of an if command starts with the execution of the condition commands that follow the if token. If the exit status of the condition commands is zero, the condition is considered "true". In that case, the body commands that follow the then token are executed, and the execution of the if command finishes. Done. Next. ๐Ÿ’…

If the exit status of the condition commands is non-zero, the condition is considered "false". In that case, the condition commands for the next elif clause are executed, and the exit status is tested in the same manner as above. If there is no elif clause, the body commands that follow the else token are executed, and the execution of the if command finishes. If there's no else clause either, the execution of the if command just ends. No drama, no nothing ๐Ÿซถ

An if command may have more than one elif-then clause. Chain them, queen ๐Ÿ‘‘

The exit status of an if command is that of the body commands that were executed. The exit status is zero if no body commands were executed โ€” that is, all the conditions were false and there was no else clause.

๐Ÿฆ‹ While and until loops ๐Ÿฆ‹

The while loop and until loop are simple loops with condition.

While loop syntax

while condition...; do body...; done

Until loop syntax

until condition...; do body...; done

If the shell is not in the POSIXly-correct mode, you can omit the condition and/or body commands of a while/until loop. Optional queen โœจ

The execution of a while loop is started by executing the condition commands. If the exit status of the condition commands is zero, the shell executes the body commands and returns to the execution of the condition commands. The condition and body commands are repeatedly executed until the exit status of the condition commands is non-zero. Round and round we go ๐Ÿ”„

Note ๐Ÿฆ‹ The body commands are not executed at all if the first execution of the condition commands yields a non-zero exit status. Zero body time. Straight to the end ๐Ÿ’€

An until loop is executed in the same manner as a while loop, except that the condition to repeat the loop is reversed: the body commands are executed when the exit status of the condition commands is non-zero. Opposite day, every day ๐ŸŽ€

The exit status of a while/until loop is that of the last executed body command. The exit status is zero if the body commands are empty or were not executed at all.

๐ŸŒธ For loop ๐ŸŒธ

Okay so the for loop ๐Ÿ’ซ is that girl who repeats commands, and every round she assigns a variable one of the values you handed her. Reliable. Consistent. We stan.

For loop syntax

for varname in word...; do command...; done

for varname do command...; done

The word list after the in token can literally be empty bestie, but the semicolon (or a newline) before the do token is required even then โ€” no skipping it. The words aren't treated as keywords, but you do need to quote separator characters (like & and |) if you want them to be part of a word. And the command list can be empty too โ€” but only if you're not in the POSIXly-correct mode.

The varname has to be a portable (ASCII-only) name when you're in POSIXly-correct mode. Rules are rules ๐Ÿ’…

The execution of a for loop kicks off by expanding the words the same way a simple command would. If the in and word tokens are omitted entirely, the shell just assumes the word tokens are "$@". Then for every word that got expanded (in the order they were expanded), this happens:

  1. Assign the word to the variable whose name is varname.
  2. Execute the commands.

By default, if a for loop runs inside a function, varname gets created as a local variable, even if it already exists globally. Turning off the for-local shell option, or enabling the POSIXly-correct mode, will disable this behavior.

If expanding the words gives you no words, then no variable is created and the commands don't run at all. Nothing. Nada. ๐Ÿซฅ

The exit status of a for loop is the exit status of the last executed command. It's zero if the commands are not empty and were not executed at all. And if the commands are empty, the exit status is that of the last executed command before the for loop. Got it? Good.

If the variable is read-only, the execution of the for loop gets interrupted and the exit status will be non-zero. No cap.

Case command ๐ŸŽ€

The case command does pattern matching to pick which commands to execute. It's giving matchmaker energy fr.

Case command syntax

case word in caseitem... esac

Case item syntax

(patterns) command... ;;

(patterns) command... ;&

(patterns) command... ;|

(patterns) command... ;;&

The word between the case and in tokens has to be exactly one word. It's not treated as a keyword, but you need to quote separator characters (like & and |) to include them as part of the word. Between the in and esac tokens you can drop any number of case items โ€” or literally none, your call.

You can omit the first ( token of a case item unless the first pattern is esac. The patterns in a case item are one or more tokens, each separated by a | token.

The commands in a case item may be empty. And if the last case item of a case command is terminated by ;;, you can just omit that ;; token. Frugal. Love that.

Execution of a case command starts by subjecting the word to the four expansions. Then for each case item (in order of appearance):

  1. For each word in the patterns, expand the word the same way as the word, and test whether the expanded pattern matches the expanded word. (If a pattern is found that matches the word, the remaining patterns are not expanded nor tested, so some of the patterns may never get expanded. Yash expands and tests patterns in the order of appearance, but it may not be the case for other shells. iykyk.)
  2. If one of the patterns matched the word in the previous step, the commands in this case item are executed.
    • If the case item is terminated by ;; (or the ;; token is omitted), any remaining case items are ignored and the whole case command is done. โœจ
    • If the case item is terminated by ;&, continue to the next case item. But heads up: the patterns of the next item are not expanded nor tested, and the commands are unconditionally executed.
    • If the case item is terminated by ;| or ;;&, continue to the next case item, looking for a next match. ๐Ÿ”
  3. If none of the patterns match the word, proceed to the next case item.

The exit status of a case command is that of the last commands executed. It's zero if commands weren't executed at all โ€” that means there were no case items, no matching pattern was found, or the commands were empty in the last executed case item.

The ;| and ;;& terminators are a non-standard extension that cannot be used in the POSIXly-correct mode. Don't try it bestie, it will not slay.

Double-bracket command ๐Ÿ‘‘

The double-bracket command is a syntactic construct that works similarly to the test built-in. It expands and evaluates the words between the brackets.

Double-bracket command syntax

[[ expression ]]

The expression can be a single primary or a combination of primaries and operators. The expression syntax is parsed when the command is parsed, not when it's executed. Operators (primary or non-primary) must not be quoted, or it'll be parsed as a normal word. ๐Ÿ™ˆ

When the command is executed, operand words get subjected to the four expansions โ€” but not brace expansion, field splitting, or pathname expansion.

In the double-bracket command, you can use these primaries from the test built-in:

Unary primaries

-b, -c, -d, -e, -f, -G, -g, -h, -k, -L, -N, -n, -O, -o, -p, -r, -S, -s, -t, -u, -w, -x, -z

Binary primaries

-ef, -eq, -ge, -gt, -le, -lt, -ne, -nt, -ot, -veq, -vge, -vgt, -vle, -vlt, -vne, ===, !==, =~, <, >

Additionally, some binary primaries can compare strings, and this works slightly differently from the ones for the test built-in: the = primary treats the right-hand-side operand word as a pattern and tests whether it matches the left-hand-side operand word. The == primary is the same as =. And the != primary is the negation of the = primary (reverse result, periodt).

The operand word of a primary must be quoted if it's ]] or if it could be confused with another primary operator.

Note More primaries may be added in future versions of the shell. You should quote any words that start with a hyphen. Protect yourself bestie ๐Ÿซถ
Note The <= and >= binary primaries cannot be used in the double-bracket command because it can't be parsed correctly in the shell grammar. Tragic, I know.

These operands (listed in descending order of precedence) can be used to combine primaries:

  • ( expression ) โ€” a pair of parentheses changes operator precedence. Classic.
  • ! expression โ€” an exclamation mark negates (reverses) the result. Not the vibe โ†’ vibe.
  • expression && expression โ€” a double ampersand is logical conjugation (the "and" operation). The whole expression is true if and only if both operand expressions are true. The left-hand-side expression is expanded and tested first. The right-hand-side is expanded only if the left-hand-side is true.
  • expression || expression โ€” a double vertical line is logical conjugation (the "or" operation). The whole expression is false if and only if both operand expressions are false. The left-hand-side is expanded and tested first. The right-hand-side is expanded only if the left-hand-side is false.
Note Unlike the test built-in, neither -a nor -o can be used as a binary operator in the double-bracket command. Sorry babe.

The exit status of the double-bracket command is 0 if expression is true, 1 if false, and 2 if it can't be evaluated because of an expansion error or any other reason.

Note The double-bracket command is also supported in bash, ksh, mksh, and zsh, but it's not defined in the POSIX standard. The behavior slightly differs between the shells. The test built-in should be preferred over the double-bracket command for maximum portability. Choose wisely ๐ŸŒธ

Function definition ๐Ÿงš

The function definition command defines a function. Groundbreaking, I know.

Function definition syntax

funcname ( ) compound_command

function funcname compound_command

function funcname ( ) compound_command

In the first syntax, without the function keyword, funcname can't contain any special characters like semicolons and quotation marks. In the second and third syntax โ€” which can't be used in the POSIXly-correct mode โ€” funcname gets subjected to the four expansions when executed. In the POSIXly-correct mode, funcname is limited to a portable (ASCII-only) name.

When a function definition command is executed, a function whose name is funcname gets defined, with its body being compound_command. Slay.

A function definition command cannot be directly redirected. Any redirections that follow a function definition are associated with compound_command rather than the whole function definition command. In func() { cat; } >/dev/null, for example, it's not func() { cat; } but { cat; } that's redirected. Sneaky little detail, but so important.

The exit status of a function definition is zero if the function was defined without errors, and non-zero otherwise.


๐Ÿ’– PARAMETERS AND VARIABLES ๐Ÿ’–

Parameters are string values that get expanded in parameter expansion. There are three types of parameters: positional parameters, special parameters and variables. Three flavors, all iconic.

Positional parameters ๐Ÿ”ข

Positional parameters are parameters identified by natural numbers. If there are three positional parameters, for example, they're identified as 1, 2, and 3. You can get the number of positional parameters with the # special parameter. And the * and @ special parameters expand to all the positional parameters.

Positional parameters are initialized from the shell's command line arguments when the shell is started (see Command line arguments). In that initialization, the order of the operands is preserved as the order of the positional parameters.

When the shell executes a function call, positional parameters get changed to the arguments of the function call, so you can access the arguments while the function is running. Then they're restored to their original values once the function's execution is finished. Glow up and glow down, respectfully. โœจ

Positional parameters can be manipulated by built-in commands like set and shift.

Note that 0 is not a positional parameter but a special parameter. Don't get it twisted ๐Ÿ’…

Special parameters โญ

Special parameters are parameters each identified by a single symbol. They cannot be directly assigned to by the user. They're the main characters and you don't get to write their lines.

Yash provides the following special parameters:

  • 0 โ€” bestie, this is the name of the shell executable file, or the script file that got named when the shell was invoked. iconic, lowkey the origin story โœจ
  • # โ€” how many positional parameters we're working with right now. the value is a non-negative integer, no cap ๐Ÿ’…
  • $ โ€” the process ID of the shell. it's a positive integer and it is never changed, not even in subshells. she stays the same, periodt ๐Ÿ‘‘
  • - โ€” the shell options that are currently enabled. the value is a concatenation of alphabet characters that are the names of currently enabled single-character options that can be specified in shell invocation. and the way it updates the moment you enable or disable options with the set built-in? obsessed ๐Ÿฆ‹
  • ? โ€” the exit status of the last executed pipeline. value is a non-negative integer, so you always know how the last thing went ๐Ÿ˜ญ
  • ! โ€” the process ID of the last started asynchronous list, or the last resumed background job. the background babes get their moment ๐ŸŒธ
  • * โ€” this special parameter represents the whole positional parameters. when there's no positional parameters, the value is the empty string. when there's more than one positional parameter, the value is a concatenation of all of them, each separated like this:
    • if the IFS variable exists and its value is not empty, positional parameters are each separated by the first character of the value of the IFS variable.
    • if the IFS variable exists and has an empty value, positional parameters are just concatenated without any separator.
    • if the IFS variable does not exist, positional parameters are each separated by a space character.

if field-splitting is applied to an expansion result of this parameter, the value is first split into the original positional parameters and then further split depending on the current IFS variable. and the first splitting is performed even if the IFS variable is empty โ€” she does not skip that step, ever ๐Ÿ’–

  • @ โ€” this special parameter represents the whole positional parameters, same as the * special parameter above. the difference between the two is the results of expansion that happens between a pair of double-quotation marks. if the @ special parameter is expanded inside double-quotations, the result is field-split into the exact positional parameter values. if there are no positional parameters, the expansion yields no word rather than an empty word. (even if the expansion is double-quoted, the result is not always a single word โ€” she's complex like that ๐ŸŽ€)
    • when there are no positional parameters, the command words echo 1 "$@" 2 are expanded to the three words echo, 1, and 2.
    • when positional parameters are the three words 1, 2 2, and 3, the command words echo "$@" are expanded to the four words echo, 1, 2 2, and 3, and the words echo "a$@b" to the four words echo, a1, 2 2, and 3b.

๐Ÿงš Variables ๐Ÿงš

Variables are parameters the user can assign values to. every variable has a name that identifies it and a value that defines the results of expansion. it's giving name-tag-and-personality, fr fr ๐Ÿ’œ

A variable name is composed of one or more alphanumeric characters and underscores (_). a name cannot start with a digit. other characters may be used in a name depending on internationalization support of your environment โ€” so it depends on your setup, iykyk ๐ŸŒˆ

Variables that are exported to external commands are called environment variables. they are passed to all external commands the shell invokes. variables passed to the shell in invocation will be automatically exported. so they just show up ready to go, no extra steps ๐Ÿซถ

You can assign to variables by a simple command as well as the typeset built-in. you can remove variables by using the unset built-in. assign and unassign, main character energy both ways ๐Ÿ’…

โœจ Variables used by the shell โœจ

The following variables are used by the shell for special purposes. these ones are the VIPs of the variable world, bestie โ€” pay attention ๐Ÿ‘‘

  • CDPATH โ€” this variable is used by the cd built-in to find a destination directory. where you're headed, she's got the map ๐Ÿ—บ๏ธ
  • COLUMNS โ€” specifies the width (the number of character columns) of the terminal screen. the value affects the display of line-editing. so if your line-editing looks weird, it's lowkey this one ๐Ÿ“
  • COMMAND โ€” while a post-prompt command is being executed, this variable is set to the just input command line. it will be unset after the post-prompt command finishes. here for a moment, gone the next, such a vibe ๐Ÿฆ‹
  • COMMAND_NOT_FOUND_HANDLER โ€” when the shell cannot find a command to be executed, the value of this variable is interpreted and executed instead. you can override the shell's error handling behavior with this variable. see Execution of simple commands for detail.
this feature is disabled in the POSIXly-correct mode. ๐Ÿ’€
  • DIRSTACK โ€” this array variable is used by the shell to store the directory stack contents. if you modify the value of this variable, the directory stack may be corrupted. do not touch, bestie, she is not yours ๐Ÿ™ˆ
  • ECHO_STYLE โ€” specifies the behavior of the echo built-in. yes, echo has a whole personality setting ๐ŸŒธ
  • ENV โ€” when an interactive shell is started in the POSIXly-correct mode, the value of this variable is used to find the initialization file. see Initialization of yash. ๐ŸŽ€
  • FCEDIT โ€” specifies an editor program used to edit command lines during execution of the fc built-in. the editor for your history glow-up โœจ
  • HANDLED โ€” this variable can be set in the command-not-found handler to tell the shell not to produce a further error message. see Execution of simple commands for detail. it's giving "we handled it, no need to panic" ๐Ÿ’…
  • HISTFILE โ€” specifies the pathname of the file to save the command history in. your receipts, saved forever ๐Ÿ“œ
  • HISTRMDUP โ€” specifies the number of command history items to be checked for duplication. when the shell is adding a new history item to the command history, if some of the most recent n items have the same contents as the new one, then the duplicate existing items are removed from the history before the new one is added, where n is the value of this variable.

if the value of this variable is 1, for example, the most recent item is removed when a new item that have the same contents is added.

items older than the *n*th recent item are not removed. no items are removed if the value of this variable is 0. all items are subject to removal if the variable value is greater than or equal to the value of the HISTSIZE variable. so basically: she does not tolerate repeats in the recent past ๐Ÿ—‘๏ธ

  • HISTSIZE โ€” specifies the maximum number of items in the command history. the cap on how much lore gets kept ๐ŸŒˆ
  • HOME โ€” specifies the pathname of the user's home directory and affects results of tilde expansion and cd built-in. home sweet home, literally ๐Ÿ 
  • IFS โ€” specifies separators used in field splitting. the variable value is initialized to the three characters of a space, a tab, and a newline when the shell is started. the original trio ๐Ÿซถ
  • LANG, LC_ALL, LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME โ€” these variables specify a locale in which the shell runs. the shell chooses the file input/output encoding, the error message language, etc. according to the locale specified. so they decide the whole aesthetic of your messages โœจ

unless the shell is interactive and not in the POSIXly-correct mode, the value of the LC_CTYPE variable is considered only when the shell is started. once the shell has been initialized, changing the value of LC_CTYPE will have no effect on the shell's behavior. so change it before, not after โ€” timing is everything ๐Ÿ’…

  • LINENO โ€” the value of this variable is automatically set to the line number in which the currently executed command appears in the file. she always knows exactly where she is ๐Ÿ“

in the interactive shell, the line number is reset to 1 each time the shell reads and executes a command.

if you assign to or remove this variable, it will no longer provide line numbers. so don't mess with her, bestie ๐Ÿ˜ญ

  • LINES โ€” specifies the height (the number of character lines) of the terminal screen. the value affects the display of line-editing. the vertical bestie to COLUMNS's horizontal one ๐Ÿ“
  • MAIL โ€” specifies the pathname of a file that is checked in mail checking. ๐Ÿ“ฌ
  • MAILCHECK โ€” specifies how often the shell should do mail checking. the value has to be specified as a positive integer in seconds. the value is initialized to the default value of 600 when the shell is started. ten minutes, on the dot โฐ
  • MAILPATH โ€” specifies the pathnames of files that are checked in mail checking. plural mailboxes, she's popular ๐Ÿ’Œ
  • NLSPATH โ€” the POSIX standard prescribes that the value of this variable specifies pathname templates of locale-dependent message data files, but yash does not use it. so the standard says one thing and yash says "no <3" ๐Ÿ™ˆ
  • OLDPWD โ€” this variable is set to the previous working directory path when you change the working directory by using the cd or other built-ins. this variable is exported by default. the "where I just was" file, always exported, always ready ๐Ÿ”™
  • OPTARG โ€” when the getopts built-in parses an option that takes an argument, the argument value is assigned to this variable. the little gift bag for option arguments ๐ŸŽ
  • OPTIND โ€” the value of this variable specifies the index of an option that is to be parsed by the next getopts built-in execution. this variable is initialized to 1 when the shell is started. she starts at one, every single time ๐ŸŒŸ
  • PATH โ€” specifies paths that are searched for a command in command search. the where-to-look list, absolutely essential ๐Ÿ›ฃ๏ธ
  • POST_PROMPT_COMMAND โ€” the shell interprets and executes the value of this variable after reading each command if the shell is interactive and not in the POSIXly-correct mode. see Prompts for details. ๐ŸŽ€
  • PPID โ€” the value of this variable is the process ID of the shell's parent process, which is a positive integer. this variable is initialized when the shell is started. the value is not changed when the shell makes a new subshell. so it stays pointing at the parent, no cap ๐Ÿ‘‘
  • PROMPT_COMMAND โ€” the shell interprets and executes the value of this variable before printing each command prompt if the shell is interactive and not in the POSIXly-correct mode. this behavior is equivalent to executing the command eval -i -- "${PROMPT_COMMAND-}" before each command prompt, but its exit status does not affect the expansion of the ? special parameter in the next command. so she runs before every prompt and she does not taint your ? โ€” respectful queen ๐Ÿ’–
  • PS1 โ€” specifies the main command prompt string printed by an interactive shell. see Prompts for the format of the variable value. the value is initialized to either $ or # depending on whether the effective user ID of the shell process is zero or not. $ for the rest of us, # for the root girlies ๐Ÿ’…
  • PS1P โ€” this one sets the font style of predicted commands. ๐Ÿ’… Peep the Prompts section for how the value should be formatted.
  • PS1R โ€” the auxiliary prompt string, printed to the right of the cursor when you're typing a command line into an interactive shell. Format deets live in Prompts.
  • PS1S โ€” the font style of the command strings you type into an interactive shell. Again, Prompts has the format.
  • PS2 โ€” literally PS1's sibling but for the second and following lines of a command that's too long to fit on one line ๐Ÿฅฒ. Format in Prompts. It starts out initialized to > when the shell boots up.
  • PS2P โ€” same energy as PS1P, but it kicks in when PS2 is being used. Format: Prompts.
  • PS2R โ€” same as PS1R, but it's the one used when PS2 is used. Format: Prompts.
  • PS2S โ€” same as PS1S, but used when PS2 is used. Format: Prompts.
  • PS4 โ€” whatever this holds gets printed before each command trace output when the xtrace option is on ๐Ÿ”. The value goes through parameter expansion, command substitution, and arithmetic expansion. You can also use backslash notations here if the shell isn't in POSIXly-correct mode. It's initialized to + when the shell starts.
  • PS4S โ€” like PS1S, but it's used when PS4 is used. Use it to change the font style of your command trace output, bestie. โœจ
  • PWD โ€” holds the pathname of the current working directory. It gets set when the shell starts and gets reset every single time the working directory changes via cd or other built-ins. This one is exported by default.
  • RANDOM โ€” your random number plug ๐ŸŽฒ. Its value is a uniformly distributed random integer between 0 and 32767 (inclusive). Every time you expand it you get a different number.

You can set the "seed" of the random numbers by assigning a non-negative integer to it.

If you remove this variable, it stops working as a random number generator. And if the shell was invoked in the POSIXly-correct mode, it doesn't work as a random number generator either. ๐Ÿ’€

  • TERM โ€” tells the shell what type of terminal it's running in. The value affects how line-editing behaves. Heads up: this variable has to be exported to actually take effect.
  • XDG_CONFIG_HOME โ€” define this to say where your shell initialization files live. If you don't define it, the shell assumes it's $HOME/.config. ๐Ÿ 
  • YASH_AFTER_CD โ€” after every time the shell's working directory changes (via cd or other built-ins), the shell interprets and executes this variable's value. It's exactly equivalent to running the command eval -i -- "${YASH_AFTER_CD-}" right after the directory was changed. ๐Ÿฆ‹
  • YASH_LOADPATH โ€” the directories the dot built-in searches through to find a script file. You can list more than one directory by separating them with colons, just like the PATH variable. When the shell starts, it's initialized to the pathname of the directory where common script files are installed.
  • YASH_LE_TIMEOUT โ€” how long (in milliseconds) the shell should wait for a possible next input from the terminal when it hits an ambiguous control sequence while line-editing. If you don't define this variable, the default 100 milliseconds is assumed. โฑ๏ธ
  • YASH_PS1, YASH_PS1P, YASH_PS1R, YASH_PS1S, YASH_PS2, YASH_PS2P, YASH_PS2R, YASH_PS2S, YASH_PS4, YASH_PS4S โ€” when not in POSIXly-correct mode, if any of these is defined, it takes precedence over the corresponding variable without the YASH_ prefix in the name (so like, YASH_PS1 beats PS1). In POSIXly-correct mode these get ignored entirely. Pro tip: define them to include yash-specific notations in your prompt, so that unhandled notations don't mangle the prompt when you're in POSIXly-correct mode. ๐Ÿซถ
  • YASH_VERSION โ€” initialized to the version number of the shell when the shell starts. ๐Ÿ‘‘

๐Ÿ“ Arrays ๐Ÿ“

An array is a variable that holds zero or more strings. The string values of an array are identified by natural numbers โ€” same vibe as positional parameters.

You can assign values to an array using a simple command or the array built-in. And you can use the unset built-in to remove arrays.

Arrays cannot be exported as arrays. When an array is exported, it's treated as a normal variable whose value is a concatenation of all the array values, each one separated by a colon.

Arrays are not supported in the POSIXly-correct mode. No cap. ๐Ÿšซ

๐ŸŒˆ WORD EXPANSIONS ๐ŸŒˆ

Word expansion is substituting part of a word with another particular string. There are so many types, bestie:

  1. Tilde expansion
  2. Parameter expansion
  3. Command substitution
  4. Arithmetic expansion
  5. Brace expansion
  6. Field splitting
  7. Pathname expansion (globbing)

Tilde expansion, parameter expansion, command substitution, and arithmetic expansion are called the four expansions, and they happen in the order of appearance. After those, brace expansion, field splitting, and pathname expansion are optionally performed in that order โ€” but only if the expansion is happening in a multi-field context, meaning a whole list of words is being expanded at once to produce a list of fields.

Tilde expansion

In tilde expansion, the parts of words that start with a tilde (~) get substituted with particular pathnames. The part of each word that gets swapped out runs from the beginning of the word (which is a tilde) up to โ€” but not including โ€” the first slash (/) in the word. If the word has no slash in it, the whole word gets substituted. And if any character in the substituted part is quoted, tilde expansion just doesn't happen on that word. ๐Ÿ’…

What you get depends on the format of the substituted part:

  • ~ โ€” a single tilde is substituted with the value of the HOME variable.
  • ~username โ€” a tilde followed by a user name is substituted with the pathname of that user's home directory.
  • ~+ โ€” substituted with the value of the PWD variable.
  • ~- โ€” substituted with the value of the OLDPWD variable.
  • ~+n, ~-n โ€” where n is a non-negative integer. This type yields the pathname of a directory for which ~+n or ~-n is the index in the directory stack.

When tilde expansion is performed on the value of a variable assignment that happens during execution of a simple command, the value is treated as a colon-separated list of words, and each of those words gets tilde expansion. For example, the variable assignment

VAR=~/a:~/b:~/c

is equivalent to

VAR=/home/foo/a:/home/foo/b:/home/foo/c

if the value of the HOME variable is /home/foo.

The POSIX standard does not prescribe how the shell should behave when it hits an error during tilde expansion (like, say, when the HOME variable isn't defined ๐Ÿ˜ญ). Yash silently ignores any errors during tilde expansion; the part of the word that would have been substituted is left intact.

In the POSIXly-correct mode, tilde expansion supports the formats ~ and ~username only.

Parameter expansion

Parameter expansion expands to the value of a parameter. Simple as that.

The syntax of typical, simple parameter expansion is ${parameter}, which expands to the value of the parameter whose name is parameter. You can drop the braces (so like, $parameter) if:

  • parameter is a special parameter,
  • parameter is a positional parameter whose index is a one-digit integer, or
  • parameter is a variable and the parameter expansion is not followed by a character that could be part of a variable name. For example, ${path}-name is equivalent to $path-name, but ${path}name and $pathname are different.

If parameter is none of a special parameter, positional parameter, or variable, it's a syntax error. (Some shells other than yash might treat that case as an expansion error, but that's not us. ๐Ÿ’…)

If the unset option is disabled and the parameter is an undefined variable, it's an expansion error. If the unset option is enabled, an undefined variable expands to the empty string.

More complex syntax of parameter expansion lets you modify the value of a parameter.

Parameter expansion

${ prefix parameter index modifier }

The spaces in that syntax definition above are purely for readability and must be omitted. You can omit prefix, index, and/or modifier.

Prefix

The prefix, if there is one, must be a hash sign (#). If a parameter expansion has the prefix, the result of the expansion is the number of characters in the value this expansion would expand to without the prefix.

Parameter name

The parameter name (parameter) has to be either:

  • a name of a special parameter, positional parameter, or variable; or
  • another parameter expansion, command substitution, or arithmetic expansion.

The parameter expansion expands to the value of the parameter. If parameter is an array variable, the values of the array are field-split just like the @ special parameter โ€” unless the index [*] is specified.

If parameter is another expansion, it's called a nested expansion. Nested expansion cannot be used in the POSIXly-correct mode. And the braces ({ }) of a nested parameter expansion can't be omitted. ๐Ÿซ 

Index

An index lets you extract part of the parameter value (or some of the array values).

[word1]

[word1,word2]

where word1 and word2 are parsed in the same manner as normal tokens, except that they're always delimited by , or ] and can contain whitespace characters.

If there's an index in a parameter expansion, here's the whole โœจinterpretationโœจ pipeline, bestie, listen up:

  1. word1 and word2 get subjected to parameter expansion, command substitution, and arithmetic expansion. Yep, all three. Go off.
  2. If there's no word2 and word1 expands to one of *, @, and #, then that is the interpretation of the index and we skip the next step entirely. No notes.
  3. Otherwise, the results from the previous steps (the expanded word1 and word2) get interpreted and evaluated as an arithmetic expression, same vibe as arithmetic expansion. The resulting integers are the interpretation of the index. If the results aren't integers โ†’ it's an expansion error, rip. And if there's no word2, it's just assumed that word2 equals word1. Periodt.

So like, what does the index even do? Depends on the parameter:

  • If parameter is an array variable, the index specifies the part of the array. ๐Ÿ’…
  • If parameter is either the * or @ special parameter, the index specifies the index range of the positional parameters.
  • In every other case, the index specifies the index range of a substring of the parameter value being expanded.

In all of these cases, the specified range of the array values, positional parameters, or parameter value stays in the results of the expansion, and everything else gets dropped. Gone. Blocked. โœจ


When the index is one or two integers ๐Ÿ”ข

  • If the interpreted index value is negative, it wraps around. So like, an index value of -1 corresponds to the last value/character. Iconic behaviour tbh.
  • It is NOT an error when the index value is out of range. The existing values/characters within the range just get selected. She's forgiving like that.
  • If the interpretation of either word1 or word2 is 0, the range is assumed empty and the expansion results in nothing. Absolutely nothing. ๐Ÿ’€

When the index is *, @, or # ๐ŸŽ€

* : If parameter is an array, all the array values are field-split or concatenated in the same manner as the * special parameter. If parameter is the * or @ special parameter, the positional parameters are likewise field-split or concatenated. In other cases, the interpretation of the index is treated as if the interpretation is the two integers 1 and -1.

@ : The interpretation of the index is treated as if the interpretation is the two integers 1 and -1. Same energy as above, no extra drama.

# : This one is special โ€” it does not simply specify a range. Instead, the expanded values are substituted with the count. ๐Ÿงฎ : If parameter is an array, the result of this parameter expansion will be the number of values in the array being expanded. If parameter is the * or @ special parameter, the result will be the number of current positional parameters. Otherwise, the result will be the number of characters in the value that's being expanded. Count 'em up, bestie.


If a parameter expansion does not contain an index, it's assumed to be [@]. And heads up: in the POSIXly-correct mode, the index cannot be specified. Not allowed, sorry babe.

Example 1. Expansion of a normal variable ๐ŸŒธ

The following commands will print the string ABC:

var='123ABC789'
echo "${var[4,6]}"

Example 2. Expansion of positional parameters ๐ŸŒˆ

The following commands will print the string 2 3 4:

set 1 2 3 4 5
echo "${*[2,-2]}"

Example 3. Expansion of an array โญ

The following commands will print the string 2 3 4:

array=(1 2 3 4 5)
echo "${array[2,-2]}"

Modifier โœจ

You can modify the value to be expanded by using modifiers. Here's the whole lineup:

  • -word โ€” If the parameter name (parameter) is an undefined variable, the parameter expansion is expanded to word. It is not treated as an error if the unset option is disabled.
  • +word โ€” If the parameter name (parameter) is an existing variable, the parameter expansion is expanded to word. It is not treated as an error if the unset option is disabled.
  • =word โ€” If the parameter name (parameter) is an undefined variable, word is assigned to the variable and the parameter expansion is expanded to word. It is not treated as an error if the unset option is disabled.
  • ?word โ€” If the parameter name (parameter) is an undefined variable, word is printed as an error message to the standard error. If word is empty, the default error message is printed instead. ๐Ÿšจ
  • :-word, :+word, :=word, :?word โ€” These are similar to the four types of modifiers above. The only difference is that, if parameter exists and has an empty value, it is also treated as an undefined variable.
  • #word โ€” The shell performs pattern matching against the value being expanded, using word as a pattern. If word matches the beginning of the value, the matching part is removed from the value and the other part remains as expansion results. The shortest matching is used if more than one matching is possible.
  • ##word โ€” Similar to #word above. The only difference is that the longest matching is used if more than one matching is possible.
  • %word โ€” Similar to #word above. The only difference is that matching is tried at the end of the value rather than at the beginning: if word matches the end of the value, the matching part is removed from the value and the other part remains as expansion results.
  • %%word โ€” Similar to %word above. The only difference is that the longest matching is used if more than one matching is possible.
  • /word1/word2 โ€” The shell performs pattern matching against the value being expanded, using word1 as a pattern. If word1 matches any part of the value, the matching part is replaced with word2 and the whole value after the replacement remains as expansion results. If word1 matches more than one part of the value, only the first part is replaced. The shortest matching is replaced if more than one matching is possible for the same starting point in the value.
This modifier cannot be used in the POSIXly-correct mode. ๐Ÿšซ
  • /#word1/word2 โ€” Similar to /word1/word2 above. The only difference is that word1 matches only at the beginning of the value being expanded.
  • /%word1/word2 โ€” Similar to /word1/word2 above. The only difference is that word1 matches only at the end of the value being expanded.
  • //word1/word2 โ€” Similar to /word1/word2 above. The only difference is that all matched parts are replaced if word1 matches more than one part of the value.
  • :/word1/word2 โ€” Similar to /word1/word2 above. The only difference is that the value is replaced only when word1 matches the whole value.

In all types of modifiers above, words are subjected to the four expansions when (and only when) they are used. Not a second before, okay?

And if parameter is an array variable or the @ or * special parameter, modifiers affect each value of the array or all positional parameters. Every single one. ๐Ÿ’…


Command substitution ๐Ÿซถ

Command substitution expands to the output of commands specified. Obsessed with this one ngl.

Command substitution

$(commands)

`commands`

When command substitution is evaluated, commands are executed by a subshell with output pipelined to the shell. When the commands finished, the command substitution is substituted with the output of the commands. Any trailing newline characters in the output are ignored. Bye bye newlines. ๐Ÿ‘‹

When command substitution of the form $(commands) is parsed, the commands are parsed carefully so that complex commands such as nested command substitution are parsed correctly. If commands start with (, you should put a space before commands so that the whole command substitution isn't confused with arithmetic expansion. Big deal, don't skip this.

If the shell is in the POSIXly-correctly mode, the commands are parsed each time the command substitution is expanded; otherwise, commands are parsed only when the command substitution is parsed.

If the command substitution is of the form `commands`, the commands are not parsed when the command substitution is parsed; the commands are parsed each time the command substitution is expanded. The end of commands is detected by the first backquote character (`) after the beginning of commands that is not quoted by a backslash. Backquotes that are part of commands (typically used for nested command substitution) must be quoted by backslashes. In commands, backslashes are treated as quotes only when preceding a dollar ($), backquote, newline, or another backslash. Additionally, if the command substitution occurs inside double quotes, double quotes in commands must be quoted with a backslash. Those backslashes are removed before commands are parsed. Messy? Yes. Still works? Also yes. ๐Ÿ˜ญ


Arithmetic expansion ๐Ÿ”ฅ

Arithmetic expansion evaluates an arithmetic expression and expands to the value of the expression.

Arithmetic expansion

$((expression))

When arithmetic expansion is expanded, the expression is subject to parameter expansion, command substitution, and (nested) arithmetic expansion. The expression is parsed in (almost) the same manner as an expression of the C programming language.

Yash allows an expression to be either an integer (of the long type in C) or a floating-point number (of the double type in C). An operation on integers yields an integer, and an operation involving a floating-point number yields a floating-point number. In the POSIXly-correct mode, you can use integers only. ๐Ÿ’€

The following operators are available (in the order of precedence):

  1. ( )
  2. ++ -- (postfix operators)
  3. ++ -- + - ~ ! (prefix operators)
  4. * / %
  5. + - (binary operators)
  6. << >>
  7. < <= > >=
  8. == !=
  9. &
  10. ^
  11. |
  12. &&
  13. ||
  14. ? :
  15. = *= /= %= += -= <<= >>= &= ^= |=
The ++ and -- operators cannot be used in the POSIXly-correct mode. ๐Ÿšซ

An atomic expression can be one of an integer literal, a floating-point number literal, and a variable. Literals are parsed in the same manner as in C. An octal integer literal starts with 0, and hexadecimal with 0x. A floating-point number literal may have an exponent (i.e. 1.23e+6). A variable with a non-numeric value will result in an error when parsed as a number. An unset variable is treated as a value of zero if the unset option is enabled.

In the POSIXly-correct mode, variables are always parsed as numbers. Otherwise, variables are parsed only when they are used as numbers in computation. Unparsed variables are left intact โ€” untouched, unbothered, living their truth. ๐Ÿฆ‹

set +o posixly-correct
foo=bar
echo $((0 ? foo : foo)) # prints "bar"
echo $((foo + 0))       # error

And bestie, if the result of an expression isn't defined in C? That's an expansion error. Periodt. ๐Ÿ’…


๐ŸŽ€ Brace expansion ๐ŸŽ€

Brace expansion goes brrr and expands into several split words, with the stuff before and after the braces duplicated onto every single one of those split words. ๐Ÿฆ‹

Heads up: brace expansion only happens when the brace-expand option is enabled. No option, no expansion, no vibes.

Comma-separated brace expansion

{word1,word2,...,wordn}

Range brace expansion

{start..end}
{start..end..delta}

Comma-separated brace expansion expands to each comma-separated word. So like, a{1,2,3}b becomes the three words a1b, a2b, and a3b. Obsessed. โœจ

Range brace expansion expands to integers in the range defined by start and end. The difference between each integer can be defined by delta. If start is larger than end, the results come out in descending order. When ..delta is omitted, it defaults to 1 or -1. For example, a{1..3}b expands to the three words a1b, a2b, and a3b; and a{1..7..2}b to the four words a1b, a3b, a5b, and a7b. Math ate that, ngl. ๐Ÿ”ฅ

Multiple brace expansions can be used in one word. Brace expansions can also be nested. And you can quote braces and/or commas to prevent them from being treated as brace expansion. ๐Ÿ’–

Any errors in brace expansion are silently ignored. It's giving "no thoughts, head empty." ๐Ÿ™ˆ

Note In yash, brace expansion is performed after the four expansions and before field splitting. This is different from what is required by POSIX, which specifies that brace expansion is performed before any other expansion. So yeah, yash is lowkey doing its own thing here. ๐Ÿ’ซ

๐ŸŒˆ Field splitting ๐ŸŒˆ

In field splitting, words are split at predefined separators. Snip snip. โœ‚๏ธ

Field splitting can occur only within parts of words that resulted from parameter expansion, command substitution, and arithmetic expansion that are not between double-quotation marks. The one exception? Expansion results of the @ special parameter get split even between double-quotation marks. Not me being special or anything. ๐Ÿ’…

Separators used in field splitting are defined by the value of the IFS variable. If the variable doesn't exist, the value is assumed to be the three characters of space, tab, and newline. ๐Ÿซถ

Characters included in the value of the IFS variable are called IFS characters. IFS characters that are any of space, tab, and newline are called IFS whitespace, and other IFS characters are called IFS non-whitespace.

Field splitting is performed as follows:

  1. The shell searches words for split points. A split point is one or more adjacent IFS characters within the word portions where field splitting can occur. The following steps are taken for each split point found.
  2. If the split point includes one or more IFS non-whitespaces, all the IFS whitespaces in the split point are ignored and the word is split at each IFS non-whitespace in the split point.
  3. If the split point includes no IFS non-whitespaces, the word is split at the split point unless it is at the beginning or end of the word.
  4. The split points are removed from the results.

Finally, the last word is removed from the results if:

  • the empty-last-field option is not enabled;
  • the result is more than one word; and
  • the last word is empty.
Note Words are not split at all when the value of the IFS variable is empty. Iconic behavior honestly. ๐Ÿ‘‘

๐ŸŒธ Pathname expansion ๐ŸŒธ

Pathname expansion does pattern matching and expands to pathnames matched by the pattern. Glow-up for your globs. โœจ

A word subjected to pathname expansion is treated as a pattern. If one or more pathnames are found that are matched by the pattern, those pathnames become the results of the pathname expansion. ๐Ÿฆ‹

Pathname expansion is not performed when the glob option is disabled. ๐Ÿšซ

The shell searches readable directories for matching pathnames. Unreadable directories are silently ignored. We don't see them, we don't talk about them. ๐Ÿ’€

The following options affect the behavior of pathname expansion:

  • null-glob โ€” This option affects the result of pathname expansion when no matching pathnames are found. If enabled, the result is no word. If disabled, the result is the original pattern word.
  • case-glob โ€” This option specifies case-sensitivity in matching. If enabled, pattern matching is done case-sensitively.
  • dot-glob โ€” This option affects matching of filenames that start with a period (.). If disabled, a period at the beginning of a filename does not match wildcard patterns (? and *) or bracket expressions. If enabled, there is no such special treatment of periods.
  • mark-dirs โ€” If enabled, each resulting pathname that is a directory name is suffixed by a slash (/).
  • extended-glob โ€” This option enables the extension. (See below โฌ‡๏ธ)

Any errors in pathname expansion are silently ignored. If the word is an invalid pattern, it just becomes the result. The results depend on the null-glob option when no matching pathnames are found.

Pattern matching is done for each filename (or pathname component) of pathnames. The shell skips matching for literal patterns that contain no wildcards or bracket expressions. As a result, the patterns /*/foo and /*/fo[o] may yield different expansion results when the case-glob option is disabled; for example, the pattern /*/fo[o] matches the pathname /bar/FOO but the pattern /*/foo does not because matching is skipped for foo. Sneaky but make it make sense. ๐Ÿ˜ญ

๐ŸŒŸ Extension in pathname expansion ๐ŸŒŸ

The following patterns can be used when the extended-glob option is enabled. Yassified globbing incoming. ๐Ÿงš

  • ** โ€” The directory is searched recursively and the pattern matches any number of directory filenames (each separated by a slash). Any directory whose name begins with a period is excluded from search. For example, the pattern dir/**/file can match the pathnames dir/file, dir/foo/file, dir/a/b/c/file, etc. This pattern is not effective when appearing at the end of the whole pattern (i.e. foo/bar/**).
  • .** โ€” This pattern is like **, but all directories are searched including ones with a name starting with a period.
  • *** โ€” This pattern is like **, but if a symbolic link to a directory is found during recursive search, the directory is searched recursively as well.
  • .*** โ€” This pattern is like ***, but all directories are searched including ones with a name starting with a period.

The way these little asterisk stacks just keep leveling up? Ate. ๐Ÿ”ฅ

๐ŸŒธ PATTERN MATCHING NOTATION ๐ŸŒธ

Okay bestie, pattern matching notation is literally just a lil syntax for patterns that stand in for whole sets of strings. When a string is in the set that a pattern represents, we say the pattern matches the string. And whether a pattern matches a string or not? Fully defined, no vibes-based guessing, here's the whole thing ๐Ÿ‘‡


โœจ Normal characters

A character that isn't quoted, and isn't any of the special characters we're about to define, is a normal character โ€” and it matches itself. That's it, that's the whole job.

So like, the pattern abc matches the string abc and literally nothing else. Iconic. Exclusive. ๐Ÿ’…

โœจ Single-character wildcard

The character ? matches any single character. One. Just one. No more no less.

For example, the pattern a?c matches any three-character string that starts with a and ends with c โ€” so aac, abc, and a;c all qualify ๐Ÿซถ

โœจ Multi-character wildcard

The character * matches any string of any length, and yes bestie, that includes the empty string. She contains multitudes.

For example, the pattern a*c matches any string that starts with a and ends with c, like ac, abc, and a;xyz;c โœจ

โœจ Bracket expression

A pattern wrapped in brackets ([ and ]) is a bracket expression. Rules: a bracket expression has to have at least one character chilling between the brackets. The characters in there get read as a bracket expression pattern, which is the special notation we're about to define below. A bracket expression pattern represents a set of characters, and the bracket expression matches any one of the characters in that set.

Now, if the opening bracket ([) is followed by an exclamation mark (!), that ! is not part of the bracket expression pattern โ€” instead the whole bracket expression matches a character that is not in the set. And if the opening bracket is followed by a caret (^), it gets treated just like that exclamation mark (but heads up: shells other than yash might treat the caret differently ๐Ÿ‘€).

Also, if the opening bracket โ€” or the exclamation or caret right after it, if there is one โ€” is followed by a closing bracket (]), then that ] is treated as part of the bracket expression pattern, not as the end of the bracket expression. And no, you cannot quote characters inside a bracket expression pattern, because quotation gets handled before bracket expression ever happens. Sorry bestie, that's just the order of operations ๐Ÿ˜ญ

One more thing: an opening bracket in a pattern is treated as a normal character if it isn't the start of a valid bracket expression. No fake-outs, no half-built brackets.

โœจ Normal characters (in bracket expression pattern)

Inside a bracket expression pattern, a character that isn't one of the special ones defined below is a normal character, and it represents itself.

For example, the bracket expression pattern abc represents the set of the three characters a, b, and c. So the bracket expression [abc] matches any of those three characters. Easy ๐Ÿ’–

โœจ Range expressions

A hyphen with a character (or a collating symbol) before and after it is a range expression. It represents the set of those two characters plus everything between them in the collation order โ€” which is just the order of characters as defined in the locale data.

If a hyphen is followed by a closing bracket (]), then the bracket is treated as the end of the bracket expression and the hyphen is just a normal character. She's off duty.

For example, the range expression 3-5 represents the set of the three characters 3, 4, and 5. So the bracket expression [3-5-] matches one of the four characters 3, 4, 5, and - ๐Ÿ”ฅ

โœจ Collating symbols

A collating symbol lets more than one character be treated as a single character during matching. It's made up of one or more characters enclosed by the special brackets [. and .].

And one or more characters treated as a single character in matching are called a collating element. Being precise here: a bracket expression pattern actually represents a set of collating elements, and a bracket expression matches a collating element rather than a character โ€” but we're not gonna differentiate them here because brevity is a kindness, bestie ๐Ÿ™ˆ

For example, the character combination "ch" was treated as a single character in the traditional Spanish language. So if that combination is registered as a collating element in the locale data, the bracket expression [[.ch.]df] matches one of ch, d, and f. Obsessed with that actually ๐ŸŒธ

โœจ Equivalence classes

An equivalence class represents a set of characters that are considered equivalent. It's made up of a character (or, more precisely, a collating element) enclosed by the special brackets [= and =].

An equivalence class represents the set consisting of the character inside the brackets plus all the characters that are in the same primary equivalence class as that enclosed character. The shell consults the locale data to find out how equivalence classes are defined in the current locale.

For example, if the six characters a, ร , รก, รข, รฃ, รค are defined to be in the same primary equivalence class, then the bracket expressions [[=a=]], [[=ร =]], and [[=รก=]] all match one of those six. The way that one expression holds six whole characters? It's giving range ๐Ÿ’…

โœจ Character classes

A character class represents a predefined set of characters. It's made up of a class name enclosed by the special brackets [: and :]. The shell consults the locale data to figure out which class a character belongs to.

These character classes can be used in all locales, no cap:

  • [:lower:] โ€” set of lowercase letters
  • [:upper:] โ€” set of uppercase letters
  • [:alpha:] โ€” set of letters, including the [:lower:] and [:upper:] classes.
  • [:digit:] โ€” set of decimal digits
  • [:xdigit:] โ€” set of hexadecimal digits
  • [:alnum:] โ€” set of letters and digits, including the [:alpha:] and [:digit:] classes.
  • [:blank:] โ€” set of blank characters, not including the newline character
  • [:space:] โ€” set of space characters, including the newline character
  • [:punct:] โ€” set of punctuations
  • [:print:] โ€” set of printable characters
  • [:cntrl:] โ€” set of control characters

For example, the bracket expression [[:lower:][:upper:]] matches a lower or upper case character โœจ And psst โ€” on top of all the classes listed above, other classes may be usable depending on how the current locale is defined. So check your locale if you're feeling adventurous ๐Ÿฆ‹

๐Ÿ’– REDIRECTION ๐Ÿ’–

Okay bestie, redirection is literally the glow-up that lets you change file descriptors of commands. With redirection you can run commands with their standard input/output connected to files or devices instead of the terminal. No cap.

You do redirection by adding redirection operators to a command (simple command or compound command). In a simple command, redirection operators may appear anywhere in the command as long as operator tokens are separated from other tokens. In a compound command, redirection operators must appear at the end of the command. Periodt.

Redirection operators are processed before the command body is executed. More than one redirection operator in a command are processed in the order of appearance. Redirection operators affect only the command in which they appear, except when they appear in an exec built-in without command operands. That is, file descriptors modified by redirection are restored after the command has finished. Slay.

A redirection operator starts with < or >. Redirection operators starting with < affect the standard input (file descriptor 0) by default. Redirection operators starting with > affect the standard output (file descriptor 1) by default. To affect another file descriptor, you can prefix a redirection operator with a non-negative integer; the operator will affect the file descriptor specified by the integer. The integer must immediately precede the < or > without any whitespaces in between. The integer must not be quoted, either. ๐Ÿ’…

Words of the form {name} that immediately precede a redirection operator are reserved for future use. Currently, they cause a syntax error if used. So don't do that, bestie. ๐Ÿ˜ญ


โœจ Redirection to files โœจ

The most common type of redirection is redirection to files.

๐Ÿ’– Redirection of input ๐Ÿ’–

< token

๐Ÿ’– Redirection of output ๐Ÿ’–

> token
>| token
>> token

๐Ÿ’– Redirection of input and output ๐Ÿ’–

<> token

The token is subject to the four expansions. It is also subject to pathname expansion if the shell is interactive. The expansion result is treated as the pathname of the file to which redirection is performed. If the pathname expansion does not result in a single pathname, it is an error. ๐Ÿ˜ค

In redirection of input, the standard input is replaced with a file descriptor which is open for read-only access to the target file. If the target file cannot be opened for read-only access, it is an error.

In redirection of output, the standard output is replaced with a file descriptor which is open for write-only access to the target file. If the target file cannot be opened for write-only access, it is an error. If the target file does not exist, a new empty file is created and opened. If the target file already exists, the file is opened as follows:

  • For the >| operator, the file is emptied when opened if it is a regular file. ๐Ÿ”ฅ
  • For the > operator, the behavior is the same as the >| operator if the clobber option is enabled. If the option is disabled and the file is a regular file, it is treated as an error. โš ๏ธ
  • For the >> operator, the file is opened for appending; any output to the file descriptor is appended to the end of the file. ๐ŸŽ€

In redirection of input and output, the standard input is replaced with a file descriptor which is open for read-and-write access to the target file. If the file does not exist, a new empty file is created and opened.

๐Ÿ’… Socket redirection ๐Ÿ’…

If the pathname of the target file is of the form /dev/tcp/host/port or /dev/udp/host/port and the file cannot be opened in the usual manner, a new socket is opened for communication with the port of the host. The redirection replaces the standard input or output with the file descriptor to the socket.

A stream socket is opened for the form /dev/tcp/host/port and a datagram socket for the form /dev/udp/host/port. The protocol actually used for communication is determined by the socket library the shell uses. Typically, stream sockets use TCP and datagram sockets UDP.

In socket redirection, the file descriptor is both readable and writable regardless of the type of the redirection operator used. ๐Ÿ‘‘

Socket redirection is yashโ€™s extension that is not defined in POSIX. Bash as well has socket redirection as extension. iykyk.


๐ŸŽ€ Duplication of file descriptors ๐ŸŽ€

Redirection allows duplicating or closing existing file descriptors.

โœจ Duplication of file descriptor โœจ

<& token
>& token

The token is subject to expansion as in redirection to files, but it is treated as a file descriptor rather than a pathname. Thus the expanded token must be a non-negative integer.

The <& and >& operators duplicate the file descriptor specified by token to the standard input and output, respectively. (The operators can be prefixed with a non-negative integer so that the file descriptor is duplicated to a file descriptor other than the standard input or output.)

If the expanded token is a single hyphen rather than a non-negative integer, the file descriptor is closed rather than duplicated. By default, the <& and >& operators close the standard input and output, respectively, but the operators can be prefixed with a non-negative integer so that another file descriptor is closed.

In the POSIXly-correct mode, a file descriptor must be readable when duplicated by the <& operator and writable when duplicated by the >& operator. ๐Ÿ’œ


๐ŸŒธ Here documents and here strings ๐ŸŒธ

Here document and here string allow redirection to file descriptors that reads strings directly specified in shell commands.

๐Ÿ’– Here document ๐Ÿ’–

<< token
<<- token

๐Ÿ’– Here string ๐Ÿ’–

<<< token

In a here document or here string, the standard input is replaced with a readable file descriptor. When the command reads from the file descriptor, it will read the contents of the here document/string, which is defined below.

When a here document operator (<< or <<-) appears in a command, the shell reads the contents of the here document starting from the next line. The contents of here documents are not parsed nor executed as commands. The token after the operand specifies a delimiter that indicates the end of the contents. (The token is not subject to any expansion, but quotation is processed.) The contents of the here document is terminated just before the first line containing the token only. When using the <<- operator, all tab characters at the beginning of each line in the here document contents are removed and the delimiter token may be preceded by tab characters. ๐Ÿฆ‹

If there are more than one here document operator on one line, the contents of the here documents are parsed in order: The contents of the first here document starts from the next line and ends before the first line containing the token that followed the first operator. Just after that line, the contents of the second here document starts, and so on.

The contents of here documents are treated literally: whitespaces, tabs, etc. remain as is. The exception is that, when the token is not quoted at all:

  • the contents are subject to parameter expansion, command substitution, arithmetic expansion. ๐Ÿ”ฅ
  • a backslash in the contents is treated as quotation if and only if it precedes $, `, ", or another backslash.
  • a backslash followed by a newline is treated as line continuation.

In here string, the token after the operator is subject to expansion as in redirection to files. The expansion result becomes the contents of the here string. A newline character is automatically appended to the end of here string contents.

Here string is yashโ€™s extension that is not defined in POSIX. Other shells like bash, ksh, and zsh have the same feature.


๐Ÿ‘‘ Pipeline redirection ๐Ÿ‘‘

Pipeline redirection allows opening pipelines that can be used for arbitrary purposes.

โœจ Pipeline redirection โœจ

>>| token

The token is subject to expansion as in redirection to files, but it is treated as a file descriptor rather than a pathname. Thus the expanded token must be a non-negative integer.

Pipeline redirection opens a new pipeline. The standard output (or the file descriptor specified before the operator, if any) is replaced with the file descriptor open for writing to the pipeline. The file descriptor specified by token is replaced with the file descriptor open for reading from the pipeline.

Pipeline redirection is yashโ€™s extension that is not defined in POSIX. ๐Ÿ’…


๐ŸŒˆ Process redirection ๐ŸŒˆ

Process redirection creates a pipeline connected to another command.

๐Ÿ’– Process redirection ๐Ÿ’–

<(command...)
>(command...)

In process redirection, the command specified is executed in a subshell. If the process redirection is of the form <(command...), the standard output of command is connected with a pipeline to the standard input of the command the redirection is associated with. If the process redirection is of the form >(command...), the standard input of command is connected with a pipeline to the standard output of the command the redirection is associated with.

Process redirection is yashโ€™s extension that is not defined in POSIX. Bash and zsh have a feature called process substitution, which uses the same syntax as yashโ€™s process redirection, but incompatibly differs in behavior. โš ๏ธ


๐Ÿ’– COMMAND EXECUTION ๐Ÿ’–

This section describes how commands are executed.

โœจ Execution of simple commands โœจ

A simple command is executed as follows:

  1. All tokens in the simple command are expanded except for assignment and redirection tokens. If an error occurs during expansion, the execution of the simple command is aborted with a non-zero exit status. ๐Ÿ˜ญ

Tokens are usually expanded in a multi-field context, but tokens that look like assignments are only subjected to the four expansions if the command is a declaration utility.

In the following steps, the first word of the expansion results is referred to as command name, and the other words as command arguments. If there is only one word of the expansion results, there are no command argument words. If there are none of the expansion results, there is no command name either.

  1. If the command name exists and there are any redirections specified in the command, they are processed. The word token after each redirection operator is expanded. If an error occurs during processing the redirections (including when expanding the word token), the execution of this simple command is aborted with a non-zero exit status.
ok so quick heads-up before we dive in โ€” other shells do redirections in a whole different step. POSIX literally does not specify the order in which redirections and assignments get processed when there's no command name, or when the name is a special built-in. so likeโ€ฆ don't come for yash about it ๐Ÿ’…
  1. Assignments that you put in the command, if there are any, get processed. For each assignment token, the value gets expanded and assigned to the variable you named. If an error pops up during assignments (yep, including while expanding the values you're trying to assign), the whole simple command gets aborted with a non-zero exit status. no mercy, bestie.
    • If there's no command name, or the name is a special built-in, the assignments are permanent โ€” the assigned values stick around after the command is done (until you reassign the variable, obvi).
    • Otherwise? The assignments are temporary โ€” those values only live during the execution of this one simple command. blink and they're gone ๐Ÿฆ‹

The assigned variables get automatically exported when the command name is specified, or when the all-export option is enabled.

note: other shells be doing the most with assignments. For special built-ins and functions, assigned variables might not be exported. And for functions, assigned variables might be persistent โ€” like, they stay even after the simple command finishes. not here tho ๐Ÿ’€
  1. If there's no command name, the redirections get processed in a subshell, and then command execution just ends. If an error happens in the redirections, the exit status of the simple command is non-zero. If there were any command substitutions performed in the assignments, the exit status of the simple command is that of the last executed command substitution. Otherwise? Exit status is zero. โœจ
  2. A command to be executed gets determined using the command search algorithm, and then the command is executed.
    • If it's an external command, it gets executed by creating a new subshell and calling the exec system call in that subshell. The command name and arguments get passed to the executed command. Exported variables get passed along as environment variables.
    • If it's a built-in, the built-in runs with the command arguments passed to it. Exception time: in the POSIXly-correct mode, the built-in is not executed if it's an elective built-in. ๐Ÿ™ˆ
    • If it's a function, the contents of the function get executed with the command arguments as function arguments.

If the command was executed, the exit status of this simple command is that of the executed command. If the algorithm failed to determine a command, no command is executed and the exit status is 127. If the shell failed to execute the determined command, the exit status is 126. If the executed command was killed by a signal, the exit status is the signal number plus 384. And if the elective built-in wasn't executed in the POSIXly-correct mode, the exit status is 127.

note: in shells that aren't yash, the exit status may differ when a command gets killed by a signal, because POSIX only requires that the exit status be "greater than 128." that's it. that's the whole requirement. yash is just built different ๐Ÿ‘‘

If the shell is not in the POSIXly-correct mode and the algorithm failed to determine a command, the command

eval -i -- "${COMMAND_NOT_FOUND_HANDLER-}"

gets evaluated. While that command is executing, positional parameters are temporarily set to the command name and arguments that resulted in the first step. Any local variables defined during that execution get removed once it's finished. The HANDLED local variable is automatically defined, with its initial value being the empty string. If the HANDLED variable has a non-empty value when the execution of the command string finishes, the shell pretends the command was successfully determined and executed. And in that case, the exit status of the simple command is that of the command string. iconic behaviour, honestly ๐Ÿซถ


๐Ÿ’– Declaration utility ๐Ÿ’–

Declaration utilities are a predefined set of command names that have special word expansion semantics. If a simple command is a declaration utility and a word token in the simple command has the form of an assignment, that word gets expanded in exactly the same manner as an assignment.

Declaration utilities are detected when commands are parsed (not when command words are expanded), so a command name word that expands to a declaration utility name does not trigger the declaration utility expansion semantics. periodt.

note: some shells that aren't yash detect declaration utilities after a command name word is expanded, which lets you dynamically produce declaration utility names. cute, but not us ๐ŸŒธ

These built-ins are the declaration utilities in yash:

  1. export
  2. local
  3. readonly
  4. typeset

On top of that, the command built-in gets treated as a declaration utility if the name is followed by another word that names a declaration utility. sneaky but valid โœจ


A command that gets executed in a simple command is determined by the command name using this algorithm:

  1. If the command name contains a slash (/), the whole name is treated as the pathname of an external command. That external command is determined as the executed command.
  2. If the command name is a special built-in, that built-in is determined as the executed command.
  3. If the command name is the name of an existing function, that function is determined as the executed command.
  4. If the command name is a mandatory or elective built-in, that built-in is determined as the executed command.
  5. If the command name is an extension built-in and the shell is not in the POSIXly-correct mode, that built-in is determined as the executed command.
  6. The shell searches the PATH for a command to execute:

The value of the PATH variable is separated by colons. Each separated part is considered a directory pathname (an empty pathname denotes the current working directory). The shell searches the directories in the order they appear, and checks if any directory directly contains an executable regular file whose name equals the command name. If such a file is found:

  • If the command name is the name of a substitutive built-in, that built-in is determined as the executed command.
  • Otherwise, the file is determined as the executed command. (And yes, that file will be executed as an external command.)

If no such file is found, no command is determined as the executed command. ๐Ÿ’€

When the shell finds a file that matches the command name during the search above, it remembers the pathname of that file if it's an absolute path. So when the algorithm runs for the same command name again, the shell skips the searching and directly determines the command to be executed. Slay, efficiency. But if an executable regular file no longer exists at that remembered pathname, the shell searches again to update the remembered pathname. You can manage these remembered pathnames using the hash built-in. ๐Ÿงš


๐ŸŒˆ Termination of the shell ๐ŸŒˆ

The shell exits when it reaches the end of input and has parsed and executed all input commands, or when the exit built-in is executed. The exit status of the shell is that of the last command the shell executed (or zero if no commands were executed). BUT โ€” and this is the twist โ€” if the exit status minus 384 is a signal number, the shell terminates itself with that signal instead of exiting with the exit status. main character exit ๐Ÿ’…

If an exit handler has been registered by the trap built-in, that handler is executed just before the shell exits. The exit status of the commands executed in the handler does not affect the exit status of the shell. (it's giving "not my problem, respectfully")

If a non-interactive shell hits one of these errors, it immediately exits with a non-zero exit status:

  • A command can't be parsed because of a syntax error (except during shell initialization).
  • An error occurs during execution of a special built-in in the POSIXly-correct mode.
  • A redirection error occurs in a simple command whose command name is a special built-in, and the shell is in the POSIXly-correct mode.
  • An assignment error occurs in a simple command.
  • An error occurs during expansion (except during shell initialization).
note: some shells other than yash exit when they fail to find a command to execute during command search. yash said no and moved on ๐Ÿฆ‹

๐ŸŽ€ Functions ๐ŸŽ€

Functions let you execute a compound command as a simple command. A function can be defined by the function definition command and executed by a simple command. You can use the unset built-in to remove function definitions. (rip bozo)

There are no functions predefined when yash is started. clean slate, bestie โœจ

A function is executed by executing its body, which is a compound command. While the function is being executed, positional parameters are set to the arguments given to the function. The old positional parameters get restored when the function execution finishes. we love a good cleanup ๐Ÿซถ

๐ŸŒธ Local variables ๐ŸŒธ

Local variables are temporary variables that are defined in a function and exist during the function execution only. They can be defined by the typeset built-in, or implicitly created by a for loop. They get removed when the function execution finishes.

Local variables may hide variables that were already defined before the function execution started. An existing variable becomes inaccessible if a local variable of the same name is defined in a function. The old variable becomes accessible again when the function execution finishes. it's giving secret identity reveal ๐Ÿ”ฅ

You cannot create a local variable when you're not executing a function. If you try, a normal variable gets created instead. sorry, rules are rules ๐Ÿ™ˆ


๐Ÿฆ‹ Command execution environment ๐Ÿฆ‹

The shell holds these properties during execution:

  • The working directory
  • Open file descriptors
  • The file creation mask (umask)
  • The set of signals whose handler is set to "ignore" (trap)
  • Environment variables
  • Resource limits (ulimit)

Those properties are inherited from the invoker of the shell to the shell, and from the shell to each external command the shell executes. hand-me-downs, but make it technical ๐Ÿ’œ

The properties can be changed during the execution of the shell by built-in commands, variable assignments, etc. so yes, you can absolutely change your whole vibe mid-run ๐Ÿ˜Œ

Subshells ๐Ÿซง

A subshell is literally a copy of the shell process โ€” like, a whole clone of your shell, no cap. Subshells are used in execution of groupings, pipelines, etc.

Subshells inherit functions, aliases, etc. defined in the shell as well as the properties above since subshells are copies of the shell process. Notable exceptions are:

  • Traps registered by the trap built-in are all reset in subshells except for ones whose action is set to โ€œignoreโ€. (See below)
  • The interactive mode and job control are disabled in subshells. Jobs are not inherited by subshells.

Subshells are executed independently of the original shell, so changes of any properties above do not affect those of the original shell.

Note ๐Ÿ’ก โ€” If the subshell contains a single trap built-in, some shells (but not yash) may not reset the traps on entry to the subshell.

๐Ÿ’… INTERACTIVE MODE ๐Ÿ’…

The interactive mode is a mode of the shell intended for direct interaction with a user. If yash is in the interactive mode, it is called an interactive shell.

Whether a shell runs in the interactive mode or not is determined in the invocation of the shell. After the shell has started up, the interactive mode cannot be switched on or off. Like, it's locked in, bestie. ๐Ÿ”’

When the shell is interactive:

  • Initialization scripts are executed during invocation.
  • The shell checks for mail and prints a command prompt when it reads a command. Job status changes are also reported if job control is active. Line-editing may be used depending on the capability of the terminal.
  • Commands executed are automatically registered in command history.
  • When an asynchronous command is started, its job number and process ID are printed to the standard error.
  • If a command executed by the shell is killed by a signal other than SIGINT and SIGPIPE, the shell reports the fact to the standard error.
  • The filename token is subject to pathname expansion in file redirection.
  • The shell does not exit when it encounters a syntax or expansion error during command execution. (cf. Termination of the shell)
  • The shell does not exit when it receives the SIGINT, SIGTERM, or SIGQUIT signal.
  • A signal handler can be changed by the trap built-in even if the handler had been set to โ€œignoreโ€ when the shell was invoked.
  • The value of the - special parameter contains i.
  • The shell's locale reflects the value of the LC_CTYPE variable whenever the value is changed (if the shell is not in the POSIXly-correct mode).
  • Commands are executed even when the exec option is off.
  • The ignore-eof option takes effect when enabled.
  • When the shell reaches the end of input or the exit built-in is executed, the shell checks if there is any stopped job. If so, the shell prints a warning and does not actually exit. ๐Ÿ™ˆ
  • The suspend built-in by default cannot stop the shell if it is a session leader.
  • The shell does not exit when the dot built-in fails to find a script file to read.
  • The shell does not exit when the exec built-in fails to execute a command.
  • When a job finished for which the wait built-in has been waiting, the fact is reported (only if job control is active and not in the POSIXly-correct mode).
  • A prompt is printed when the read built-in reads a second or following line.

โœจ Prompts โœจ

The interactive shell prints a prompt just before it reads a command. The contents of the prompt is specified by the value of the PS1 and PS2 variables. The former is used for reading the first line of the command and the latter for other lines.

When the prompt is printed, the variable value is subjected to parameter expansion, command substitution, and arithmetic expansion (but note that the POSIX standard requires parameter expansion only). The result of the expansion is parsed by the rules below to make the actual prompt string, which is printed to the standard error.

In the POSIXly-correct mode, each exclamation mark (!) in the string is substituted with the command history number of the command that is being input. Two adjacent exclamation marks (!!) are printed as a single exclamation. Other characters are printed intact.

If the shell is not in the POSIXly-command mode, the following notations can be used to format the prompt string. Notations are replaced with the strings designated in the list below. Characters that are not interpreted as notations are printed intact.

  • \a โ€” Bell character (ASCII code: 7)
  • \e โ€” Escape character (ASCII code: 27)
  • \j โ€” The number of jobs in the shell.
  • \n โ€” Newline character (ASCII code: 10)
  • \r โ€” Carriage return character (ASCII code: 13)
  • \! โ€” The command history number of the command that is being input
  • \$ โ€” # if the shell's effective user ID is 0; $ otherwise.
  • \\ โ€” Backslash
  • \[, \] โ€” These two notations can surround part of the prompt string that is not visible on the terminal. The surrounded part is ignored when the shell counts the number of characters that is displayed on the terminal, thus making characters correctly aligned on the terminal when the prompt string contains special invisible characters.
  • \ffontspecs. โ€” When line-editing is active, this notation is replaced with special characters to change font styles on the terminal if the terminal is capable of it. If line-editing is inactive or the terminal is incapable of changing font styles, this notation is silently ignored. One or more of the following can be used for fontspecs:
    • k โ€” Change font color to black
    • r โ€” Change font color to red
    • g โ€” Change font color to green
    • y โ€” Change font color to yellow
    • b โ€” Change font color to blue
    • m โ€” Change font color to magenta
    • c โ€” Change font color to cyan
    • w โ€” Change font color to white
    • K โ€” Change background color to black
    • R โ€” Change background color to red
    • G โ€” Change background color to green
    • Y โ€” Change background color to yellow
    • B โ€” Change background color to blue
    • M โ€” Change background color to magenta
    • C โ€” Change background color to cyan
    • W โ€” Change background color to white
    • t โ€” Make font color or background brighter (can only be used just after one of the characters above)
    • d โ€” Change font and background colors to normal
    • s โ€” Make font standout
    • u โ€” Make font underlined
    • v โ€” Make font and background colors reversed
    • b โ€” Make font blink
    • i โ€” Make font dim
    • o โ€” Make font bold
    • x โ€” Make font invisible
    • D โ€” Make color and style normal

The actual colors of font and background are defined by the terminal. Different terminals may use different colors.

In addition to the normal prompt, a prompt string can be displayed to the right of the cursor if line-editing is active. Those prompts are called right prompts. The contents of right prompts are defined by the value of the PS1R and PS2R variables, each corresponding to the PS1 and PS2 variables.

Using the above-said notations, the font style of command strings the user inputs can be changed as well as that of prompts. The font style of command strings is defined by the value of the PS1S and PS2S variables, each corresponding to the PS1 and PS2 variables. The value can contain the \ffontspecs. notation only. If you enable command line prediction, the predicted part of the command line can also be styled with the PS1P and PS2P variables.

When the shell is not in the POSIXly-correct mode, the prompt variables can be defined with a name prefixed with YASH_ (e.g. YASH_PS1). This allows using a different prompt string than that in the POSIXly-correct mode.

When the shell is not in the POSIXly-correct mode:

  • The value of the PROMPT_COMMAND variable is executed before each prompt.
  • The value of the POST_PROMPT_COMMAND variable is executed after each line is input. While the execution, the COMMAND variable is set to the just input line. You can even modify the variable to manipulate the command to be executed. If you unset the variable, the command will not be executed. ๐Ÿซข

๐ŸŽ€ Command history ๐ŸŽ€

Command history is a feature of the shell that remembers executed commands to allow re-executing them later. Commands executed in the interactive mode are automatically saved in the command history. Saved commands can be edited and re-executed using line-editing and the fc and history built-ins.

Commands are saved line by line. Lines that do not contain any non-whitespace characters are not saved in the history. Lines that start with whitespaces are not saved when the hist-space option is on.

Command history is saved in a file. When history is first used after an interactive shell was started, the shell opens a file to save history in. The filename is specified by the value of the HISTFILE variable. If the file contains history data when opened, the data is restored to the shell's history. The file contents are updated in real time as the user inputs commands into the shell. If the HISTFILE variable is not set or the file cannot be opened successfully, history is not saved in the file, but the history feature will be functional in all other respects.

The number of commands saved in history is specified by the value of the HISTSIZE variable. The shell automatically removes old history data so that the number of saved commands does not exceed the value. If the HISTSIZE variable is not set or its value is not a natural number, 500 items will be saved in history.

The shell looks at the value of the HISTFILE and HISTSIZE variables only when the history feature is first used after the shell was started. โ€œThe history feature is usedโ€ when:

  • the fc or history built-in is executed,
  • line-editing is used (regardless of whether or not history data is recalled in line-editing), or
  • a command is input to the shell

Therefore, the variables should be set in initialization scripts. ๐Ÿ’–

Same HISTSIZE energy, bestie โ€” if a bunch of yash instances are all sharing one history file, they're all reading the same history data, so a command one shell ran can totally get recalled on another shell instance. Shells sharing history should have the same HISTSIZE value so they manipulate history data properly. ๐Ÿ’–

Yash's history data file has its own format that is incompatible with other kinds of shells. No cap. It's not gonna play nice with bash's file, periodt.

The HISTRMDUP variable can be set to remove duplicate history items. ๐ŸŽ€


Mail checking ๐Ÿ“ฌ

An interactive shell can notify receipt of email. The shell periodically checks the modification date/time of a file specified by the user. If the file has been modified since the previous check, the shell prints a notification message โ€” except when the shell is not in the POSIXly-correct mode and the file is empty. By specifying a mailbox file to be checked, the shell will print a message when the file has been modified, that is, some mail has been received. it's giving inbox zero but never ๐Ÿ’…

Check is done just before the shell prints a command line prompt. The interval of checks can be specified by the MAILCHECK variable in seconds. If the variable value is 0, check is done before every prompt. If the variable value is not a non-negative integer, no checks are done. โญ

The file whose modification time is checked is specified by the MAIL variable. The variable value should be set to the pathname of the file.

If you want to check more than one file or customize the notification message, you can set the MAILPATH variable instead of the MAIL variable. When the MAILPATH variable is set, the MAIL variable is ignored. The value of the MAILPATH variable should be set to one or more colon-separated pathnames of files to be checked. Each pathname can be followed by a percent sign (%) and a custom notification message, which is printed when the corresponding file has been modified. If the pathname contains a percent sign, it should be quoted by a backslash. The specified message is subject to parameter expansion. ๐Ÿฆ‹

For example, if the value of the MAILPATH variable is /foo/mail%New mail!:/bar/mailbox%You've got mail:/baz/mail\%data, the shell will print

  • New mail! when the file /foo/mail has been modified
  • You've got mail when the file /bar/mailbox has been modified
  • the default message when the file /baz/mail%data has been modified.

โœจ JOB CONTROL โœจ

Job control is a function of the shell that executes multiple commands simultaneously and suspends/resumes the commands. main character energy for your processes, fr fr. ๐Ÿ‘‘

When job control is active:

  • Every subshell executed by the shell is assigned a process group ID that is different from the shell's process group ID. The process group ID is unique except for the subcommands of the same pipeline, which share the same process group ID. Such a process group is called a job.
    • Exceptionally, subshells created for executing a command substitution or process redirection remain in the same process group as the shell.
  • If a process in a job is suspended while the shell is waiting for the job to complete, the shell stops waiting and continues to the next command as if the job has finished. The shell remembers the job as suspended so that it can be resumed later.
    • If the shell is interactive in this case, the shell discards any remaining commands that have been read but not executed, and continues to the next command line prompt.
  • If a job is executed synchronously, the shell sets the foreground process group of the terminal to the process group of the job. When the job is finished (or suspended), the shell gets back to the foreground.
  • If the shell is interactive, job status is reported before every command line prompt as if the command jobs -n is executed.
  • The standard input of an asynchronous command is not automatically redirected to /dev/null. ๐Ÿ™ˆ
  • The shell does not stop when it receives the SIGTTIN, SIGTTOU, or SIGTSTP signal. Subshells created for executing a command substitution or process redirection do not stop when they receive the SIGTSTP signal.
  • The value of the - special parameter contains m.
  • When a job finished for which the wait built-in has been waiting, the fact is reported (only if the shell is interactive and not in the POSIXly-correct mode).

When job control is inactive, processes executed by the shell have the same process group ID as the shell. The shell treats asynchronous commands as an uncontrolled job.

You can use the following built-ins to manipulate jobs:

  • jobs โ€” prints existing jobs
  • fg and bg โ€” run jobs in the foreground or background
  • wait โ€” waits for jobs to be finished
  • disown โ€” forgets jobs
  • kill โ€” sends a signal to jobs

An interactive job-controlling shell reports jobs status before every prompt by default. You can set the following options to make the shell report status at other timings:

  • notify โ€” the shell reports immediately whenever job status changes.
  • notify-le โ€” the shell reports immediately when job status changes while line-editing.

A job is removed from the shell's job list when:

  • it has finished and the jobs built-in reported it,
  • the wait built-in successfully waited for the job to finish, or
  • the disown built-in removed the job.

Jobs are not removed from the list when an interactive shell automatically reports the status of jobs.

Note ๐Ÿ’œ The word "stop" is synonymous to "suspend" in the context of job control.

Job ID ๐ŸŽ€

Some built-ins use the following notation, which is called job ID, to specify a job to operate on:

  • %, %%, %+ โ€” the current job
  • %- โ€” the previous job
  • %n โ€” the job that has job number n, where n is a positive integer
  • %string โ€” the job whose name begins with string
  • %?string โ€” the job whose name contains string

The current job and previous job are jobs selected by the shell according to the following rules:

  • When there is one or more suspended jobs, the current job is selected from them.
  • When there is one or more suspended jobs other than the current job, the previous job is selected from them.
  • The current and previous jobs are always different. When the shell has only one job, it is the current job and there is no previous job.
  • When the current job finished, the previous job becomes the current job.
  • When the current job is changed, the old current job becomes the previous job except when the old job finished.
  • When the foreground job is suspended, the job becomes the current job.

Yash has some options to modify the rules of the current/previous job selection. (The rules above have priority over the options below.) โญ

  • cur-async โ€” When a new asynchronous command is started, it becomes the current job.
  • cur-bg โ€” When a job is resumed by the bg built-in, the job becomes the current job.
  • cur-stop โ€” When a job is suspended, it becomes the current job.

The current and previous jobs are not changed as long as the rules above are met.

The rules of the current/previous job selection defined in the POSIX standard are looser than yash's rules above. Other POSIX-compliant shells may select the current and previous jobs differently. ๐ŸŒˆ

๐Ÿ’– BUILT-IN COMMANDS ๐Ÿ’–

Okay bestie, so built-in commands are commands that are literally inside the shell โ€” the shell runs them itself, no external program gets launched. Iconic behaviour, honestly. โœจ


๐ŸŽ€ Types of built-in commands ๐ŸŽ€

Yash has several types of built-in commands and I'm gonna walk you through every single one, no skips. ๐Ÿ’…

Special built-in commands are the most important kinds of built-in commands. They run regardless of whether the corresponding external commands exist or not. Results of variable assignments that happen in a simple command that invokes a special built-in last after the command has finished. Also โ€” and listen up, this matters โ€” in the POSIXly-correct mode, a non-interactive shell immediately exits with a non-zero exit status when an error occurs in a simple command executing a special built-in. ๐Ÿ‘‘

Mandatory built-in commands and elective built-in commands are similar to special built-ins in that they do not need external commands to be executed, but they differ in that they may be overridden by functions. While mandatory built-ins are always available, you cannot use elective built-ins in the POSIXly-correct mode because POSIX only reserves their names without defining their behavior. ๐Ÿ™ˆ

An extension built-in command is a built-in that is not mentioned in POSIX. Like an elective built-in, it can be executed without an external command when the POSIXly-correct mode is off. However, when the POSIXly-correct mode is on, the shell works as if the built-in does not exist. ๐Ÿ’€

Substitutive built-in commands work on behalf of external commands found in PATH. These built-ins improve execution speed by bypassing invocation overheads for external programs. Slay, efficiency. ๐Ÿ”ฅ


๐Ÿฆ‹ Syntax of command arguments ๐Ÿฆ‹

In this section we explain common rules about command arguments. The built-in commands of yash follow the rules unless otherwise stated.

There are two types of command arguments. One is options and the other is operands. An option is an argument that starts with a hyphen (-) and changes the way the command behaves. Some options take arguments. An operand is an argument that is not an option and specifies objects the command operates on.

If you specify more than one option to a command, the order of the options are normally not significant. The order of operands, however, affects the command behavior. fr fr. ๐Ÿ‘€

An option is either a single-character option or a long option. A single-character option is identified by one alphabetic character. A long option is identified by multiple alphabetic characters. The POSIX standard only prescribes single-character options, so in the POSIXly-correct mode you cannot use long options.

A single-character option is composed of a hyphen followed by a letter. For example, -a is a single-character option. A single-character option that takes an argument requires the argument to be just after the option name.

Example 4. The set built-in and single-character options

For the set built-in, -m is a single-character option that does not take an argument and -o is one that takes an argument.

  • set -o errexit -m
  • set -oerrexit -m

In these two command lines, errexit is the argument to the -o option.

In the second example above, the -o option and its argument are combined into a single command line argument. The POSIX standard deprecates that style and any POSIX-conforming applications must specify options and their arguments as separate command line arguments, although yash accepts both styles. So like, yash is being generous, but POSIX said no. ๐Ÿ˜ญ

You can combine single-character options that do not take arguments into a single command line argument. For example, the three options -a, -b and -c can be combined into -abc. Cute, right? ๐ŸŽ€

A long option is composed of two hyphens followed by an option name. For example, --long-option is a long option. You can omit some last characters of a long option name as long as it is not ambiguous. For example, you can use --long instead of --long-option if there is no other options beginning with --long. Like a single-character option, a long option that takes an argument requires the argument to be a command line argument just after the option name or to be specified in the same command line argument as the option name, separated by an equal sign (=).

Example 5. The fc built-in and long options

For the fc built-in, --quiet is a long option that does not take an argument and --editor is one that takes an argument.

  • fc --editor vi --quiet
  • fc --editor=vi --quiet

In these command lines, vi is the argument to the --editor option.

Arguments that are not options (nor arguments to them) are interpreted as operands. The POSIX standard requires all options should be specified before any operands. Therefore, in the POSIXly-correct mode, any arguments that come after the first operand are interpreted as operands (even if they look like options). If not in the POSIXly-correct mode, you can specify options after operand. ๐Ÿ‘‘

Regardless of whether the shell is in the POSIXly-correct mode or not, an argument that is just composed of two hyphens (--) can be used as a separator between options and operands. All command line arguments after the -- separator are interpreted as operands, so you can specify operands that start with a hyphen correctly using the separator.

Example 6. Options and operands to the set built-in

  • set -a -b -- -c -d

In this example, -a and -b are options and -c and -d are operands. The -- separator itself is neither an option nor an operand.

Regardless of whether the shell is in the POSIXly-correct mode or not, an argument that is just composed of a single hyphen (-) is interpreted as an operand. ๐Ÿ’…


๐ŸŒˆ LINE-EDITING ๐ŸŒˆ

With the line-editing feature, you can edit the command text when you input a command to an interactive shell. It not only works as a simple visual-interface editor, but also is integrated with the command history. You can recall, edit, and execute commands in the history with line-editing instead of using the fc built-in. Obsessed honestly. ๐Ÿ’–

Line-editing has two editing modes, the vi and emacs modes, which each have their own key binding settings. By switching editing modes, you can change key bindings used in line-editing. Each mode has a corresponding shell option, which determines whether the mode is currently active or not. No more than one mode can be active at a time, so the options for the other modes are automatically turned off when you turn on the option for one mode. The whole line-editing feature is deactivated when those options are off. ๐Ÿ™ˆ

When an interactive shell is started, the vi mode is automatically activated if the standard input and error are both connected to a terminal.

Line-editing can be used only when the standard input and error are both connected to a terminal. If not, the shell silently falls back to the normal input mechanism. While line-editing is being used, the shell uses the termios interface to change I/O settings of the terminal and the terminfo interface to parse input key sequences. โœจ


๐Ÿ’œ Shell options on line-editing ๐Ÿ’œ

The following options can be set by the set built-in to enable line-editing and choose an editing mode to activate:

  • vi โ€” activates the vi mode.
  • emacs โ€” activates the emacs mode.

The other line-editing-related options are:

  • le-always-rp โ€” When this options is enabled, the right prompt is always visible: when the cursor reaches the right prompt, it moves to the next line from the original position, which would otherwise be overwritten by input text.
  • le-comp-debug โ€” When enabled, internal information is printed during completion, which will help debugging completion scripts.
  • le-conv-meta โ€” When enabled, the 8th bit of each input byte is always treated as a meta-key flag, regardless of terminfo data.
  • le-no-conv-meta โ€” When enabled, the 8th bit of each input byte is never treated as a meta-key flag, regardless of terminfo data.
The le-conv-meta and le-no-conv-meta options cannot be both enabled at a time. When either is enabled, the other is automatically disabled. When neither is enabled, the 8th bit may be treated as a meta-key flag depending on terminfo data.
  • le-predict โ€” activates command line prediction.
  • le-predict-empty โ€” When enabled, and command line prediction is active, suggestions are also provided for empty input lines.
  • le-prompt-sp โ€” When enabled, the shell prints a special character sequence before printing each prompt so that every prompt is printed at the beginning of a line.

This option is enabled by default. ๐ŸŽ€

  • le-visible-bell โ€” When enabled, the shell flashes the terminal instead of sounding an alarm when an alert is required.
  • le-trim-right โ€” When enabled, the right prompt, set by YASH_PS1R will right justify all the way to the edge of the screen, the default behaviour is to leave an empty space for the cursor when the prompt is too large.

๐ŸŒธ Editing modes ๐ŸŒธ

The vi mode is an editing mode that offers key bindings similar to that of the vi editor. The vi mode has two sub-modes that are switched during editing: the insert and command modes. The sub-mode is always reset to the insert mode when line-editing is started for a new command line. In the insert mode, most characters are inserted to the buffer as typed. In the command mode, input characters are treated as commands that move the cursor, insert/delete text, etc. ๐Ÿ’…

The emacs mode offers key bindings similar to the emacs editor. Most characters are inserted to the buffer as typed, but more characters are treated as commands than the vi insert mode.

Another sub-mode is used while you enter search keywords. The sub-mode is called the search mode, which offers slightly different key bindings depending on the active editing mode. ๐Ÿ”


โญ Line-editing commands โญ

All characters the user enters while line-editing is active are treated as line-editing commands listed below. The bindkey built-in allows customizing the key bindings of each mode (except for the search mode).

The list below shows not only the functions of commands but also the default key bindings. The keywords โ€œvi-insertโ€, โ€œvi-commandโ€, โ€œvi-searchโ€, โ€œemacsโ€, โ€œemacs-searchโ€ means the vi insert mode, the vi command mode, the search mode for the vi mode (the vi search mode), the emacs mode, and the search mode for the emacs mode (the emacs search mode), respectively. ๐Ÿ‘‘

Some commands take an argument that affects the function of the commands. For example, the forward-char command moves the cursor by as many characters as specified by the argument. To specify an argument, use the digit-argument command just before another command that takes an argument.

๐Ÿฆ‹ Basic editing commands ๐Ÿฆ‹

  • noop โ€” Do nothing.
    • vi-command โ€” \^[
  • alert โ€” Alert.
  • self-insert โ€” drops the character you just typed right at the cursor. โœจ tiny but iconic. Characters that came in through escape sequences cannot be inserted, sorry bestie, that's just the rules.
    • vi-insert, emacs: \\
  • insert-tab โ€” slaps a literal tab character at the cursor position. go off, indentation. ๐Ÿ’…
    • emacs: \^[\^I
  • expect-verbatim โ€” the next character you type gets inserted raw at the cursor. this is the cheat code for characters that self-insert can't handle โ€” except a null character ('\0'), that one is simply not invited to the party. ๐Ÿ™ˆ
    • vi-insert, vi-search, emacs-search: \^V
    • emacs: \^Q, \^V
  • digit-argument โ€” hands the digit you typed to the next command as an argument. big brain move fr fr.

This one can be bound to a digit or a hyphen. Want to pass โ€œ12โ€ as an argument to the forward-char command in vi mode? just type 12l. that's it. that's the whole trick. ๐Ÿ”ฅ

  • vi-command: 1, 2, 3, 4, 5, 6, 7, 8, 9
  • emacs: \^[0, \^[1, \^[2, \^[3, \^[4, \^[5, \^[6, \^[7, \^[8, \^[9, \^[-
  • bol-or-digit โ€” if there's no argument it acts like beginning-of-line; if there is one it acts like digit-argument. two moods, one command. ๐Ÿฆ‹
    • vi-command: 0
  • accept-line โ€” done editing this line, bestie! a newline gets appended automatically and the shell runs it. ๐Ÿ‘‘

Heads up: if command line prediction is active, whatever the current prediction is (if there even is one) gets ignored. peek at the accept-prediction command if you want the other flavour.

  • vi-insert, vi-command, emacs: \^J, \^M, \et
  • emacs-search: \^M, \et
  • abort-line โ€” yeet the whole buffer and finish editing as if you'd typed an empty line.
    • vi-insert, vi-command, vi-search, emacs, emacs-search: \!, \^C
  • eof โ€” abandon the current buffer and finish editing as if the shell hit the end of input. which, yeah, normally makes the shell exit. ๐Ÿ’€
  • eof-if-empty โ€” acts like eof when the buffer is empty; acts like alert any other time.
    • vi-insert, vi-command: \#, \^D
  • eof-or-delete โ€” acts like eof when the buffer is empty; acts like delete-char otherwise.
    • emacs: \#, \^D
  • accept-with-hash โ€” if the current line doesn't start with a hash sign (#), or you gave this command no argument, it pops a hash sign onto the beginning of the line. otherwise it takes that leading hash sign back off. and then? then the line gets accepted, just like accept-line. ๐ŸŽ€
    • vi-command: #
    • emacs: \^[#
  • accept-prediction โ€” like accept-line, but it keeps the predicted part in. no take-backs on the prediction. โœจ
  • setmode-viinsert โ€” switch over to vi insert mode.
    • vi-command: i, \I
  • setmode-vicommand โ€” switch over to vi command mode.
    • vi-insert: \^[
  • setmode-emacs โ€” switch over to emacs mode.
  • expect-char, abort-expect-char โ€” bestie, these are not for you. they're not meant for use by the user at all; the shell uses them internally to build some of the other commands. iykyk. ๐Ÿ™ˆ
  • redraw-all โ€” reprints the prompt and the current line to the terminal. pass any argument at all and the terminal screen gets cleared first. ๐ŸŒธ
    • vi-insert, vi-command, vi-search, emacs, emacs-search: \^L
  • clear-and-redraw-all โ€” clears the terminal screen and reprints the prompt and the current line. pass any argument and the screen is not cleared. yes, it's the inverse, don't think about it too hard. ๐Ÿ˜ญ
    • vi-insert, vi-command, vi-search, emacs, emacs-search: \^L

๐ŸŒˆ Motion commands ๐ŸŒˆ

Motion commands move the cursor around the line. most of them accept an argument โ€” hand them one and they repeat the cursor motion that many times. pass โ€œ4โ€ as an argument to forward-char, for example, and the cursor advances by four characters. periodt. ๐Ÿ’…

the shell has a whole bunch of definitions for what counts as a โ€œwordโ€ as a unit of distance:

  • A bigword is one or more adjacent non-whitespace characters.
  • A semiword is one or more adjacent characters that contain no whitespaces or punctuations.
  • An emacsword is one or more adjacent alphanumeric characters.
  • A viword is either:
    • one or more adjacent alphanumeric characters and/or underscores (_), or
    • one or more adjacent characters that contain none of alphanumeric characters, underscores, and whitespaces.
  • forward-char โ€” move the cursor to the next character. โœจ
    • vi-insert: \R
    • vi-command: l, (space), \R
    • emacs: \R, \^F
  • backward-char โ€” move the cursor to the previous character.
    • vi-insert: \L
    • vi-command: h, \B, \L, \?, \^H
    • emacs: \L, \^B
  • forward-bigword โ€” move the cursor to the next bigword.
    • vi-command: W
  • end-of-bigword โ€” move the cursor to the next end of a bigword.
    • vi-command: E
  • backward-bigword โ€” move the cursor to the previous bigword.
    • vi-command: B
  • forward-semiword โ€” move the cursor to the next semiword.
  • end-of-semiword โ€” move the cursor to the next end of a semiword.
  • backward-semiword โ€” move the cursor to the previous semiword.
  • forward-viword โ€” move the cursor to the next viword.
    • vi-command: w
  • end-of-viword โ€” move the cursor to the next end of a viword.
    • vi-command: e
  • backward-viword โ€” move the cursor to the previous viword.
    • vi-command: b
  • forward-emacsword โ€” move the cursor to the next emacsword.
    • emacs: \^[f, \^[F
  • backward-emacsword โ€” move the cursor to the previous emacsword.
    • emacs: \^[b, \^[B
  • beginning-of-line โ€” move the cursor to the beginning of the line. main character positioning. ๐Ÿ‘‘
    • vi-insert, vi-command: \H
    • emacs: \H, \^A
  • end-of-line โ€” move the cursor to the end of the line.
    • vi-insert: \E
    • vi-command: $, \E
    • emacs: \E, \^E
  • go-to-column โ€” move the cursor to the nth character on the line, where n is the argument. no argument? assume n = 1.
    • vi-command: |
  • first-nonblank โ€” move the cursor to the first non-blank character on the line.
    • vi-command: ^
  • find-char โ€” move the cursor to the first position where a character you type right after this command appears after the current cursor position.
    • vi-command: f
    • emacs: \^]
  • find-char-rev โ€” move the cursor to the last position where a character you type right after this command appears before the current cursor position.
    • vi-command: F
    • emacs: \^[\^]
  • till-char โ€” move the cursor to the first position just before a character you type right after this command appears after the current cursor position.
    • vi-command: t
  • till-char-rev โ€” move the cursor to the last position just after a character you type right after this command appears before the current cursor position.
    • vi-command: T
  • refind-char โ€” redo the last find-char, find-char-rev, till-char, or till-char-rev command. ๐Ÿฆ‹
    • vi-command: ;
  • refind-char-rev โ€” redo the last find-char, find-char-rev, till-char, or till-char-rev command, but in the reverse direction.
    • vi-command: ,

๐Ÿ’– Editing commands ๐Ÿ’–

Editing commands modify the contents of the buffer. most of them accept an argument too โ€” pass one and they repeat the modification that many times. ๐Ÿ’…

Texts deleted by commands whose name starts with โ€œkillโ€ get saved in the kill ring, and from there the deleted contents can be restored back into the buffer. the most recent 32 texts are kept in the kill ring. ๐ŸŽ€
  • delete-char โ€” with no argument, deletes the character at the current cursor position; otherwise it acts like kill-char.
    • vi-insert, emacs: \X
  • delete-bigword โ€” with no argument, deletes a bigword at the current cursor position; otherwise it acts like kill-bigword.
  • delete-semiword โ€” with no argument, deletes a semiword at the current cursor position; otherwise it acts like kill-semiword.
  • delete-viword โ€” with no argument, deletes a viword at the current cursor position; otherwise it acts like kill-viword.
  • delete-emacsword โ€” with no argument, deletes an emacsword at the current cursor position; otherwise it acts like kill-emacsword.
  • backward-delete-char โ€” with no argument, deletes the character just before the current cursor position; otherwise it acts like backward-kill-char.
    • vi-insert, emacs: \B, \?, \^H
  • backward-delete-bigword โ€” with no argument, deletes a bigword just before the current cursor position; otherwise it acts like backward-kill-bigword.
  • backward-delete-semiword โ€” with no argument, deletes a semiword just before the current cursor position; otherwise it acts like backward-kill-semiword.
    • vi-insert: \^W
  • backward-delete-viword โ€” with no argument, deletes a viword just before the current cursor position; otherwise it acts like backward-kill-viword.
  • backward-delete-emacsword โ€” with no argument, deletes an emacsword just before the current cursor position; otherwise it acts like backward-kill-emacsword.

okay continuing straight into the editing-command lineup, and bestie these next ones are all about deleting stuff and bringing it back like it never left ๐Ÿ’…


  • delete-line โ€” deletes the whole buffer contents. the entire thing. gone. no crumbs. ๐Ÿ’€
  • forward-delete-line โ€” deletes every character from wherever the cursor is sitting right now all the way to the end of the buffer.
  • backward-delete-line โ€” deletes every character before the cursor. the past is in the past, periodt.
    • vi-insert: \$, \^U
  • kill-char โ€” deletes the character sitting at the current cursor position and drops it into the kill ring. ๐ŸŽ€ (yes, "kill ring" โ€” it's like a clipboard but with main character energy)
    • vi-command: x, \X
  • kill-bigword โ€” deletes a bigword at the current cursor position and adds it to the kill ring.
  • kill-semiword โ€” deletes a semiword at the current cursor position and adds it to the kill ring.
  • kill-viword โ€” deletes a viword at the current cursor position and adds it to the kill ring.
  • kill-emacsword โ€” deletes an emacsword at the current cursor position and adds it to the kill ring.
    • emacs: \^[d, \^[D
  • backward-kill-char โ€” deletes the character right before the cursor and adds it to the kill ring.
    • vi-command: X
  • backward-kill-bigword โ€” deletes a bigword just before the cursor and adds it to the kill ring.
    • emacs: \^W
  • backward-kill-semiword โ€” deletes a semiword just before the cursor and adds it to the kill ring.
  • backward-kill-viword โ€” deletes a viword just before the cursor and adds it to the kill ring.
  • backward-kill-emacsword โ€” deletes an emacsword just before the cursor and adds it to the kill ring.
    • emacs: \^[\B, \^[\?, \^[\^H
  • kill-line โ€” deletes the whole buffer contents and adds it to the kill ring. lowkey dramatic, highkey useful โœจ
  • forward-kill-line โ€” deletes everything from the cursor to the end of the buffer and adds it to the kill ring.
    • emacs: \^K
  • backward-kill-line โ€” deletes everything before the cursor and adds it to the kill ring.
    • emacs: \$, \^U, \^X\B, \^X\?
  • put-before โ€” inserts the last-killed text before the cursor and moves the cursor onto the last character that got inserted.
    • vi-command: P
  • put โ€” inserts the last-killed text after the cursor and moves the cursor onto the last character that got inserted.
    • vi-command: p
  • put-left โ€” inserts the last-killed text before the cursor and moves the cursor onto the last character that got inserted.
    • emacs: \^Y
  • put-pop โ€” replaces the text you just put with the next older killed text. it's giving time travel ๐Ÿฆ‹
ngl this one has rules: it can be used only right after a put-before, put, put-left, or put-pop command. miss that window and it's a no.
  • emacs: \^[y, \^[Y
  • undo โ€” cancels the modification made by the last editing command. we love a take-backsies queen ๐Ÿ‘‘
    • vi: u
    • emacs: \^_, \^X\$, \^X\^U
  • undo-all โ€” cancels all modifications in the current buffer, restoring the initial contents. factory reset but make it cute.
    • vi: U
    • emacs: \^[\^R, \^[r, \^[R
  • cancel-undo โ€” cancels the cancellation done by the last undo or undo-all command. un-take-backsies fr fr.
    • vi: \^R
  • cancel-undo-all โ€” cancels all the cancellation done by all the most recent undo and undo-all commands.
  • redo โ€” repeats the modification made by the last editing command.
    • vi-command: .

โœจ Completion commands โœจ

  • complete โ€” completes a word just before the cursor and, if there's more than one candidate, shows you the whole list of candidates. ๐ŸŒธ
  • complete-next-candidate โ€” acts like the complete command when candidates aren't being listed; otherwise it selects the next candidate in the list.
    • vi-insert, emacs: \^I
  • complete-prev-candidate โ€” acts like the complete command when candidates aren't being listed; otherwise it selects the previous candidate in the list.
    • vi-insert, emacs: \bt
  • complete-next-column โ€” acts like the complete command when candidates aren't being listed; otherwise it selects the first candidate in the next column of the list.
  • complete-prev-column โ€” acts like the complete command when candidates aren't being listed; otherwise it selects the first candidate in the previous column of the list.
  • complete-next-page โ€” acts like the complete command when candidates aren't being listed; otherwise it selects the first candidate on the next page of the list.
  • complete-prev-page โ€” acts like the complete command when candidates aren't being listed; otherwise it selects the first candidate on the previous page of the list.
  • complete-list โ€” completes a word just before the cursor.
    • if you pass no argument, a list of completion candidates is shown.
    • otherwise, the word is completed with the nth candidate, where n is the argument.
    • emacs: \^[?, \^[=
  • complete-all โ€” replaces a word just before the cursor with all possible completion candidates, each one separated by a space. giving maximalist ๐Ÿซถ
    • emacs: \^[*
  • complete-max โ€” completes a word just before the cursor with the longest prefix shared by all possible completion candidates.
  • complete-max-then-list โ€” works like complete-max the first time you use it, then like complete when you keep using it successively.
  • complete-max-then-next-candidate โ€” works like complete-max the first time, then like complete-next-candidate when used successively.
  • complete-max-then-prev-candidate โ€” works like complete-max the first time, then like complete-prev-candidate when used successively.
  • clear-candidates โ€” clears the list of completion candidates. bye bye list ๐Ÿ‘‹

๐Ÿฆ‹ Vi-specific commands ๐Ÿฆ‹

  • vi-replace-char โ€” replaces the character at the cursor position with a character you type just after this command.
    • vi-command: r
  • vi-insert-beginning โ€” moves the cursor to the beginning of the line and switches to vi insert mode.
    • vi-command: I
  • vi-append โ€” moves the cursor to the next character and switches to vi insert mode.
    • vi-command: I
  • vi-append-to-eol โ€” moves the cursor to the end of the line and switches to vi insert mode.
    • vi-command: A
  • vi-replace โ€” switches to vi insert mode and starts overwriting. while overwriting, the self-insert command replaces the character at the cursor position instead of inserting a character. overwriting ends when the editing mode is changed.
    • vi-command: R
  • vi-switch-case โ€” switches the case of the characters between the current and next cursor positions. this command must be followed by a motion command, which determines the next cursor position.
  • vi-switch-case-char โ€” switches the case of the character at the current cursor position and moves the cursor to the next character.
    • vi-command: ~
  • vi-yank โ€” adds the characters between the current and next cursor positions to the kill ring. this command must be followed by a motion command, which determines the next cursor position.
    • vi-command: y
  • vi-yank-to-eol โ€” adds the characters from the current cursor position to the end of the line to the kill ring.
    • vi-command: Y
  • vi-delete โ€” deletes the characters between the current and next cursor positions and adds them to the kill ring. this command must be followed by a motion command, which determines the next cursor position.
    • vi-command: d
  • vi-delete-to-eol โ€” deletes the characters from the current cursor position to the end of the line and adds them to the kill ring.
    • vi-command: D
  • vi-change โ€” deletes the characters between the current and next cursor positions and switches to vi insert mode. this command must be followed by a motion command, which determines the next cursor position.
    • vi-command: c
  • vi-change-to-eol โ€” deletes the characters from the current cursor position to the end of the line and switches to vi insert mode.
    • vi-command: C
  • vi-change-line โ€” deletes the whole buffer contents and switches to vi insert mode.
    • vi-command: S
  • vi-yank-and-change โ€” like the vi-change command, but the deleted text is added to the kill ring.
  • vi-yank-and-change-to-eol โ€” like the vi-change-to-eol command, but the deleted text is added to the kill ring.
  • vi-yank-and-change-line โ€” like the vi-change-line command, but the deleted text is added to the kill ring.
  • vi-substitute โ€” deletes the character at the current cursor position, adds it to the kill ring, and switches to vi insert mode.
    • vi-command: s
  • vi-append-last-bigword โ€” inserts a space and the last bigword from the most recent command history entry just after the current cursor position and switches to vi insert mode. if argument n is passed, the *n*th bigword in the entry gets inserted instead of the last one.
    • vi-command: _
  • vi-exec-alias โ€” executes the value of an alias named _c as editing commands, where c is a character you input just after this command.
    • vi-command: @
  • vi-edit-and-accept โ€” opens up an editor so you can โœจedit the current buffer contentsโœจ. Pass an argument n and it edits the entry whose number is n instead, bestie. If the VISUAL or EDITOR variable is defined, its value is used as the editor name โ€” otherwise it just grabs vi. When the editor finishes, the edited buffer contents gets accepted, same as the accept-line command would do it, unless the exit status of the editor is non-zero (in which case, nope, we're not accepting that ๐Ÿ’€).
    • vi-command: v
  • vi-complete-list โ€” literally complete-list but it also flips you into vi insert mode. Two birds ๐Ÿฆ‹
    • vi-command: =
  • vi-complete-all โ€” same energy as complete-all, plus it switches to the vi insert mode too.
    • vi-command: *
  • vi-complete-max โ€” complete-max's twin, and yep, it also drops you into vi insert mode.
    • vi-command: \\
  • vi-search-forward โ€” switches into vi search mode and kicks off a forward history search. ๐Ÿ”
    • vi-command: ?
  • vi-search-backward โ€” switches into vi search mode and starts a backward history search.
    • vi-command: /

Emacs-specific commands โœจ

  • emacs-transpose-chars โ€” grabs the character sitting just before the cursor and yeets it to the right. swap szn ๐Ÿ’…
    • emacs: \^T
  • emacs-transpose-words โ€” same deal but for an emacsword just before the cursor, moved to the right.
    • emacs: \^[t, \^[T
  • emacs-downcase-word โ€” makes the emacsword just after the cursor lowercase. quiet down bestie ๐Ÿคซ
    • emacs: \^[l, \^[L
  • emacs-upcase-word โ€” makes the emacsword just after the cursor uppercase. LOUDER ๐Ÿ“ข
    • emacs: \^[u, \^[U
  • emacs-capitalize-word โ€” capitalizes the first letter of the emacsword just after the cursor. main character treatment ๐Ÿ‘‘
    • emacs: \^[c, \^[u
  • emacs-delete-horizontal-space โ€” deletes the spaces hanging around the cursor. If any argument was passed, it only deletes spaces just before the cursor.
    • emacs: \^[\\
  • emacs-just-one-space โ€” clears the spaces around the cursor and leaves exactly one space. If an argument is specified, it leaves as many spaces as the argument says. one space, no crumbs ๐Ÿ“
    • emacs: \^[ (Escape followed by a space)
  • emacs-search-forward โ€” switches to emacs search mode and starts a forward history search.
    • emacs: \^S
  • emacs-search-forward-current โ€” same as emacs-search-forward, except the search gets initialised with whatever's currently chillin' in the line-edit buffer.
    • emacs: (no default binding listed)
  • emacs-search-backward โ€” switches to emacs search mode and starts a backward history search.
    • emacs: \^R
  • emacs-search-backward-current โ€” same as emacs-search-backward, but the search is initialised with the current contents of the line-edit buffer.
    • emacs: (no default binding listed)

  • oldest-history โ€” recalls the oldest entry in the history. Pass argument n and it recalls the entry whose number is n instead. Cursor position stays exactly where it was, untouched.
    • vi-command: (no default binding listed)
  • newest-history โ€” recalls the newest entry in the history. Pass argument n and it recalls entry number n instead. Cursor position remains unchanged.
    • vi-command: (no default binding listed)
  • return-history โ€” takes you back to the initial buffer, the one that corresponds to none of the existing history entries. Pass argument n and it recalls entry number n instead. Cursor position stays put.
    • vi-command: (no default binding listed)
  • oldest-history-bol โ€” recalls the oldest history entry and moves the cursor to the beginning of the line. Pass argument n and it recalls entry number n instead.
    • vi-command: G
  • newest-history-bol โ€” recalls the newest history entry and moves the cursor to the beginning of the line. Pass argument n and it recalls entry number n instead.
    • vi-command: (no default binding listed)
  • return-history-bol โ€” back to the initial buffer (the one matching none of the existing history entries) and the cursor hops to the beginning of the line. Pass argument n and it recalls entry number n instead.
    • vi-command: g
  • oldest-history-eol โ€” recalls the oldest history entry and moves the cursor to the end of the line. Pass argument n and it recalls entry number n instead.
    • emacs: \^[<
  • newest-history-eol โ€” recalls the newest history entry and moves the cursor to the end of the line. Pass argument n and it recalls entry number n instead.
    • emacs: (no default binding listed)
  • return-history-eol โ€” returns to the initial buffer corresponding to none of the existing history entries, cursor parked at the end of the line. Pass argument n and it recalls entry number n instead.
    • emacs: \^[>
  • next-history โ€” recalls the next history entry. Cursor position remains unchanged.
    • vi-command: (no default binding listed)
  • prev-history โ€” recalls the previous history entry. Cursor position remains unchanged.
    • vi-command: (no default binding listed)
  • next-history-bol โ€” recalls the next history entry and moves the cursor to the beginning of the line.
    • vi-command: j, +, \D, \^N
  • prev-history-bol โ€” recalls the previous history entry and moves the cursor to the beginning of the line.
    • vi-command: k, -, \U, \^P
  • next-history-eol โ€” recalls the next history entry and moves the cursor to the end of the line.
    • vi-insert, emacs: \D, \^N
  • prev-history-eol โ€” recalls the previous history entry and moves the cursor to the end of the line.
    • vi-insert, emacs: \U, \^P
  • search-again โ€” repeats the last command history search. run it back ๐Ÿ”
    • vi-command: n
  • search-again-rev โ€” repeats the last command history search but in the reverse direction.
    • vi-command: N
  • search-again-forward โ€” repeats the last command history search in the forward direction.
    • vi-command: (no default binding listed)
  • search-again-backward โ€” repeats the last command history search in the backward direction.
    • vi-command: (no default binding listed)
  • beginning-search-forward โ€” recalls the next history entry that starts with the same text as the text running from the beginning of the line up to the current cursor position. Cursor position remains unchanged.
    • vi-command: (no default binding listed)
  • beginning-search-backward โ€” recalls the previous history entry that starts with the same text as the text from the beginning of the line up to the current cursor position. Cursor position remains unchanged.
    • vi-command: (no default binding listed)

Search mode commands ๐Ÿ”Ž

  • srch-self-insert โ€” inserts the input character at the current cursor position. Characters escaped by escape sequences cannot be inserted, so don't even try ๐Ÿ’€
    • vi-search, emacs-search: \\
  • srch-backward-delete-char โ€” deletes the last character in the search text. And if the text is empty? Well:
    • it acts like the srch-abort-search command when you're in the vi search mode, or
    • it acts like the alert command when you're in the emacs search mode.
    • vi-search, emacs-search: \B, \?, \^H
  • srch-backward-delete-line โ€” deletes the whole search text. gone. periodt.
    • vi-search, emacs-search: \$, \^U
  • srch-continue-forward โ€” finds the next matching history entry.
    • emacs-search: \^S
  • srch-continue-backward โ€” finds the previous matching history entry.
    • emacs-search: \^R
  • srch-accept-search โ€” finishes the search mode and accepts the result currently being shown. slay, you found it โœจ
    • vi-search: \^J, \^M, \et
    • emacs-search: \^J, \^[
  • srch-abort-search โ€” aborts the search and restores the previous buffer contents. nvm bestie, we're putting it back ๐Ÿ™ˆ
    • vi-search: \^[
    • emacs-search: \^G

Escape sequences ๐ŸŒˆ

In the bindkey built-in, escape sequences are how we represent special keys โ€” think function keys, arrow keys, all that good stuff. Every escape sequence starts with a backslash (\), which means yes, there's also an escape sequence for a backslash itself. very meta of it ๐Ÿ’…

Below are the available escape sequences:

  • \\ โ€” Backslash (\)
  • \B โ€” Backspace
  • \D โ€” Down arrow
  • \E โ€” End
  • \H โ€” Home
  • \I โ€” Insert (Insert-char, Enter-insert-mode)
  • \L โ€” Left arrow
  • \N โ€” Page-down (Next-page)
  • \P โ€” Page-up (Previous-page)
  • \R โ€” Right arrow
  • \U โ€” Up arrow
  • \X โ€” Delete
  • \! โ€” INTR
  • \# โ€” EOF
  • \$ โ€” KILL
  • \? โ€” ERASE
  • \^@ โ€” Ctrl + @
  • \^A, \^B, ..., \^Z โ€” Ctrl + A, Ctrl + B, ..., Ctrl + Z
    • > heads up bestie: Ctrl + I, Ctrl + J, and Ctrl + M are tab, newline, and carriage return, respectively. not your usual letters, ik ๐Ÿ˜ญ
  • \^[ โ€” Ctrl + [ (Escape)
  • \^\ โ€” Ctrl + \
  • \^] โ€” Ctrl + ]
  • \^^ โ€” Ctrl + ^
  • \^_ โ€” Ctrl + _
  • \^? โ€” Ctrl + ? (Delete)
  • \F00, \F01, ..., \F63 โ€” F0, F1, ..., F63
  • \a1 โ€” Top-left on keypad
  • \a3 โ€” Top-right on keypad
  • \b2 โ€” Center on keypad
  • \bg โ€” Beginning
  • \bt โ€” Back-tab
  • \c1 โ€” Bottom-left on keypad
  • \c3 โ€” Bottom-right on keypad
  • \ca โ€” Clear-all-tabs
  • \cl โ€” Close
  • \cn โ€” Cancel
  • \co โ€” Command
  • \cp โ€” Copy
  • \cr โ€” Create
  • \cs โ€” Clear-screen or erase
  • \ct โ€” Clear-tab
  • \dl โ€” Delete-line
  • \ei โ€” Exit-insert-mode
  • \el โ€” Clear-to-end-of-line
  • \es โ€” Clear-to-end-of-screen
  • \et โ€” Enter (Send)
  • \ex โ€” Exit
  • \fd โ€” Find
  • \hp โ€” Help
  • \il โ€” Insert-line
  • \ll โ€” Home-down
  • \me โ€” Message
  • \mk โ€” Mark
  • \ms โ€” Mouse event
  • \mv โ€” Move
  • \nx โ€” Next-object
  • \on โ€” Open
  • \op โ€” Options
  • \pr โ€” Print (aka Copy) ๐Ÿ–จ๏ธ
  • \pv โ€” Previous-object
  • \rd โ€” Redo ๐Ÿ”
  • \re โ€” Resume
  • \rf โ€” Ref (Reference)
  • \rh โ€” Refresh
  • \rp โ€” Replace
  • \rs โ€” Restart
  • \sf โ€” Scroll-forward (Scroll-down)
  • \sl โ€” Select
  • \sr โ€” Scroll-backward (Scroll-up)
  • \st โ€” Set-tab
  • \su โ€” Suspend
  • \sv โ€” Save ๐Ÿ’พ
  • \ud โ€” Undo
  • \SE โ€” Shift + End
  • \SH โ€” Shift + Home
  • \SI โ€” Shift + Insert
  • \SL โ€” Shift + Left arrow
  • \SR โ€” Shift + Right arrow
  • \SX โ€” Shift + Delete
  • \Sbg โ€” Shift + Beginning
  • \Scn โ€” Shift + Cancel
  • \Sco โ€” Shift + Command
  • \Scp โ€” Shift + Copy
  • \Scr โ€” Shift + Create
  • \Sdl โ€” Shift + Delete-line
  • \Sel โ€” Shift + End-of-line
  • \Sex โ€” Shift + Exit
  • \Sfd โ€” Shift + Find
  • \Shp โ€” Shift + Help
  • \Smg โ€” Shift + Message
  • \Smv โ€” Shift + Move
  • \Snx โ€” Shift + Next
  • \Sop โ€” Shift + Options
  • \Spr โ€” Shift + Print
  • \Spv โ€” Shift + Previous
  • \Srd โ€” Shift + Redo
  • \Sre โ€” Shift + Resume
  • \Srp โ€” Shift + Replace
  • \Ssu โ€” Shift + Suspend
  • \Ssv โ€” Shift + Save
  • \Sud โ€” Shift + Undo

INTR, EOF, KILL, and ERASE are the special characters that get configured by the stty command, bestie. In a typical setup they're sent by typing Ctrl+C, Ctrl+D, Ctrl+U, and Ctrl+H respectively โ€” but heads up, some configurations use Ctrl+? instead of Ctrl+H for ERASE. ๐Ÿ™ˆ


๐Ÿฆ‹ Command line completion ๐Ÿฆ‹

Okay so this is the part where the shell basically finishes your sentences for you, and honestly? It's giving main character energy. By using the complete and complete-next-candidate commands, etc., you can complete command names, options, and operands. By default, the complete-next-candidate command is bound with the Tab key in the vi insert and emacs modes. โŒจ๏ธโœจ

Type a few first letters of a command name or pathname and hit the Tab key, and a list of matching names will be shown. You can choose a candidate from the list to complete the name by hitting the Tab key again. If there's only one matching name, no list will be shown and the name will directly be completed. Slay. ๐Ÿ’…

If the name to be completed contains characters like * and ?, it is treated as a pattern. The name on the command line will be directly substituted with all possible names matching the pattern (you cannot choose from a list โ€” no picking favorites, it's all of them or nothing, periodt).

Normally, command names are completed with command names and command arguments with pathnames. However, completion functions can be defined to refine completion results. ๐ŸŽ€

๐ŸŒธ Completion details ๐ŸŒธ

When doing completion for the first time after the shell has been started, the INIT file is loaded as if the command string . -AL completion/INIT is executed. If the file is not found, it is silently ignored (no drama, no error message, it just moves on). This automatic loading is mainly intended for loading completion functions bundled with the shell, but you can let the shell load your own functions by putting a file in the load path. ๐Ÿซถ

When completing a command name, the shell executes the completion//command function and when completing a command argument, the completion//argument function. If those completion functions are not defined, the shell just completes with command names or pathnames. When completing other names, such as the user name in tilde expansion and the parameter name in parameter expansion, completion functions are never used: the shell just completes with user names, parameter names, or whatever applicable. No cap.

Completion functions are executed without any arguments. The following local variables are automatically defined while executing completion functions:

  • IFS โ€” The value is the three characters of a space, a tab, and a newline, which are the default value of the variable.
  • WORDS โ€” This variable is an array whose elements are a command name and arguments that have already been entered before the argument being completed. When completing a command name, the array has no elements.
  • TARGETWORD โ€” The value is the partially entered command name or argument that is being completed.

Completion candidates are generated by executing the complete built-in during a completion function. ๐Ÿงš

โš ๏ธ Completion functions must not perform I/O to the terminal, or displayed text will be corrupted. And completion functions should run as quickly as possible for better user experience. Like, be fast, bestie, we're all waiting. ๐Ÿ˜ญ

While a completion function is being executed:

  • the POSIXly-correct mode and the err-return option are temporarily disabled,
  • the err-exit option is temporarily ignored, and
  • traps are not executed.

๐Ÿ”ฎ Command line prediction ๐Ÿ”ฎ

Okay so this one is experimental โ€” like, it's still in its testing era, so keep that in mind. When the le-predict option is enabled, the shell automatically tries to predict a command string fragment that follows the part of the command you've already typed in line-editing. It's literally reading your mind (it's not, it's reading your history, but let it have this). โœจ

For example, assume you have once typed the command ls Documents. Next time you start typing ls Doc, the shell will show uments just after the cursor. If you're satisfied with this suggestion, you can move the cursor to the right by the forward-char or any other motion commands instead of typing the rest of the command. After moving the cursor after the last s, you can use the accept-line command to execute the command. You can also use the accept-prediction command to immediately execute the suggested command without moving the cursor. Obsessed. ๐Ÿ’–

To distinguish the typed and predicted parts of a command string, you can change the font style of those parts by setting the PS1S and PS1P variables. ๐Ÿ’…

When you move the cursor to the right, the predicted part up to the cursor becomes the typed part as if you actually typed it. Moving the cursor to the left does not turn the typed part back to the prediction. Use deletion commands such as backward-delete-char to delete typed command fragment. (No take-backsies on typing, only deleting. ๐Ÿ’€)

The predicted part of the command is shown only when the cursor is at the end of the typed part.

By default, the predicted part is shown after you start typing a command string. By enabling the le-predict-empty option, the predicted part is also shown before you type the first character of the command string. ๐ŸŒˆ

The prediction algorithm suggests command fragments on the basis of the command history. The algorithm considers recent history entries more probable. It also takes command succession patterns into account. A predicted command fragment is not always a complete valid command because less probable part of the fragment is excluded from prediction. ๐Ÿ‘‘

๐Ÿ’… POSIXLY-CORRECT MODE ๐Ÿ’…

Okay so bestie, listen. Yash behaves like POSIX.1-2024, Shell & Utilities says it should, for the most part โ€” but some functionalities disobey POSIX for usability. When you need full POSIX-conformance, you can turn on the POSIXly-correct mode to make yash obey POSIX as much as it possibly can. ๐Ÿ‘‘

If yash is started with the name โ€œshโ€, the POSIXly-correct mode is automatically enabled. The -o posixly-correct command-line option also enables the POSIXly-correct mode. And after yash has already been started? You can enable it by executing the command string set -o posixly-correct. ๐Ÿช„

When the POSIXly-correct mode is on, yash not only tries to obey the requirements by POSIX, but also treats as errors most conditions where the behavior is undefined or unspecified by POSIX. As a result, most yash-specific functionalities are disabled in the POSIXly-correct mode. ๐Ÿ’€

Below is the complete list of the behavioral differences between when yash is in the POSIXly-correct mode and when not. When the POSIXly-correct mode is enabled:

  • Different initialization scripts are used.
  • If the shell was started with the -c option, sh -c (instead of yash -c) is printed as the script pathname on a syntax error.
  • Global aliases are not substituted.
  • Nested commands in a compound command must not be empty.
  • The for loop iteration variable is created as global, regardless of the for-local shell option. The variable must have a portable (ASCII-only) name.
  • The ;| and ;;& terminators cannot be used in a case command.
  • The ! keyword cannot be followed by ( without any whitespaces in-between.
  • The double-bracket command cannot be used.
  • The function keyword cannot be used for function definition. The function must have a portable (ASCII-only) name.
  • Simple commands cannot assign to arrays.
  • Changing the value of the LC_CTYPE variable after the shell has been initialized does not affect the shellโ€™s locale.
  • The RANDOM variable cannot be used to generate random numbers.
  • The LINENO, OLDPWD, OPTARG, OPTIND, and PWD variables cannot be made readonly.
  • Tilde expansion only expands ~ and ~username.
  • Parameter expansion cannot be nested. No indexes or modifiers with word2 are allowed.
  • The commands in a command substitution of the form $(commands) are parsed every time the substitution is executed.
  • In arithmetic expansion, fractional numbers and the ++ and -- operators cannot be used. All variables must be numeric.
  • The operand of a redirection cannot be the integer prefix to a next redirection operator.
  • A compound command with a redirection cannot be immediately followed by a keyword like } and fi.
  • In a redirection to a file, if the pathname expansion yields no pathname, it is not immediately treated as an error. Instead, the shell tries to proceed as if the pathname expansion did not occur.
  • A file descriptor must be readable and writable when duplicated by the <& and >& redirection operator, respectively.
  • Socket redirection, here strings, pipe redirection, and process redirection cannot be used.
  • When executing a simple command, failure in command search does not trigger execution of the COMMAND_NOT_FOUND_HANDLER variable.
  • Elective and extension built-ins cannot be executed.
  • Some built-ins behave differently. Especially, long command-line options (as well as some others) cannot be used.
  • A non-interactive shell exits when a special built-in is given a syntactically wrong arguments or when an error occurs in assignment or redirection with a special built-in.
  • An interactive shell does not execute the PROMPT_COMMAND or POST_PROMPT_COMMAND variable before and after a prompt, respectively. The values of the PS1, PS2, and PS4 variables are parsed differently. Prompt variables with a YASH_ prefix (e.g. YASH_PS1) are not used.
  • In mail checking, a notification message is printed if the file has been modified, regardless of whether the file is empty.

โœจ FREQUENTLY ASKED QUESTIONS AND TROUBLESHOOTING โœจ

๐Ÿฆ‹ How can I use Unicode (non-ASCII) characters? ๐Ÿฆ‹

You need to set locale environment variables to enable Unicode character support. ๐ŸŒˆ

If the variables have already been configured for your preference, you probably donโ€™t have to do anything. To check the current locale configurations, you can use the locale command:

$ locale
LANG=
LC_CTYPE="en_US.utf8"
LC_NUMERIC="en_US.utf8"
LC_TIME="en_US.utf8"
LC_COLLATE="en_US.utf8"
LC_MONETARY="en_US.utf8"
LC_MESSAGES="en_US.utf8"
LC_PAPER="en_US.utf8"
LC_NAME="en_US.utf8"
LC_ADDRESS="en_US.utf8"
LC_TELEPHONE="en_US.utf8"
LC_MEASUREMENT="en_US.utf8"
LC_IDENTIFICATION="en_US.utf8"
LC_ALL=en_US.utf8

In this example, the locale command shows that all the locale setting categories are configured for the English language, the United States region, and the UTF-8 encoding. โœจ

If the current configuration does not seem to match your preference, set the LC_ALL variable like this:

export LC_ALL=en_US.utf8

If you want to use other languages, regions, or encodings, you have to set the variable to a different value. Please consult your OSโ€™s documentation to learn how to configure these variables in detail. ๐Ÿ“–

If you want to apply the same configuration every time you start yash, write the command in $XDG_CONFIG_HOME/yash/rc or $XDG_CONFIG_HOME/yash/profile. The old locations ~/.yashrc and ~/.yash_profile are also supported as a fallback. ๐Ÿซถ

If yash still rejects Unicode characters being entered, see the section below regarding line-editing. ๐Ÿ‘‡

๐Ÿ’€ Line-editing does not work ๐Ÿ’€

First, type echo $TERM and see if it prints a sane value. xterm is the safest value that should work on any existing environment. Colored versions like xterm-16color and other terminal types like rxvt and vt100 may also work. All possible values can be listed by the toe command. Itโ€™s most desirable to choose a value that matches the actual terminal type you are using, but it might not work if the terminal type you chose is not supported on the system on which yash is running. If so, try changing the TERM value by export TERM=xterm, for example, to find a value that works. ๐Ÿ”

If line-editing works but you have trouble entering Unicode (non-ASCII) characters, try enabling the le-no-conv-meta option by set -o le-no-conv-meta. ๐Ÿ’…

๐ŸŽ€ FORMAL DEFINITION OF COMMAND SYNTAX ๐ŸŽ€

okay bestie, deep breath โ€” this chapter is the formal definition of the shell command language syntax. like, the actual grammar. it's giving "final boss of the spec" energy but we're getting through it together ๐Ÿ’–

heads up!! some of the syntactic features described below are not supported in the POSIXly-correct mode. no cap.

โœจ Tokenization โœจ

first things first: the characters of the input source code get delimited into tokens. and the rule is that tokens are delimited so that the earlier token spans as long as possible (maximal munch, slay ๐Ÿ’…). a sequence of one or more unquoted blank characters delimits a token.

these are the operator tokens:

& && ( ) ; ;; | || < << <& <( <<- <<< <> > >> >& >( >>| >| (newline)
note โœจ unlike other programming languages, the newline operator is a token rather than a white space. iconic honestly.

characters that are neither blank nor part of an operator compose a word token. words are parsed by the following parsing expression grammar:

Word
    (WordElement / !SpecialChar .)+

WordElement
    \ . /

    ' (!' .)*  ' /

    " QuoteElement* " /

    Parameter /

    Arithmetic /

    CommandSubstitution

QuoteElement
    \ ([$`"\] / <newline>) /

    Parameter /

    Arithmetic /

    CommandSubstitutionQuoted /

    ![`"\] .

Parameter
    $ [@*#?-$!  [:digit:]] /

    $ PortableName /

    $ ParameterBody

PortableName
    ![0-9] [0-9 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_]+

ParameterBody
    { ParameterNumber? (ParameterName / ParameterBody / $ ParameterBody
    / Arithmetic / CommandSubstitution) ParameterIndex?  ParameterMatch?
    }

ParameterNumber
    # ![}+=:/%] !([-?#] !})

ParameterName
    [@*#?-$!] /

    [[:alnum:] _]+

ParameterIndex
    [ ParameterIndexWord (, ParameterIndexWord)?  ]

ParameterIndexWord
    (WordElement / !["'],] .)+

ParameterMatch
    :? [-+=?]  ParameterMatchWord /

    (# / ## / % / %%) ParameterMatchWord /

    (:/ / / [#%/]?)  ParameterMatchWordNoSlash (/ ParameterMatchWord)?

ParameterMatchWord
    (WordElement / !["'}] .)*

ParameterMatchWordNoSlash
    (WordElement / !["'/}] .)*

Arithmetic
    $(( ArithmeticBody* ))

ArithmeticBody
    \ . /

    Parameter /

    Arithmetic /

    CommandSubstitution /

    ( ArithmeticBody ) /

    ![`()] .

CommandSubstitution
    $( CompleteProgram ) /

    ` CommandSubstitutionBody* `

CommandSubstitutionQuoted
    $( CompleteProgram ) /

    ` CommandSubstitutionBodyQuoted* `

CommandSubstitutionBody
    \ [$`\] /

    !` .

CommandSubstitutionBodyQuoted
    \ [$`\`] /

    !` .

SpecialChar
    [|&;<>()`\"' [:blank:]] / <newline>

the set of terminals of the grammar is the set of characters that can be handled on the environment in which the shell is run (aka the execution character set), except that the set does not contain the null character ('\0'). ๐ŸŒธ

and ngl, strictly speaking the definition above is not a complete parsing expression grammar, because the rule for CommandSubstitution (Quoted) depends on CompleteProgram, which is a non-terminal of the syntax. so it's a whole vibe. ๐Ÿฆ‹

๐Ÿ’… Token classification ๐Ÿ’…

once a word token is delimited, it may be further classified as an IO_NUMBER token, a reserved word, a name word, an assignment word, or just a normal word. classification other than the normal word is applied only when applicable in the context in which the word appears. (peep Tokens and keywords for the list of reserved words (keywords) and the context where a word can be recognized as a reserved word.)

a token is an IO_NUMBER token iff it's composed of digit characters only and is immediately followed by < or >.

an assignment token is a token that starts with a name followed by =:

AssignmentWord
    AssignmentPrefix Word

AssignmentPrefix
    Name =

Name
    ![[:digit:]] \[[:alnum:] _]+

๐ŸŒธ Comments ๐ŸŒธ

a comment begins with # and continues up to (but not including) the next newline character. comments are treated like a blank character and do not become part of a token. the initial # of a comment must appear as if it would otherwise be the first character of a word token; other #s are just treated as part of a word token.

Comment
    # (!<newline> .)*

โญ Syntax โญ

after tokens have been delimited, the sequence of tokens is parsed according to the context-free grammar defined below, where *, +, and ? mean exactly what they mean in standard regular expressions:

CompleteProgram
    NL* | CompoundList

CompoundList
    NL* AndOrList ((; | & | NL) CompleteProgram)?

AndOrList
    Pipeline ((&& | ||) NL* Pipeline)*

Pipeline
    !?  Command (| NL* Command)*

Command
    CompoundCommand Redirection* |

    FunctionDefinition |

    SimpleCommand

CompoundCommand
    Subshell |

    Grouping |

    IfCommand |

    ForCommand |

    WhileCommand |

    CaseCommand |

    DoubleBracketCommand |

    FunctionCommand

Subshell
    ( CompoundList )

Grouping
    { CompoundList }

IfCommand
    if CompoundList then CompoundList (elif CompoundList then
    CompoundList)* (else CompoundList)?  fi

ForCommand
    for Name ((NL* in Word*)? (; | NL) NL*)?  do CompoundList done

WhileCommand
    (while | until) CompoundList do CompoundList done

CaseCommand
    case Word NL* in NL* CaseList?  esac

CaseList
    CaseItem (;; NL* CaseList)?

CaseItem
    (?  Word (| Word)* ) CompleteProgram

DoubleBracketCommand
    [[ Ors ]]

Ors
    Ands (|| Ands)*

Ands
    Nots (&& Nots)*

Nots
    !*  Primary

Primary
    (-b | -c | -d | -e | -f | -G | -g | -h | -k | -L | -N | -n | -O | -o
    | -p | -r | -S | -s | -t | -u | -w | -x | -z) Word |

    Word (-ef | -eq | -ge | -gt | -le | -lt | -ne | -nt | -ot | -veq |
    -vge | -vgt | -vle | -vlt | -vne | = | == | === | =~ | != | !== | <
    | >) Word |

    ( Ors ) |

    Word

FunctionCommand
    function Word (( ))?  NL* CompoundCommand Redirection*

FunctionDefinition
    Name ( ) NL* CompoundCommand Redirection*

SimpleCommand
    (Assignment | Redirection) SimpleCommand? |

    Word (Word | Redirection)*

Assignment
    AssignmentWord |

    AssignmentPrefix( NL* (Word NL*)* )

Redirection
    IO_NUMBER?  RedirectionOperator Word |

    IO_NUMBER?  <( CompleteProgram ) |

    IO_NUMBER?  >( CompleteProgram )

RedirectionOperator
    < | <> | > | >| | >> | >>| | <& | >& | << | <<- | <<<

NL
    <newline>

okay these next bits are load-bearing, don't skip them bestie:

  • in the rule for Primary, Word tokens must not be ]]. and if a Primary starts with a Word, it must not be any of the possible unary operators allowed in the rule.
  • in the rule for SimpleCommand, a Word token is accepted only when the token can't be parsed as the first token of an Assignment.
  • in the rule for Assignment, the ( token must immediately follow the AssignmentPrefix token, with no blank characters in between. no sneaky spaces. ๐Ÿ™ˆ

here-document contents don't appear as part of the grammar above โ€” they're parsed just after the newline (NL) token that follows the corresponding redirection operator. ๐Ÿ˜Œ

๐Ÿฆ‹ Alias substitution ๐Ÿฆ‹

Word tokens are subject to alias substitution.

  • if a word is going to be parsed as a Word of a SimpleCommand, the word is subjected to alias substitution of any kind (normal and global aliases).
  • if a word is the next token after the result of an alias substitution, and the substitution string ends with a blank character, then that word is also subjected to alias substitution of any kind.
  • other words are subjected to global alias substitution unless the shell is in the POSIXly-correct mode.
  • tokens that are classified as reserved words are not subject to alias substitution.

๐Ÿ‘‘ ALIAS BUILT-IN ๐Ÿ‘‘

the alias built-in defines and/or prints aliases. very main character of it. ๐ŸŽ€

Syntax

  • alias [-gp] [name[=value]...]

Description

the alias built-in defines and/or prints aliases as specified by its operands. the printed aliases can be used as (part of) shell commands. and when you give it no operands at all, the built-in prints all currently defined aliases. ๐Ÿ’–

Options

  • -g, --global โ€” with this option, aliases are defined as global aliases; without it, they're defined as normal aliases.
  • -p, --prefix โ€” with this option, aliases are printed in a full command form like alias -g foo='bar'. without it, only the command operands get printed, like foo='bar'.

Operands

  • name โ€” the name of an alias that should be printed.
  • name=value โ€” the name and value of an alias that is being defined.

Exit status

the exit status of the alias built-in is zero unless there's any error. โœจ

Notes

  • the characters that cannot be used in an alias name are the space, tab, newline, and any of =$<>\'"`;&|()#. you can use any characters in an alias value, though. ๐Ÿ’…
  • the alias built-in is a mandatory built-in.
  • the POSIX standard defines no options for the alias built-in, so no options are available in the POSIXly correct mode.

๐Ÿ“ ARRAY BUILT-IN ๐Ÿ“

The array built-in prints or modifies arrays. bestie it is literally the whole vibe ๐Ÿ’…


โœจ Syntax โœจ

array
array name [value...]
array -d name [index...]
array -i name index [value...]
array -s name index value

๐ŸŒธ Description ๐ŸŒธ

When you run it with no option and no operands, the built-in prints all array definitions to the standard output in a form that can be parsed as commands. cute, right? it's self-documenting, no cap.

When you run it with name and values (but without an option), the built-in sets those values as the values of the array named name.

With the -d (--delete) option, the built-in removes the indexth values of the array named name. The number of values in the array will be decreased by the number of the indexes specified. If the indexth value does not exist, it is silently ignored โ€” like it simply didn't happen ๐Ÿ™ˆ.

With the -i (--insert) option, the built-in inserts values into the array named name. The number of values in the array will be increased by the number of the values specified. The values are inserted between the indexth and next values. If index is zero, the values are inserted before the first value. If index is larger than the number of values in the array, the values are appended after the last element. slay, no edge cases lost ๐ŸŽ€

With the -s (--set) option, the built-in sets value as the indexth value of the array named name. The array must have at least index values. no shortcuts here bestie.


๐Ÿ’– Options ๐Ÿ’–

  • -d, --delete โ€” Delete array values. bye bye ๐Ÿ‘‹
  • -i, --insert โ€” Insert array values. hello hello โœจ
  • -s, --set โ€” Set an array value. one slot, one value, periodt.

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • name โ€” The name of an array to operate on.
  • index โ€” The index to an array element. The first element has the index of 1. (yes bestie, one, not zero โ€” count like a human ๐Ÿซถ)
  • value โ€” A string to which the array element is set.

โญ Exit status โญ

The exit status of the array built-in is zero unless there is any error.


๐ŸŒˆ Notes ๐ŸŒˆ

The array built-in is not defined in the POSIX standard. Yash implements the built-in as an extension. iykyk ๐Ÿ’…

The command array name value... is equivalent to the assignment name=(value...).


๐Ÿ’œ BG BUILT-IN ๐Ÿ’œ

The bg built-in resumes a job in the background. main character energy for your suspended jobs.


โœจ Syntax โœจ

bg [job...]

๐ŸŒธ Description ๐ŸŒธ

The bg built-in sends the SIGCONT signal to the specified job. As a result, the job is resumed in the background (if it has been suspended). wake up babes ๐Ÿ”ฅ

The name of the job is printed when the job is resumed.

The built-in can be used only when job control is enabled. no job control, no bg, sorry bestie ๐Ÿ˜ญ

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • job โ€” The job ID of the job to be resumed.

More than one job can be specified at a time. The current job is resumed if none is specified.

The percent sign (%) at the beginning of a job ID can be omitted if the shell is not in the POSIXly-correct mode.


โญ Exit status โญ

The exit status of the bg built-in is zero unless there is any error.


๐ŸŒˆ Notes ๐ŸŒˆ

The bg built-in is a mandatory built-in.

The POSIX standard provides that the built-in shall have no effect when the job is already running. The bg built-in of yash, however, always sends the SIGCONT signal to the job. not me being extra ๐Ÿ’…


๐ŸŽ€ BINDKEY BUILT-IN ๐ŸŽ€

The bindkey built-in prints or modifies key bindings used in line-editing. this is the one that makes your keyboard go brrr.


โœจ Syntax โœจ

bindkey -aev [key [command]]
bindkey -l

๐ŸŒธ Description ๐ŸŒธ

When executed with the -l (--list) option, the built-in lists all available line-editing commands to the standard output. the full menu, no secrets ๐Ÿ“

When executed with one of the other options, the built-in prints or modifies key bindings for the editing mode specified by the option:

  • Without key or command, all currently defined bindings are printed to the standard output in a form that can be parsed as commands that restore the current bindings when executed. it's giving "save file" energy.
  • With key but without command, only the binding for the given key is printed.
  • With key and command, key is bound to command.

๐Ÿ’– Options ๐Ÿ’–

  • -a, --vi-command โ€” Print or modify bindings for the vi command mode.
  • -e, --emacs โ€” Print or modify bindings for the emacs mode.
  • -v, --vi-insert โ€” Print or modify bindings for the vi insert mode.

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • key โ€” A character sequence of one or more keys that is bound to an editing command. The sequence may include escape sequences.
  • command โ€” A line-editing command to which key is bound. If command is a single hyphen (-), key is unbound. unbinding, the breakup arc ๐Ÿ’€

โญ Exit status โญ

The exit status of the bindkey built-in is zero unless there is any error.


๐ŸŒˆ Notes ๐ŸŒˆ

The bindkey built-in is an elective built-in. It cannot be used in the POSIXly-correct mode because POSIX does not define its behavior.


๐Ÿฆ‹ BREAK BUILT-IN ๐Ÿฆ‹

The break built-in aborts a loop being executed. loop? cancelled bestie โœ‹


โœจ Syntax โœจ

break [nest]
break -i

๐ŸŒธ Description ๐ŸŒธ

When executed without the -i (--iteration) option, the built-in aborts a currently executed for, while, or until loop. When executed in nested loops, it aborts the nestth innermost loop. The default nest is 1. If the number of currently executed nested loops is less than nest, the built-in aborts the outermost loop. so dramatic, it goes all the way out ๐Ÿซถ

When executed with the -i (--iteration) option, the built-in aborts the currently executed (innermost) iterative execution.


๐Ÿ’– Options ๐Ÿ’–

  • -i, --iteration โ€” Abort an iterative execution instead of a loop.

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • nest โ€” The number of loops to abort, which must be a positive integer.

โญ Exit status โญ

The exit status of the break built-in is:

  • zero if a loop was successfully aborted.
  • that of the command that was executed just before the break built-in if an iterative execution was successfully aborted.

๐ŸŒˆ Notes ๐ŸŒˆ

The break built-in is a special built-in.

The POSIX standard defines no options for the break built-in; the built-in accepts no options in the POSIXly-correct mode.

Treatment of currently executed loops that are not lexically enclosing the break built-in is unspecified in POSIX. Examples of such loops include:

  • A loop invoking a function in which the break built-in is used.
  • A loop in which a trap action is executed in which the break built-in is used.

Yash does not allow breaking such loops. nope, not on my watch ๐Ÿ’…


๐Ÿ‘‘ CD BUILT-IN ๐Ÿ‘‘

The cd built-in changes the working directory. the classic. the icon. the moment.


โœจ Syntax โœจ

cd [-L|-P [-e]] [directory]

๐ŸŒธ Description ๐ŸŒธ

The cd built-in changes the working directory to the directory specified by the operand.

The pathname of the new working directory is assigned to the PWD variable, whose previous value is again assigned to the OLDPWD variable. The new PWD value will not contain any . or .. components except when the shell is in the POSIXly-correct mode and the new pathname begins with /....

If directory is a relative path that does not start with . or .., paths in the CDPATH variable are searched to find a new working directory. The search is done in a manner similar to the last step of command search, but a directory is sought instead of an executable regular file. If a new working directory was found from CDPATH, its pathname is printed to the standard output. If no applicable directory was found in the search, directory is simply treated as a pathname relative to the current working directory.

If the working directory was successfully changed, the value of the YASH_AFTER_CD variable is executed as a command unless the shell is in the POSIXly-correct mode. If the variable is an array, its values are executed iteratively (cf. eval built-in). it's giving "post-cd ritual" โœจ


๐Ÿ’– Options ๐Ÿ’–

  • -L, --logical โ€” Symbolic links in the pathname of the new working directory are not resolved. The new value of the PWD may include pathname components that are symbolic links.
  • -P, --physical โ€” Symbolic links in the pathname of the new working directory are resolved. The new value of the PWD variable never includes pathname components that are symbolic links.
  • -e, --ensure-pwd โ€” This option must be specified with -P (--physical). If this option is specified and if the absolute pathname of the new working directory cannot be obtained after successfully changing the working directory, the exit status is 1. If this option is not specified, the exit status is 0 in such a case.
  • --default-directory=directory โ€” If this option is specified and the directory operand is omitted, the argument to this option is used for the directory operand. If the directory operand is specified, this option is ignored.
The -L (--logical) and -P (--physical) options are mutually exclusive: only the last specified one is effective. If neither is specified, -L is assumed. last one wins, periodt ๐Ÿ’…

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • directory โ€” The pathname of the new working directory.

If directory is a single hyphen (-), the value of the OLDPWD variable is assumed for the new directory pathname, which is printed to the standard output. the "take me back" button ๐Ÿฅบ

If directory is omitted, the working directory is changed to the directory specified by the --default-directory=... option. If that option is not specified either, the default is the home directory.


โญ Exit status โญ

  • If the working directory is successfully changed, the exit status is 0, except in the following cases the exit status is 1:
    • The PWD or OLDPWD variable cannot be updated because the variable is read-only.
    • The -P and -e options are effective and the PWD variable cannot be updated because the absolute pathname of the new working directory cannot be obtained.
  • If the working directory cannot be changed because of an error in the underlying chdir system call, the exit status is 2.
  • If the -L option is effective and the new directory pathname cannot be canonicalized because of a .. component referring to a non-existent directory, the exit status is 3.
  • If directory cannot be processed because of an unset or empty HOME or OLDPWD, the exit status is 4.
  • If the command arguments are invalid or if an empty operand is supplied for directory, the exit status is 5.

If and only if the exit status is 2 or larger, the working directory is not changed. so read that number, bestie, it's the whole plot ๐Ÿ”ฅ


๐ŸŒˆ Notes ๐ŸŒˆ

The cd built-in is a mandatory built-in.

The POSIX standard does not define the use of the YASH_AFTER_CD variable or the --default-directory=... option.

The exit status of the commands in the YASH_AFTER_CD variable does not affect that of the cd built-in. no takebacks ๐Ÿ’…


๐Ÿซถ COLON BUILT-IN ๐Ÿซถ

The colon built-in does nothing. yes bestie, that's the whole job description. iconic behaviour.


โœจ Syntax โœจ

: [argument...]

๐ŸŒธ Description ๐ŸŒธ

The colon built-in does nothing. Any command line arguments are ignored. unbothered. moisturized. in its own lane. โœจ


โญ Exit status โญ

The exit status of the colon built-in is zero.


๐ŸŒˆ Notes ๐ŸŒˆ

The colon built-in is a special built-in.

Arguments are expanded and redirections are performed as usual. The colon and true built-ins have the same effect, but colon is a special built-in while true is a substitutive. they're twins but one of them has main character status ๐Ÿ‘‘


๐ŸŽ€ COMMAND BUILT-IN ๐ŸŽ€

The command built-in runs a command, or just, like, identifies it for you. Detective mode bestie ๐Ÿ”

โœจ Syntax โœจ

  • command [-befp] command [argument...]
  • command -v|-V [-abefkp] command...

๐Ÿ’– Description ๐Ÿ’–

If you don't hand it the -v (--identify) or -V (--verbose-identify) option, the built-in just executes command with its arguments, literally the same way the last step of execution of simple commands does it. Whether the thing gets treated as a built-in, an external command, or a function depends on which options you gave the command built-in. And lowkey important: the shell does not exit on argument syntax error etc., even if the command is a special built-in. no cap ๐Ÿซถ

With the -v (--identify) option, command gets identified instead. If it's found in $PATH, its full pathname gets printed. If it's a keyword, function, or built-in that is not found in $PATH, the command name is just printed as-is. If it's an alias, it's printed in a form like alias ll='ls -l'. And if the command is nowhere to be found? Nothing gets printed, and the exit status is non-zero. it's giving ghosted ๐Ÿ’€

The -V (--verbose-identify) option is like -v (--identify) but the output format is way more human-friendly. โœจ

๐ŸŒธ Options ๐ŸŒธ

  • -a, --alias โ€” Search for the command as an alias. Has to be used with the -v (--identify) or -V (--verbose-identify) option.
  • -b, --builtin-command โ€” Search for the command as a built-in.
  • -e, --external-command โ€” Search for the command as an external command.
  • -f, --function โ€” Search for the command as a function.
  • -k, --keyword โ€” Search for the command as a keyword. Has to be used with the -v (--identify) or -V (--verbose-identify) option.
  • -p, --standard-path โ€” Search the system's default PATH instead of the current $PATH.
  • -v, --identify โ€” Identify commands and print in the format defined in the POSIX standard.
  • -V, --verbose-identify โ€” Identify commands and print in a human-friendly format.

If none of the -a (--alias), -b (--builtin-command), -e (--external-command), -f (--function), and -k (--keyword) options is specified, these defaults are assumed:

  • Without the -v (--identify) or -V (--verbose-identify) option: -b -e
  • With the -v (--identify) or -V (--verbose-identify) option: -a -b -e -f -k

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • command โ€” A command to be executed or identified.
  • argument... โ€” Arguments passed to the executed command.

๐Ÿ’… Exit status ๐Ÿ’…

The exit status of the command built-in is:

  • Without the -v (--identify) or -V (--verbose-identify) option: the exit status of the executed command.
  • With the -v (--identify) or -V (--verbose-identify) option: zero unless there is any error.

๐Ÿ‘‘ Notes ๐Ÿ‘‘

The command built-in is a mandatory built-in.

In the POSIXly-correct mode, options other than -p, -v, and -V cannot be used, and at most one command can be specified. The POSIX standard does not allow specifying both -v and -V together, but yash does โ€” only the last specified one is effective. main character behaviour fr fr ๐Ÿ’…


โญ COMPLETE BUILT-IN โญ

The complete built-in generates completion candidates. Heads up bestie: this built-in can only be executed from completion functions, during command line completion. iykyk ๐Ÿงš

โœจ Syntax โœจ

complete [-A pattern] [-R pattern] [-T] [-P prefix] [-S suffix]
         [-abcdfghjkuv] [[-O] [-D description] word...]

๐Ÿ’– Description ๐Ÿ’–

The built-in generates completion candidates according to the arguments you gave it. And no matter how the candidates get generated, only candidates that match the word being completed are generated. vibe check passed โœ…

๐ŸŒธ Options ๐ŸŒธ

  • -A pattern, --accept=pattern โ€” Only accept candidates that match the pattern given by this option. When more than one of this option is specified, only candidates that match all of the patterns are generated.
  • -D description, --description=description โ€” Give a description of the word candidates. The description shows up beside the candidates in the candidate list.
  • -O, --option โ€” The candidates are treated as command line options. A hyphen is prepended to each candidate that is treated as an option.
  • -P prefix, --prefix=prefix โ€” Ignore prefix of the word being completed when generating candidates. The specified prefix must be the initial part of the word.

If the word being completed is file:///home/user/docume for example, the command line complete -P file:// -f will generate pathname candidates that complete /home/user/docume.

  • -R pattern, --reject=pattern โ€” Reject candidates that match the pattern given by this option. When more than one of this option is specified, only candidates that match none of the patterns are generated.
  • -S suffix, --suffix=suffix โ€” Append suffix to each generated candidate.
  • -T, --no-termination โ€” Do not append a space after the word is completed. Without this option, a space is appended to the completed word so that you don't have to type a space before the next word. so convenient ๐Ÿฅฐ

๐ŸŽ€ Options that select candidate types ๐ŸŽ€

  • -a, --alias โ€” Aliases. (same as --normal-alias --global-alias)
  • --array-variable โ€” Arrays.
  • --bindkey โ€” Line-editing commands the bindkey built-in accepts.
  • -b, --builtin-command โ€” Built-in commands. (same as --special-builtin --mandatory-builtin --elective-builtin --extension-builtin --substitutive-builtin)
  • -c, --command โ€” Commands. (same as --builtin-command --external-command --function)
  • -d, --directory โ€” Directories.
  • --dirstack-index โ€” Valid indices of the directory stack.
  • --elective-builtin โ€” Elective built-in commands.
  • --executable-file โ€” Executable regular files.
  • --extension-builtin โ€” Extension built-in commands.
  • --external-command โ€” External commands.
  • -f, --file โ€” Files (including directories).
  • --finished-job โ€” Job IDs of finished jobs.
  • --function โ€” Functions.
  • --global-alias โ€” Global aliases.
  • -g, --group โ€” User groups.
  • -h, --hostname โ€” Host names.
  • -j, --job โ€” Job IDs.
  • -k, --keyword โ€” Keywords.
  • --mandatory-builtin โ€” Mandatory built-in commands.
  • --normal-alias โ€” Normal aliases.
  • --regular-builtin โ€” Obsolete synonym for --extension-builtin --substitutive-builtin.
  • --running-job โ€” Job IDs of jobs that are being executed.
  • --scalar-variable โ€” Variables that are not arrays.
  • --semi-special-builtin โ€” Obsolete synonym for --mandatory-builtin --elective-builtin.
  • --signal โ€” Signals.
  • --special-builtin โ€” Special built-in commands.
  • --stopped-job โ€” Job IDs of jobs that are suspended.
  • --substitutive-builtin โ€” Substitutive built-in commands.
  • -u, --username โ€” Users' log-in names.
  • -v, --variable โ€” Variables.

If the -d (--directory) option is specified without the -f (--file) option, the -S / -T options are assumed. noted bestie ๐Ÿ“

Generated candidates for job IDs do not have leading percent signs (%). If the word being completed starts with a percent sign, the -P % option should be specified.

๐Ÿฆ‹ Operands ๐Ÿฆ‹

Operands are treated as completion candidates.

๐Ÿ’… Exit status ๐Ÿ’…

The exit status of the built-in is zero if one or more candidates were generated, one if no candidates were generated, or larger than one if an error occurred.

๐Ÿ‘‘ Notes ๐Ÿ‘‘

The complete built-in is an elective built-in. It can be used in completion functions even in the POSIXly-correct mode, because the mode is temporarily disabled during completion. slay ๐Ÿ’–


๐ŸŒˆ CONTINUE BUILT-IN ๐ŸŒˆ

The continue built-in skips an iteration of a loop that's currently being executed. hop out of this one, bestie ๐Ÿฆ‹

โœจ Syntax โœจ

  • continue [nest]
  • continue -i

๐Ÿ’– Description ๐Ÿ’–

When executed without the -i (--iteration) option, the built-in aborts the current iteration of a for, while, or until loop and starts the next iteration of the loop. When executed in nested loops, it affects the nestth innermost loop. The default nest is one. If the number of currently executed nested loops is less than nest, the built-in affects the outermost loop. ok so it just keeps going up until it finds one ๐Ÿซถ

When executed with the -i (--iteration) option, the built-in aborts the current iteration of (innermost) iterative execution.

๐ŸŒธ Options ๐ŸŒธ

  • -i, --iteration โ€” Skip an iterative execution instead of a loop.

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • nest โ€” The nestth innermost loop is affected. nest must be a positive integer.

๐Ÿ’… Exit status ๐Ÿ’…

The exit status of the continue built-in is:

  • zero if loop iteration was successfully skipped.
  • that of the command that was executed just before the continue built-in if iterative execution was successfully skipped.

๐Ÿ‘‘ Notes ๐Ÿ‘‘

The continue built-in is a special built-in.

The POSIX standard defines no options for the continue built-in; the built-in accepts no options in the POSIXly-correct mode.

Treatment of currently executed loops that are not lexically enclosing the continue built-in is unspecified in POSIX. no cap, that's literally undefined ๐Ÿ’€

Examples of such loops include:

  • A loop invoking a function in which the continue built-in is used
  • A loop in which a trap action is executed in which the continue built-in is used

Yash does not allow continuing such loops. periodt โœ‹

๐ŸŽ€ DIRS BUILT-IN ๐ŸŽ€

okay so the dirs built-in? it literally just prints out what's in your directory stack. that's it. that's the whole job. ๐Ÿ’…

โœจ Syntax โœจ

dirs [-cv] [index..]

โœจ Description โœจ

so bestie, the directory stack is this cute little feature that keeps a history of your working directories. think of it as a scrapbook of everywhere you've been ๐Ÿฆ‹. you use the pushd built-in to save a working directory into the stack, the popd built-in to recall a saved one, and dirs to actually peek at what's in there. those built-ins use the DIRSTACK array and the PWD variable to store the stack contents โ€” so modifying the array means modifying the stack contents. no cap, they're the same thing.

stack entries are indexed by signed integers, which is giving math class but make it useful:

  • index +0 = the current working directory
  • +1 = the last saved directory
  • +2 = the second last, and so on
  • negative indices go the reverse way: -0 is the first saved directory, -1 is the second, and -n is the current working directory if the stack has n entries

when you run it without the -c (--clear) option, dirs prints the current contents of the directory stack to standard output. with -c (--clear)? it clears the whole stack. bye ๐Ÿ‘‹

โœจ Options โœจ

  • -c, --clear โ€” clears the directory stack contents, except for the current working directory, which has index +0. so it's not a full wipe, it's a glow-up.
  • -v, --verbose โ€” prints the indices too when printing stack contents. main character energy, shows the receipts ๐Ÿ”ฅ

โœจ Operands โœจ

  • index โ€” the index of a stack entry to be printed.

you can specify more than one index, go off ๐Ÿ‘‘. if you don't specify any index, all the entries get printed.

โœจ Exit status โœจ

the exit status of dirs is zero unless there's any error. simple as.

โœจ Notes โœจ

the dirs built-in is an elective built-in. it cannot be used in the POSIXly-correct mode because POSIX does not define its behavior. ๐Ÿ˜ญ

๐Ÿฆ‹ DISOWN BUILT-IN ๐Ÿฆ‹

the disown built-in removes jobs. it's the "you're not my problem anymore" of built-ins, but like, politely.

โœจ Syntax โœจ

disown [-a] [job...}

โœจ Description โœจ

disown removes the specified jobs from the job list. the removed jobs will no longer be job-controlled, but the job processes continue execution โ€” unless they've been suspended, in which case, yeah, they're just sitting there. it's giving ghosted but the process still lives ๐Ÿ’€

โœจ Options โœจ

  • -a, --all โ€” removes all jobs. every single one. no survivors ๐ŸŽ€

โœจ Operands โœจ

  • job โ€” the job ID of the job to be removed.

you can specify more than one job ID. if you don't specify any, the current job is removed. and if the shell is not in the POSIXly-correct mode, you can drop the %-prefix of the job ID entirely. convenient โœจ

โœจ Exit status โœจ

the exit status of disown is zero unless there's any error.

โœจ Notes โœจ

the disown built-in is an elective built-in. it cannot be used in the POSIXly-correct mode because POSIX does not define its behavior. ๐Ÿ™ˆ

๐ŸŒธ DOT BUILT-IN ๐ŸŒธ

the dot built-in reads a file and executes the commands inside it. it's the original "run this script" move, and yes it's literally just a period. iconic minimalism.

โœจ Syntax โœจ

. [-AL] file [argument...]

โœจ Description โœจ

the dot built-in reads the specified file, parses its contents as commands, and executes them in the current command execution environment. so no new shell, no separate little universe โ€” it's all happening right here, right now.

if arguments are specified, positional parameters are temporarily set to them. the positional parameters get restored when dot finishes. if no arguments are specified, the positional parameters are not changed at all. so it cleans up after itself, we love a tidy queen ๐Ÿ’–

now, about finding the file: if file doesn't contain any slashes, the shell searches $PATH for a readable (but not necessarily executable) shell script file whose name is file, in the same manner as command search. if nothing turns up, the shell searches the current working directory โ€” unless you're in the POSIXly-correct mode. to be absolutely sure the file in the current working directory is the one used, start file with ./. periodt.

โœจ Options โœจ

  • -A, --no-alias โ€” disables alias substitution while parsing. sometimes you want the raw truth, yk?
  • -L, --autoload โ€” searches $YASH_LOADPATH instead of $PATH, regardless of whether file contains slashes. the file value is not considered relative to the current working directory.
heads up: the dot built-in treats as operands any command line arguments after the first operand. so like, everything after file is just arguments, no takebacks.

โœจ Operands โœจ

  • file โ€” the pathname of a file to be read.
  • arguments... โ€” strings to which positional parameters are set while execution.

โœจ Exit status โœจ

the exit status of dot is that of the last command executed. the exit status is zero if the file contains no commands to execute, and non-zero if a file was not found or could not be opened. ๐Ÿ˜ญ

โœจ Notes โœจ

the dot built-in is a special built-in.
a non-interactive shell immediately exits with a non-zero exit status if the dot built-in fails to find or open a file to execute. so yeah, that's a whole thing. dramatic but true.
the POSIX standard defines no options for the dot built-in; the built-in accepts no options in the POSIXly-correct mode.
the POSIX standard does not define the arguments... operands. it is an error to specify the arguments... operands in the POSIXly-correct mode.

โญ ECHO BUILT-IN โญ

the echo built-in prints its arguments. yes, the one you've been using since forever. it's the hello world of built-ins and honestly? legend behavior.

โœจ Syntax โœจ

echo [string...]

the built-in treats all command line arguments as operands, except for the options described below. any word that can't be parsed as an acceptable option is treated as an operand. options must precede all operands. and the wildest part: syntax errors never happen in the echo built-in. it simply refuses to fail at parsing ๐Ÿ’…

โœจ Description โœจ

echo prints the operand strings followed by a newline to the standard output. the strings are each separated by a space.

๐ŸŒˆ Escape sequences ๐ŸŒˆ

the ECHO_STYLE variable and the -e option enable escape sequences that get replaced with corresponding characters:

  • \a โ€” Bell character (ASCII code: 7)
  • \b โ€” Backspace (ASCII code: 8)
  • \c โ€” Nothing. after this escape sequence, no characters are printed at all.
  • \e โ€” Escape character (ASCII code: 27)
  • \f โ€” Form feed character (ASCII code: 12)
  • \n โ€” Newline character (ASCII code: 10)
  • \r โ€” Carriage return character (ASCII code: 13)
  • \t โ€” Horizontal tab character (ASCII code: 9)
  • \v โ€” Vertical tab character (ASCII code: 11)
  • \\ โ€” Backslash
  • \0xxx โ€” character whose code is xxx, where xxx is an octal number of at most three digits.

when escape sequences are not enabled, they're just printed intact. no processing, no drama.

๐ŸŒธ ECHO_STYLE variable ๐ŸŒธ

the ECHO_STYLE variable defines which options are accepted and whether escape sequences are enabled by default. the variable value should be set to one of these:

  • SYSV, XSI โ€” no options are accepted. escape sequences are always enabled.
  • BSD โ€” the -n option is accepted. escape sequences are never enabled.
  • GNU โ€” the -n, -e, and -E options are accepted. escape sequences are not enabled by default, but can be enabled by the -e option.
  • ZSH โ€” the -n, -e, and -E options are accepted. escape sequences are enabled by default, but can be disabled by the -E option.
  • DASH โ€” the -n option is accepted. escape sequences are always enabled.
  • RAW โ€” no options are accepted. escape sequences are never enabled.

when the ECHO_STYLE variable is not set, it defaults to SYSV.

โœจ Options โœจ

  • -n โ€” do not print a newline at the end.
  • -e โ€” enable escape sequences.
  • -E โ€” disable escape sequences.

โœจ Exit status โœจ

the exit status of echo is zero unless there's any error.

โœจ Notes โœจ

the POSIX standard does not define the ECHO_STYLE variable nor any options for the built-in. according to POSIX, the behavior of the built-in is implementation-defined when the first argument is -n or when any argument contains a backslash. for maximum portability, the printf built-in should be preferred over the echo built-in. fr fr, use printf.
although many values for the ECHO_STYLE variable are defined on the basis of other existing implementations, yash is not intended to exactly imitate those originals. Zsh's echo built-in interprets a single hyphen argument as a separator between options and operands. Yash does not support such use of hyphen. so don't come here expecting Zsh behavior, bestie ๐Ÿ™ˆ

๐Ÿ’œ EVAL BUILT-IN ๐Ÿ’œ

the eval built-in evaluates operands as commands. it's the "make this string become code" button and it is powerful.

โœจ Syntax โœจ

eval [-i] [command...]

the eval built-in requires that all options precede operands. any command line arguments after the first operand are all treated as operands.

โœจ Description โœจ

eval parses operands as commands and executes them in the current command execution environment. no new process, just vibes and execution โœจ

when executed without the -i (--iteration) option, all the operands are concatenated into one string โ€” with a space inserted between each operand โ€” and parsed/executed at once. one big blob, one big moment.

with the -i (--iteration) option, the built-in performs iterative execution: operands are parsed/executed one by one. and here's the fun part ๐ŸŽ€ if the continue built-in is executed with the -i (--iteration) option during the iterative execution, the execution of the current operand is aborted and the next operand is parsed/executed immediately. the break built-in with the -i (--iteration) option is similar, but the remaining operands are not parsed/executed. the value of the ? special parameter is saved before the iterative execution is started, and the parameter value is restored to the saved one after each iteration. so it keeps the receipts and puts them back, we love consistency ๐Ÿ’–

โœจ Options โœจ

  • -i, --iteration โ€” perform iterative execution.

โœจ Operands โœจ

  • command โ€” a string that is parsed and executed as commands.

โœจ Exit status โœจ

the exit status is zero if no command was specified, or if command contained no actual command that can be executed. otherwise โ€” that is, if the eval built-in executed one or more commands โ€” the exit status of eval is that of the last executed command.

โœจ Notes โœจ

the eval built-in is a special built-in.
the POSIX standard defines no options for the eval built-in; the built-in accepts no options in the POSIXly-correct mode.

โœจ EXEC BUILT-IN โœจ

The exec built-in is the one where the shell literally hands its whole self over to another external command. It doesn't fork, it doesn't copy โ€” it becomes the command. It's giving "I'm leaving and you can't come" energy. ๐Ÿ’…

๐Ÿ“ Syntax

exec [-cf] [-a name] [command [argument...]]

The exec built-in requires that all options precede operands. This is important, bestie, so that options to the exec built-in don't get confused with options to command. Any command line arguments after command are treated as arguments. Periodt.

๐Ÿฆ‹ Description

When the exec built-in is executed with command, the shell executes command with arguments in a manner similar to the last step of execution of a simple command. The differences are:

  • command is always treated as an external command, ignoring any existing functions and built-ins, and
  • the exec system call that starts the external command is called in the current command execution environment instead of a subshell, replacing the shell process with the new command process. ๐Ÿ˜ญ no going back

If the shell is not interactive, failure in execution of command causes the shell to exit immediately.

If an interactive shell that is not in the POSIXly-correct mode has a stopped job, the shell prints a warning message and refuses to execute command. Once the shell process is replaced with an external command, information about the shell's jobs is lost, so you will have to resume or kill the stopped jobs by sending signals by hand. To force the shell to execute command regardless, specify the -f (--force) option.

๐Ÿ’€ Forcing this while you've got stopped jobs means those jobs are just... gone from the shell's memory. Bye bestie, you'll have to signal them manually.

When executed without command, the built-in does nothing. As a side effect, however, redirection applied to the built-in remains in the current command execution environment even after the built-in finished. Sneaky little thing. ๐Ÿ™ˆ

๐ŸŒธ Options

  • -a name, --as=name โ€” Pass name, instead of command, to the external command as its name.
  • -c, --clear โ€” Pass to the external command only variables that are assigned in the simple command in which the built-in is being executed. Other environment variables are not passed to the command.
  • -f, --force โ€” Suppress warnings that would prevent command execution.

๐ŸŽ€ Operands

  • command โ€” An external command to be executed.
  • argument... โ€” Arguments to be passed to the command.

โญ Exit status

If the shell process was successfully replaced with the external command, there is no exit status since the shell process no longer exists. Like, it's not there anymore. There's nobody left to have an exit status. ๐Ÿ’€

The exit status is:

  • 127 if the command was not found,
  • 126 if the command was found but could not be executed, and
  • zero if no command was specified.

๐Ÿ’œ Notes

The exec built-in is a special built-in.

The POSIX standard defines no options for the exec built-in; the built-in accepts no options in the POSIXly-correct mode.


๐ŸŒˆ EXIT BUILT-IN ๐ŸŒˆ

The exit built-in does exactly what it says on the tin: it makes the shell process exit. Main character exit, no notes. ๐Ÿ‘‘

๐Ÿ“ Syntax

exit [-f] [exit_status]

๐Ÿฆ‹ Description

The exit built-in causes the current shell (or subshell) process to exit.

If an interactive shell has a stopped job, the shell prints a warning message and refuses to exit. To force the shell to exit regardless, specify the -f (--force) option or execute the built-in twice in a row. Yes, twice. The double-tap is real. ๐Ÿ’…

If an EXIT trap has been set, the shell executes the trap before exiting.

๐ŸŒธ Options

  • -f, --force โ€” Suppress warnings that would prevent the shell from exiting.

๐ŸŽ€ Operands

  • exit_status โ€” A non-negative integer that will be the exit status of the exiting shell.

If this operand is omitted, the exit status of the shell will be that of the last command executed before the exit built-in (but, if the built-in is executed during a trap, the exit status will be that of the last command before the trap is entered).

If the exit status is larger than 384, the shell may terminate itself with a signal instead of exiting with the specified exit status. See Termination of the shell for details.

โญ Exit status

Because the built-in causes the shell to exit, there is no exit status of the built-in.

As an exception, if the shell refused to exit, the exit status of the built-in is non-zero.

๐Ÿ’œ Notes

The exit built-in is a special built-in.

The POSIX standard defines no options for the exit built-in; the built-in accepts no options in the POSIXly-correct mode.

Exit status values greater than 255 are non-portable extensions. ๐Ÿซถ

If the built-in is executed during an EXIT trap, the shell just exits without executing the trap again. If exit_status was not specified, the exit status of the shell is what the exit status would be if the trap had not been set. See also Termination of the shell.


๐Ÿงš EXPORT BUILT-IN ๐Ÿงš

The export built-in marks variables for export to child processes. It's how you make sure your variables get handed down to the next generation, fr fr. ๐ŸŒธ

๐Ÿ“ Syntax

export [-prX] [name[=value]...]

๐Ÿฆ‹ Description

The export built-in is equivalent to the typeset built-in with the -gx option.

๐Ÿ’œ Notes

The export built-in is a special built-in.

The POSIX standard defines the -p option only; other options cannot be used in the POSIXly-correct mode. The POSIX does not allow using the option together with operands.


๐Ÿ’ซ FALSE BUILT-IN ๐Ÿ’ซ

The false built-in does nothing, but unsuccessfully. It's the icon of failing on purpose and I'm obsessed. ๐Ÿ˜ญ๐Ÿ’€

๐Ÿ“ Syntax

false

๐Ÿฆ‹ Description

The false built-in does nothing. Any command line arguments are ignored.

โญ Exit status

The exit status of the false built-in is non-zero.

๐Ÿ’œ Notes

The false built-in is a substitutive built-in.


๐ŸŽ€ FC BUILT-IN ๐ŸŽ€

The fc built-in re-executes or prints commands from command history. It's the "wait, what did I even type earlier" built-in, and honestly, relatable. ๐Ÿฆ‹

๐Ÿ“ Syntax

fc [-qr] [-e editor] [start [end]]
fc -s[q] [old=new] [start]
fc -l[nrv] [start [end]]

๐Ÿฆ‹ Description

When executed without the -l (--list) option, the built-in executes the commands in the command history range specified by the operands. If the -s (--silent) option is not specified, the shell invokes an editor which allows you to edit the commands before they are executed. The commands are executed when you quit the editor. If the -s (--silent) option is specified, the commands are immediately executed. In either case, the executed commands are printed to the standard output and added to the history.

When executed with the -l (--list) option, the built-in prints the commands in the command history range specified by the operands. By default, commands are printed with their history entry numbers, but output format can be changed using the -n (--no-numbers) and -v (--verbose) options.

๐ŸŒธ Options

  • -e editor, --editor=editor โ€” Specify an editor that is used to edit commands.

If this option is not specified, the value of the FCEDIT variable is used. If the variable is not set either, ed is used.

  • -l, --list โ€” Print command history entries.
  • -n, --no-numbers โ€” Don't print entry numbers when printing history entries.
  • -q, --quiet โ€” Don't print commands before executing.
  • -r, --reverse โ€” Reverse the order of command entries in the range.
  • -s, --silent โ€” Execute commands without editing them.
  • -v, --verbose โ€” Print execution time before each history entry when printing.

๐ŸŽ€ Operands

  • start and end โ€” The start and end operands specify a range of command history entries that are executed or printed.

If one of the operands is an integer, it is treated as a history entry number. A negative integer means the nth most recent entry where n is the absolute value of the integer.

If one of the operands is not an integer, it is treated as part of a command string: the most recent entry that starts with the string is selected as the start or end of the range.

If the first entry of the range that is specified by start is newer than the last entry of the range that is specified by end, the range is reversed as if the -r (--reverse) option was specified. (If the option is already specified, it is cancelled.)

The default values for start and end are:

  • with -l: start defaults to -16, end defaults to -1
  • without -l: start defaults to -1, end defaults to the same as start
  • old=new โ€” An operand of this format replaces part of the command string. If the command string contains old, it is replaced with new and the new string is executed. Only the first occurrence of old is replaced.

โญ Exit status

If commands was executed, the exit status of the fc built-in is that of the last executed command. Otherwise, the exit status is zero unless there is any error.

๐Ÿ’œ Notes

The fc built-in is a mandatory built-in.

The POSIX standard does not define the -q (--quiet) or -v (--verbose) options, so they cannot be used in the POSIXly-correct mode.

Command history cannot be modified during line-editing. Sorry bestie, that's just the rules. ๐Ÿ™ˆ


๐Ÿ‘‘ FG BUILT-IN ๐Ÿ‘‘

The fg built-in resumes a job in the foreground. It's the "come back here, you're not done" built-in. ๐Ÿ”ฅ

๐Ÿ“ Syntax

fg [job...]

๐Ÿฆ‹ Description

The fg built-in brings the specified job to the foreground and sends the SIGCONT signal to the job. As a result, the job is resumed in the foreground (if it has been suspended). The built-in then waits for the job to finish and returns the exit status of it.

The name of the job is printed when the job is resumed. When not in the POSIXly-correct mode, the job number is also printed.

The built-in can be used only when job control is enabled.

๐ŸŽ€ Operands

  • job โ€” The job ID of the job to be resumed.

If more than one job is specified, they are resumed in order, one at a time. The current job is resumed if none is specified.

The percent sign (%) at the beginning of a job ID can be omitted if the shell is not in the POSIXly-correct mode.

โญ Exit status

The exit status of the fg built-in is that of the (last) job resumed. The exit status is non-zero when there was some error.

๐Ÿ’œ Notes

The fg built-in is a mandatory built-in.

You cannot specify more than one job in the POSIXly-correct mode.

๐ŸŽ€ GETOPTS BUILT-IN ๐ŸŽ€

The getopts built-in parses command options. yes bestie, it's the option-parsing girlie of the shell ๐Ÿ’…

โœจ Syntax โœจ

  • getopts optionlist variable [argument...]

๐ŸŒธ Description ๐ŸŒธ

The getopts built-in parses single-character options that appear in arguments. Every time you invoke it, it parses one option and assigns the option character to variable. one at a time, we're being thorough here ๐Ÿ’–

The optionlist operand is a list of option characters that should be accepted by the parser. Inside optionlist, an option that takes an argument should be specified as the option character followed by a colon. So like, if you want -a, -b and -c to be parsed, and you want -b to take an argument, then optionlist should be ab:c. ๐Ÿซถ

When an option that takes an argument is parsed, the argument is assigned to the OPTARG variable.

When an option that is not specified in optionlist is found, or when an option argument is missing, the result depends on the first character of optionlist:

  • If optionlist starts with a colon, the option character is assigned to the OPTARG variable and variable is set to either ? (when the option is not in optionlist) or : (when the option argument is missing).
  • Otherwise, variable is set to ?, the OPTARG variable is unset, and an error message is printed.

The built-in parses one option for each execution. For all options in a set of command line arguments to be parsed, the built-in has to be executed repeatedly with the same arguments. The built-in uses the OPTIND variable to remember which argument should be parsed next. When the built-in is invoked for the first time, the variable value must be 1, which is the default value. You must not modify the variable until all the options have been parsed, at which point the built-in sets the variable to the index of the first operand in arguments. (If there are no operands, it will be set to the number of arguments plus one.) no cap ๐Ÿ”ฅ

When you want to start parsing a new set of arguments, you have to reset the OPTIND variable to 1 beforehand. reset the girlie, she's tired ๐Ÿ˜ญ


๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • optionlist โ€” a list of options that should be accepted as valid options in parsing.
  • variable โ€” the name of a variable the result is to be assigned to.
  • arguments โ€” command line arguments that are to be parsed.

When no arguments are given, the positional parameters are parsed.


๐Ÿ‘‘ Exit status ๐Ÿ‘‘

If an option is found, whether or not it is specified in optionlist, the exit status is zero. If there is no more option to be parsed, the exit status is one. The exit status is two on error.


โญ Example โญ

aopt=false bopt= copt=false
while getopts ab:c opt
do
  case $opt in
  a) aopt=true ;;
  b) bopt=$OPTARG ;;
  c) copt=true ;;
  \?) return 2 ;;
  esac
done
if $aopt;          then echo Option -a specified;       fi
if [ -n "$bopt" ]; then echo Option -b $bopt specified; fi
if $copt;          then echo Option -c specified;       fi
shift $((OPTIND - 1))
echo Operands are: $*

๐Ÿ’œ Notes ๐Ÿ’œ

In arguments that are parsed, options must precede operands. The built-in ends parsing when it encounters the first operand. options first, besties, it's giving very strict ๐Ÿ˜ค

The getopts built-in is a mandatory built-in.

The POSIX standard does not specify what will happen when the OPTIND variable is assigned a value other than 1. so like, don't do that ๐Ÿ™ˆ

In the POSIXly-correct mode, option characters in optionlist must be alphanumeric.


๐Ÿ’… HASH BUILT-IN ๐Ÿ’…

The hash built-in remembers, forgets, or reports command locations. it's the memory keeper of the shell โœจ

โœจ Syntax โœจ

  • hash command...
  • hash -r [command...]
  • hash [-a]
  • hash -d user...
  • hash -dr [user...]
  • hash -d

๐ŸŒˆ Description ๐ŸŒˆ

When executed with commands but without options, the built-in immediately performs command path search and caches the commands' full paths.

When executed with the -r (--remove) option, it removes the paths of commands (or all cached paths if none are specified) from the cache.

When executed without options or commands, it prints the currently cached paths to the standard output.

With the -d (--directory) option, the built-in does the same things to the home directory cache, rather than the command path cache. Cached home directory paths are used in tilde expansion. ๐Ÿ โœจ


๐ŸŽ€ Options ๐ŸŽ€

  • -a, --all โ€” print all cached paths. Without this option, paths for built-ins are not printed.
  • -d, --directory โ€” affect the home directory cache instead of the command path cache.
  • -r, --remove โ€” remove cached paths.

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • command โ€” the name of an external command (that does not contain any slash).
  • user โ€” a user name.

๐Ÿ‘‘ Exit status ๐Ÿ‘‘

The exit status of the hash built-in is zero unless there is any error.


๐Ÿ’œ Notes ๐Ÿ’œ

The shell automatically caches command and directory paths when executing a command or performing tilde expansion, so normally there is no need to use this built-in explicitly to cache paths. it does it for you, effortlessly, main character energy ๐Ÿ’…

Assigning a value to the PATH variable removes all command paths from the cache as if hash -r was executed.

The POSIX standard defines the -r option only: other options cannot be used in the POSIXly-correct mode.

The hash built-in is a mandatory built-in.


โญ HELP BUILT-IN โญ

The help built-in prints usage of built-ins. it's the built-in that explains the other built-ins, iconic behaviour honestly ๐Ÿ‘‘

โœจ Syntax โœจ

  • help [built-in...]

๐ŸŒธ Description ๐ŸŒธ

The help built-in prints a description of built-ins.


๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • built-ins โ€” names of built-ins.

๐Ÿ‘‘ Exit status ๐Ÿ‘‘

The exit status of the help built-in is zero unless there is any error.


๐Ÿ’œ Notes ๐Ÿ’œ

The help built-in is an elective built-in. It cannot be used in the POSIXly-correct mode because POSIX does not define its behavior.

Many built-ins of yash accept the --help option that prints the same description.


๐Ÿฆ‹ HISTORY BUILT-IN ๐Ÿฆ‹

The history built-in prints or edits command history. the receipts, bestie, the receipts ๐ŸŽ€

โœจ Syntax โœจ

  • history [-cF] [-d entry] [-s command] [-r file] [-w file] [count]

๐ŸŒˆ Description ๐ŸŒˆ

The history built-in prints or edits command history.

When executed with an option, the built-in edits history according to the option. If more than one option is specified, each option is processed in order. one at a time, in order, no skipping ๐Ÿ’…

When executed with the count operand, the built-in prints the most recent count history entries to the standard output in the same manner as the fc built-in.

When executed with neither options nor operands, the built-in prints the whole history. all of it, every last crumb ๐Ÿ“


๐ŸŽ€ Options ๐ŸŽ€

  • -c, --clear โ€” clear all history entries completely.
  • -d entry, --delete=entry โ€” delete the specified entry. The entry should be specified in the same manner as the start and end operands of the fc built-in.
  • -F, --flush-file โ€” rebuild the history file. This operation removes unused old data from the file.
  • -r file, --read=file โ€” read command lines from file and add them to the history. The file contents are treated as lines of simple text.
  • -s command, --set=command โ€” add command as a new history entry after removing the most recent entry.
  • -w file, --write=file โ€” write the whole history to file. Any existing data in the file will be lost. The output format is lines of simple text, each of which is a command string.
okay so -w really does overwrite everything in that file. existing data? gone. gone gone. be careful, bestie ๐Ÿ˜ญ

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • count โ€” the number of entries to be printed.

๐Ÿ‘‘ Exit status ๐Ÿ‘‘

The exit status of the history built-in is zero unless there is any error.


๐Ÿ’œ Notes ๐Ÿ’œ

The history built-in is an elective built-in. It cannot be used in the POSIXly-correct mode because POSIX does not define its behavior.

Command history cannot be modified during line-editing. not while you're typing, girlie, hands off ๐Ÿ™ˆ


๐ŸŽ€ JOBS BUILT-IN ๐ŸŽ€

The jobs built-in reports job status. the group chat update on everything running in the background ๐Ÿ’–

โœจ Syntax โœจ

  • jobs [-lnprs] [job...]

๐ŸŒˆ Description ๐ŸŒˆ

The jobs built-in prints information of jobs the shell is currently controlling.

By default, the following information is printed for each job, line by line:

  • the job number,
  • the + or - symbol if the job is the current or previous job, respectively,
  • the status, and
  • the command string.

๐ŸŽ€ Options ๐ŸŽ€

  • -l, --verbose โ€” print the process ID, status, and command string for each process in the jobs.
  • -n, --new โ€” print new jobs only: jobs whose status has never been reported since the status changed.
  • -p, --pgid-only โ€” print process group IDs of jobs only.
  • -r, --running-only โ€” print running jobs only.
  • -s, --stopped-only โ€” print stopped jobs only.

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • jobs โ€” the job IDs of jobs to be reported. When no job is specified, all jobs under the shell's control are reported.

The percent sign (%) at the beginning of a job ID can be omitted if the shell is not in the POSIXly-correct mode.


๐Ÿ‘‘ Exit status ๐Ÿ‘‘

The exit status of the jobs built-in is zero unless there is any error.


๐Ÿ’œ Notes ๐Ÿ’œ

The jobs built-in is a mandatory built-in.

The POSIX standard defines the -l and -p options only: other options cannot be used in the POSIXly-correct mode. In the POSIXly-correct mode, the effect of the -l option is different in that status is reported for each job rather than for each process.

The process group ID of a job executed by yash is equal to the process ID of the first command of the pipeline that forms the job. periodt ๐Ÿ’…


๐Ÿ’€ KILL BUILT-IN ๐Ÿ’€

The kill built-in sends a signal to processes. Yes bestie, it's giving menace, but it's also just how you tell a process to wrap it up. ๐Ÿ’…

โœจ Syntax โœจ

kill [-signal|-s signal|-n signal] process...
kill -l [-v] [signal...]

The kill built-in requires that all options precede operands. Any command line arguments after the first operand are all treated as operands. So no sneaking options in at the end, okay? ๐Ÿ™ˆ

๐ŸŒธ Description ๐ŸŒธ

When executed without the -l option, the built-in sends a signal to processes. The signal sent can be specified by option. The SIGTERM signal is sent if no signal is specified. (That's the "hey bestie, time to go" signal. ๐Ÿซถ)

When executed with the -l option, the built-in prints information of signals to the standard output. If no signal is specified, information of all signals is printed. It's a whole signal lore dump, iconic. โญ

๐Ÿ’– Options ๐Ÿ’–

Signal-specifying options

  • -signal, -s signal, -n signal โ€” a signal-specifying option specifies a signal to be sent to processes. signal can be specified by name or number. If number 0 is specified, the built-in checks if a signal could be sent to the processes but no signal is actually sent. Signal names are case-insensitive.
You can specify at most one signal-specifying option at a time. One. Not two. Periodt.

Other options

  • -l โ€” print signal information instead of sending a signal.
  • -v โ€” print more signal information. Without this option, the built-in prints the signal name only. This option adds the signal number and a short description. When the -v option is specified, the -l option can be omitted.

๐Ÿฆ‹ Operands ๐Ÿฆ‹

  • processes โ€” specify processes to which a signal is sent.

Processes can be specified by the process ID, the process group ID, or the job ID. The process group ID must be prefixed with a hyphen (-) so that it is not treated as a process ID.

When 0 is specified as process, the signal is sent to the process group to which the shell process belongs. When -1 is specified, the signal is sent to all processes on the system. ๐Ÿ˜ญ That's the nuclear option, fr fr.

  • signal โ€” specify a signal of which information is printed.

The signal can be specified by the name, the number, or the exit status of a command that was killed by the signal.

๐ŸŒˆ Exit status ๐ŸŒˆ

The exit status of the kill built-in is zero unless there is any error. If the signal was sent to at least one process, the exit status is zero even if the signal was not sent to all of the specified processes. Partial slay still counts as slay. ๐Ÿ‘‘

๐Ÿ’œ Notes ๐Ÿ’œ

The kill built-in is a mandatory built-in.

Command arguments that start with a hyphen should be used with care. The command kill -1 -2, for example, sends signal 1 to process group 2 since -1 is treated as a signal-specifying option and -2 as an operand that specifies a process group. The commands kill -- -1 -2 and kill -TERM -1 -2, on the other hand, treats both -1 and -2 as operands. The way that hyphen changes everything... ๐Ÿ’€

The POSIX standard does not define the -n or -v options, so they cannot be used in the POSIXly-correct mode. The standard does not allow specifying a signal number as the argument of the -s option or a signal name as the signal operand.

The standard requires signal names to be specified without the SIG prefix, like INT and QUIT. If the shell is not in the POSIXly-correct mode, the built-in accepts SIG-prefixed signal names as well.


๐ŸŽ€ LOCAL BUILT-IN ๐ŸŽ€

The local built-in prints or sets local variables. It's the "what happens in this function stays in this function" of variables. โœจ

โœจ Syntax โœจ

local [-rxX] [name[=value]...]

๐ŸŒธ Description ๐ŸŒธ

The local built-in is equivalent to the typeset built-in except that the -f (--functions) and -g (--global) options cannot be used. So like, it's typeset but it knows its limits. ๐Ÿ™ˆ

๐Ÿ’œ Notes ๐Ÿ’œ

The local built-in is an elective built-in. It cannot be used in the POSIXly-correct mode because POSIX does not define its behavior. No cap, POSIX just never wrote it down. ๐Ÿ’…


๐Ÿฆ‹ POPD BUILT-IN ๐Ÿฆ‹

The popd built-in pops a directory from the directory stack. Popping off, as one does. ๐Ÿ’…

โœจ Syntax โœจ

popd [index]

๐ŸŒธ Description ๐ŸŒธ

The popd built-in removes the last entry from the directory stack, returning to the previous working directory. If index is given, the entry specified by index is removed instead of the last one. Choose your own adventure. โญ

๐Ÿ’– Operands ๐Ÿ’–

  • index โ€” the index of a directory stack entry you want to remove.

If omitted, +0 (the last entry) is assumed.

๐ŸŒˆ Exit status ๐ŸŒˆ

In addition to the exit status of the cd built-in, the popd built-in returns the following exit status:

  • If the DIRSTACK variable is read-only or empty, the exit status is 4.
  • If index is out of range, the exit status is 4.

๐Ÿ’œ Notes ๐Ÿ’œ

It is an error to use this built-in when there is only one directory stack entry. You can't pop what isn't there, bestie. ๐Ÿ˜ญ

The popd built-in is an elective built-in. It cannot be used in the POSIXly-correct mode because POSIX does not define its behavior.

๐ŸŒธ PRINTF BUILT-IN ๐ŸŒธ

The printf built-in prints formatted values. It's the โœจfancyโœจ sibling of echo, bestie.


๐ŸŽ€ Syntax ๐ŸŽ€

printf format [value...]

๐Ÿ’– Description ๐Ÿ’–

The printf built-in formats values according to format and prints them to the standard output. Unlike the echo built-in, the printf built-in does not print a newline automatically โ€” no sneaky newline, you have to add that \n yourself, periodt. ๐Ÿ˜Œ

The formatting process is very similar to that of the printf function in the C programming language. You can use conversion specifications (which start with %) and escape sequences (which start with \) in format. Any other characters that are not part of a conversion specification or escape sequence are printed literally.

โœจ Conversion specifications โœจ

A conversion specification starts with a percent sign (%).

A conversion specification except %% consumes a value, which is formatted according to the specification and printed. By default, the values are consumed in the order of their appearance, but you can specify which value to consume by using the position specifier. If there are more values than conversion specifications, the entire format is re-processed until all the values are consumed. If a value to be consumed is missing, it is assumed to be an empty string (if the specification requires a string) or zero (if a number). If no values are given, format is processed just once.

Available conversion specifications are:

  • %d, %i โ€” prints a signed integer in decimal
  • %u โ€” prints an unsigned integer in decimal
  • %o โ€” prints an unsigned integer in octal
  • %x โ€” prints an unsigned integer in lowercase hexadecimal
  • %X โ€” prints an unsigned integer in uppercase hexadecimal
  • %f โ€” prints a floating-point number in lowercase
  • %F โ€” prints a floating-point number in uppercase
  • %e โ€” prints a floating-point number with exponent in lowercase
  • %E โ€” prints a floating-point number with exponent in uppercase
  • %g โ€” the same as %f or %e, automatically selected
  • %G โ€” the same as %F or %E, automatically selected
  • %c โ€” prints the first character of string
  • %s โ€” prints a string
  • %b โ€” prints a string (recognizing escape sequences like the echo built-in)
  • %% โ€” prints a percent sign (%)

For %g and %G, the specification that is actually used is %f or %F if the exponent part is between -5 and the precision (exclusive); %e or %E otherwise. Slay, it picks for you. ๐Ÿ’…

In a conversion specification except %%, the leading percent sign may be followed by position, flags, field width, and/or precision in this order.

A position specifier is a dollar sign ($) preceded by a positive decimal integer. The integer specifies the index of the value to be consumed. For example, in the format string %2$d %1$d, the second value is consumed first and the first value is consumed next.

More than one position specifier can specify the same value.

It is also possible that a value is not selected by any position specifier. If a position refers to a value, any preceding values that are not selected by any other position specifier are silently consumed.

The flags are a sequence of any number of the following characters:

  • Minus sign (-) โ€” With this flag, spaces are appended to the formatted value to fill up to the field width. Otherwise, spaces are prepended.
  • Plus sign (+) โ€” A plus or minus sign is always prepended to a number.
  • Space ( ) โ€” A space is prepended to a formatted number if it has no plus or minus sign.
  • Hash sign (#) โ€” The value is formatted in an alternative form: For %o, the printed octal integer has at least one leading zero. For %x and %X, a non-zero integer is formatted with 0x and 0X prefixes, respectively. For %e, %E, %f, %F, %g, and %G, a decimal mark (a.k.a. radix character) is always printed even if the value is an exact integer. For %g and %G, the printed number has at least one digit in the fractional part.
  • Zero (0) โ€” Zeros are prepended to a formatted number to fill up to the field width. This flag is ignored if the minus flag is specified or if the conversion specification is %d, %i, %u, %o, %x, or %X with a precision.

A field width is specified as a decimal integer that has no leading zeros.

A field width defines a minimum byte count of a formatted value. If the formatted value does not reach the minimum byte count, so many spaces are prepended that the printed value has the specified byte count.

A precision is specified as a period (.) followed by a decimal integer. If the integer is omitted after the period, the precision is assumed to be zero.

For conversion specifications %d, %i, %u, %o, %x, and %X, a precision defines a minimum digit count. If the formatted integer does not reach the minimum digit count, so many zeros are prepended that the printed integer has the specified number of digits. The default precision is one for these conversion specifications.

For conversion specifications %e, %E, %f, and %F, a precision defines the number of digits after the decimal mark. The default precision is six for these conversion specifications.

For conversion specifications %g, and %G, a precision defines a maximum number of significant digits in the printed value. The default precision is six for these conversion specifications.

For conversion specifications %s, and %b, a precision defines a maximum byte count of the printed string. The default precision is infinity for these conversion specifications.

In the conversion specification %08.3f, the zero flag is specified, the field width is 8, and the precision is 3. If this specification is applied to value 12.34, the output will be 0012.340. Look at her go. ๐Ÿ”ฅ

๐Ÿฆ‹ Escape sequences ๐Ÿฆ‹

The following escape sequences are recognized in format:

  • \a โ€” Bell character (ASCII code: 7)
  • \b โ€” Backspace (ASCII code: 8)
  • \f โ€” Form feed character (ASCII code: 12)
  • \n โ€” Newline character (ASCII code: 10)
  • \r โ€” Carriage return character (ASCII code: 13)
  • \t โ€” Horizontal tab character (ASCII code: 9)
  • \v โ€” Vertical tab character (ASCII code: 11)
  • \\ โ€” Backslash
  • \" โ€” Double quotation
  • \' โ€” Single quotation (apostrophe)
  • \xxx โ€” Character whose code is xxx, where xxx is an octal number of at most three digits.

๐Ÿ“ Operands ๐Ÿ“

  • format โ€” A string that defines how values should be formatted.
  • values โ€” Values that are formatted according to format.
    • A value is either a number or a string.
    • When a numeric value is required, value can be a single or double quotation followed by a character, instead of a normal number. For example, the command printf '%d' '"3' will print 51 on a typical environment where character 3 has character code 51.

๐Ÿ’œ Exit status ๐Ÿ’œ

The exit status of the printf built-in is zero unless there is any error.


๐Ÿ™ˆ Notes ๐Ÿ™ˆ

The POSIX standard does not precisely define how multibyte characters should be handled by the built-in. When you use the %s conversion specification with precision or the %c conversion specification, you may obtain unexpected results if the formatted value contains a character that is represented by more than one byte. Yash never prints only part of the bytes that represent a single multibyte character because all multibyte characters are converted to wide characters when processed in the shell. ๐Ÿงš

If the shell is not in the POSIXly-correct mode and the long double floating-point arithmetic is supported on the running system, then long double is used for floating-point conversion specifications. Otherwise, double is used.

POSIX leaves the behavior unspecified when a format contains conversion specifications with a position and others without a position. You should not rely on the selection order of values in such cases. Future versions of yash may treat such cases as errors. ๐Ÿ˜ญ

๐Ÿ‘‘ PUSHD BUILT-IN ๐Ÿ‘‘

The pushd built-in pushes a directory into the directory stack.


๐ŸŽ€ Syntax ๐ŸŽ€

pushd [-L|-P [-e]] [directory]

๐Ÿ’– Description ๐Ÿ’–

The pushd built-in changes the working directory to directory in the same manner as the cd built-in and adds it to the directory stack. If the working directory could not be changed successfully, the stack is not modified. No half-done chaos, bestie. โœจ


๐ŸŒˆ Options ๐ŸŒˆ

The pushd built-in accepts the following option as well as the options that can be used for the cd built-in:

  • --remove-duplicates โ€” If the new working directory has already been in the directory stack, the existing entry is removed from the stack before the new directory is pushed into the stack.

๐Ÿ“ Operands ๐Ÿ“

  • directory โ€” The pathname of the new working directory.
    • If directory is a single hyphen (โ€˜-โ€™), the value of the OLDPWD variable is assumed for the new directory pathname, which is printed to the standard output.
    • If directory is an integer with a plus or minus sign, it is considered as an entry index of the directory stack. The entry is removed from the stack and then pushed to the stack again.
    • If directory is omitted, the working directory is changed to the directory specified by the --default-directory=... option. If that option is not specified either, the default is index +1.

๐Ÿ’œ Exit status ๐Ÿ’œ

In addition to the exit status of the cd built-in, the pushd built-in returns the following exit status:

  • If the DIRSTACK variable is read-only, the exit status is 1.
  • If the PWD variable is unset, the exit status is 4.
  • If directory is a signed integer and the index is out of range, the exit status is 4.
  • If directory is omitted, the --default-directory=... option is not specified, and the directory stack is empty, the exit status is 4.

๐Ÿ™ˆ Notes ๐Ÿ™ˆ

The pushd built-in is an elective built-in. It cannot be used in the POSIXly-correct mode because POSIX does not define its behavior. Sorry POSIX girlies, this one's an extra. ๐Ÿ’…

๐Ÿ’… PWD BUILT-IN ๐Ÿ’…

The pwd built-in prints the current working directory. That's it, that's the whole vibe โ€” where am I, bestie? ๐Ÿ“โœจ

Syntax

pwd [-L|-P]

Description

The pwd built-in prints an absolute path to the shell's current working directory, straight to the standard output. Like, the full receipts. ๐Ÿงพ๐Ÿ’–

Options

  • -L, --logical โ€” if the value of the PWD variable is an absolute path to the shell's working directory and that path does not contain any . or .. components, then that path is what gets printed. Otherwise? You get the same path as when -P is specified. So it's giving "logical if it's cute, physical if it's not." ๐Ÿฆ‹
  • -P, --physical โ€” the printed path contains no . or .. components, no symbolic link components, and no redundant slashes. Clean. Pristine. Immaculate. โœจ
The -L (--logical) and -P (--physical) options are mutually exclusive: only the last one you specify is the one that counts. And if you specify neither? -L is assumed, no cap. ๐Ÿ’…

Exit status

The exit status of the pwd built-in is zero unless there's any error. Periodt. ๐Ÿซถ

Notes

The pwd built-in is a substitutive built-in.


๐ŸŽ€ READ BUILT-IN ๐ŸŽ€

The read built-in reads a line from the standard input. It's literally how you get the shell to listen to you. ๐Ÿ‘‚๐Ÿ’–

Syntax

read [-Aer] [-d delimiter] [-P|-p prompt] variable...

Description

The read built-in reads a line of string from the standard input and assigns it to the variables you name. Simple as that, bestie. ๐ŸŒธ

If the -r (--raw-mode) option is specified, all characters in the line are treated literally. No interpretation, no drama, just raw text. ๐Ÿ’€

If the -r (--raw-mode) option is not specified, backslashes in the line are treated as quotations. And if a backslash is hanging out at the very end of the line? That's a line continuation, babes. ๐Ÿ”— When the built-in reads the next line, the PS2 variable is used as a prompt if the shell is interactive and the standard input is a terminal.

By default, the input line is read up to a newline character. But if you specify the -d (--delimiter) option, it reads up to that delimiter character instead โ€” and heads up, the delimiter character must be a single character. One. Singular. ๐Ÿ™ˆ

The input line is subject to field splitting. The resulting words get assigned to your variables in order. If there are more words than variables, the last variable gets all the remaining words (as if the words were never split at all โ€” greedy queen behavior ๐Ÿ‘‘). If there are fewer words than variables, the leftover variables get assigned empty strings. Aw. ๐Ÿซถ

Options

  • -A, --array โ€” makes the last variable an array. So instead of cramming a concatenation of the remaining words into a normal variable, the words get assigned to an array. Iconic. โœจ
  • *-d *delimiter, --delimiter=delimiter โ€” read up to the specified delimiter character instead of a newline. ๐Ÿ›‘
  • -e, --line-editing โ€” use line-editing to read the line. Fancy! ๐Ÿ’…

To use line-editing, all of the following conditions must also be met:

  • The shell is interactive.
  • The vi or emacs option is enabled.
  • The standard input and standard error are connected to a terminal.
  • The delimiter is a newline.
  • -P, --ps1 โ€” print the PS1 variable as a prompt before reading the (first) line, if the shell is interactive and the standard input is a terminal. ๐ŸŽ€
  • *-p *prompt, --prompt=prompt โ€” print the specified prompt before reading the (first) line, if the shell is interactive and the standard input is a terminal.
  • -r, --raw-mode โ€” don't treat backslashes as quotations. Raw. Unfiltered. ๐Ÿ”ฅ

Operands

  • variables โ€” names of variables to which input words are assigned.

Exit status

Listen up, because these numbers matter: ๐Ÿ‘‡

  • The exit status is 0 if the read built-in successfully reads a line.
  • If the end of the input is reached, the exit status is 1. The variables are assigned the words read so far.
  • If a variable is read-only, the exit status is 2.
  • If the standard input is unreadable, the exit status is 3.
  • In case of a syntax error in the command line arguments, the exit status is 4.

Notes

The read built-in is a mandatory built-in.

The POSIX standard defines the -d and -r options only. Other options cannot be used in the POSIXly-correct mode. And only the exit status 0 and 1 are portable โ€” for other error conditions, the exit status may be different in other shells. So don't @ me if another shell does its own thing. ๐Ÿ˜ญ

The PS1R and PS1S variables affect the behavior of line-editing if the PS1 prompt is used. Same goes for PS2. ๐Ÿงš


๐Ÿ‘‘ READONLY BUILT-IN ๐Ÿ‘‘

The readonly built-in makes variables and functions read-only. Once you lock it, it's locked. No take-backs. ๐Ÿ”’๐Ÿ’–

Syntax

readonly [-pxX] [name[=value]...]
readonly -f[p] [name...]

Description

The readonly built-in is equivalent to the typeset built-in with the -gr option. Twin energy, fr fr. ๐Ÿซถ

Notes

The readonly built-in is a special built-in.

The POSIX standard defines the -p option only; other options cannot be used in the POSIXly-correct mode. And POSIX does not allow using the option together with operands. Nope, not even a little. ๐Ÿ™ˆ

In the POSIXly-correct mode, the following variables cannot be made readonly:

  • LINENO
  • OLDPWD
  • OPTARG
  • OPTIND
  • PWD
That's a hard no on those five, bestie. Try it in POSIXly-correct mode and the shell's gonna have opinions. ๐Ÿ’€

๐Ÿฆ‹ RETURN BUILT-IN ๐Ÿฆ‹

The return built-in returns from a function or script. It's the "okay I'm out, bye" of shell built-ins. โœŒ๏ธ๐Ÿ’œ

Syntax

return [-n] [exit_status]

Description

When executed without the -n (--no-return) option, one of the following happens:

  • If the shell is executing a function, the execution of the function is terminated. ๐Ÿšช
  • If the dot built-in is executing a script, the execution of the script is terminated.
  • If the shell is executing a script during initialization, the execution of the script is terminated.
  • If the shell is executing a trap, the execution of the trap is terminated for the currently handled signal. ๐Ÿชค
  • Otherwise, the shell exits โ€” unless it is interactive. (Interactive shells get to live, main character privilege. ๐Ÿ‘‘)

When executed with the -n (--no-return) option, the built-in does literally nothing except return the specified exit_status. Just vibes, no consequences. โœจ

Options

  • -n, --no-return โ€” do not terminate a function, script, trap, or the shell. Just pass through. ๐ŸŽ€

Operands

  • exit_status โ€” the exit status of the built-in.

The value must be a non-negative integer. ๐Ÿ”ข

If omitted, the exit status of the last executed command is used. (But when the shell is executing a trap, the exit status of the last command before the trap is used. Sneaky detail, iykyk. ๐Ÿ™ˆ)

Exit status

The exit status of the return built-in is defined by the exit_status operand. And that same exit status is used also as the exit status of the terminated function, script, or the shell. So it's giving double duty. ๐Ÿ’…

Notes

The return built-in is a special built-in.

Exit status values greater than 255 are non-portable extensions. So don't get too comfy with them.

In the POSIX standard, the behavior of the return built-in is defined only when the shell is executing a function or script. That's the whole scope, bestie. ๐ŸŒˆ

The POSIX standard defines no options for the return built-in; the built-in accepts no options in the POSIXly-correct mode. Zero. None. ๐Ÿšซ

๐ŸŽ€ SET BUILT-IN ๐ŸŽ€

okay so bestie, the set built-in is THE girl. she sets shell options AND positional parameters, and she's literally the main character of shell configuration. no cap. ๐Ÿ’…


Syntax โœจ

  • set [options] [operands]
  • set -o
  • set +o

heads up: set is a strict queen โ€” all options have to come before operands. any command line arguments after the first operand? all treated as operands. periodt.


Description ๐Ÿ’–

when you run it with zero command arguments, the built-in prints a list of all existing variables to the standard input, in a form you can reuse as commands to restore those variable definitions. (yes, standard input, that's what it says, don't @ me.)

when -o is the only command argument, it prints a list of shell options with their current settings. when +o is the only command argument, it prints commands you can reuse to restore the current shell option settings. so cute, so useful. ๐Ÿซถ

in all other cases, the built-in changes shell option settings and/or positional parameters. that's the vibe check.


Options ๐Ÿ‘‘

when you give one or more options, the built-in enables or disables shell options. a normal hyphen-prefixed option enables a shell option. an option prefixed with a plus (+) instead of a hyphen disables it. for example, options -m, -o monitor, and --monitor enable the monitor option, and options +m, +o monitor, ++monitor disable it. iconic behavior honestly.

the name of a long option is case-insensitive and may include irrelevant non-alphanumeric characters, which are ignored. for example, --le-comp-debug and --LeCompDebug are equivalent. and if no is prepended to the name of a long option, the meaning is reversed. for example, --noallexport is equivalent to ++allexport and ++nonotify to --notify. the way that works is so clever, i'm obsessed. ๐Ÿ˜ญ

an option can be specified in one of these forms:

  • a long option e.g. --allexport
  • an -o option with a option name specified as the argument e.g. -o allexport
  • a single-character option e.g. -a

not all options can be specified as single-character options. sad but true.

the available options are:

  • all-export (-a) โ€” when enabled, all variables are automatically exported when assigned.
  • brace-expand โ€” this option enables brace expansion.
  • case-glob โ€” (enabled by default) when enabled, pattern matching is case-sensitive in pathname expansion.
  • clobber (+C) โ€” (enabled by default) when enabled, the > redirection behaves the same as the >| redirection.
  • cur-async, cur-bg, cur-stop โ€” (enabled by default) these options affect choice of the current job (cf. job ID).
  • dot-glob โ€” when enabled, periods at the beginning of filenames are not treated specially in pathname expansion.
  • emacs โ€” this option enables line-editing in the emacs mode.
  • empty-last-field โ€” when enabled, field splitting does not remove the last field even if it is empty.
  • err-exit (-e) โ€” when enabled, if a pipeline ends with a non-zero exit status, the shell immediately exits unless the following suppress condition is met:
    • the pipeline is a condition of an if command or while or until loop;
    • the pipeline is prefixed by !; or
    • the pipeline is a single compound command other than a subshell grouping.
  • err-return โ€” this option is like the err-exit option, but the return built-in is executed instead of the shell exiting on a non-zero exit status. unlike err-exit, the suppress condition does not apply inside a function, subshell grouping, or script file.
  • exec (+n) โ€” (enabled by default) commands are actually executed only when this option is enabled. otherwise, commands are just parsed and not executed. disabling this option may be useful for syntax checking. in an interactive shell, this option is always assumed enabled.
  • extended-glob โ€” this option enables extension in pathname expansion.
  • for-local โ€” (enabled by default) if a for loop is executed within a function, this option causes the iteration variable to be created as a local variable, even if the variable already exists globally. this option has no effect if the POSIXly-correct mode is active.
  • glob (+f) โ€” (enabled by default) this option enables pathname expansion.
  • hash-on-def (-h) โ€” when a function is defined when this option is enabled, the shell immediately performs command path search for each command that appears in the function and caches the command's full path.
  • hist-space โ€” when enabled, command lines that start with a whitespace are not saved in command history.
  • ignore-eof โ€” when enabled, an interactive shell does not exit when EOF (end of file) is input. this prevents the shell from exiting when you accidentally hit Ctrl-D. a lifesaver fr fr ๐Ÿ’€
  • le-always-rp, le-comp-debug, le-conv-meta, le-no-conv-meta, le-predict, le-predict-empty, le-prompt-sp, le-visible-bell, le-trim-right โ€” see shell options on line-editing.
  • mark-dirs โ€” when enabled, resulting directory names are suffixed by a slash in pathname expansion.
  • monitor (-m) โ€” this option enables job control. this option is enabled by default for an interactive shell.
  • notify (-b) โ€” when the status of a job changes when this option is enabled, the shell immediately notifies at any time. this option overrides the notify-le option.
  • notify-le โ€” this option is similar to the notify option, but the status change is notified only while the shell is waiting for input with line-editing.
  • null-glob โ€” when enabled, in pathname expansion, patterns that do not match any pathname are removed from the command line rather than left as is.
  • pipe-fail โ€” when enabled, the exit status of a pipeline is zero if and only if all the subcommands of the pipeline exit with an exit status of zero.
  • posixly-correct โ€” this option enables the POSIXly-correct mode.
  • trace-all โ€” (enabled by default) when this option is disabled, the x-trace option is temporarily disabled while the shell is executing commands defined in the COMMAND_NOT_FOUND_HANDLER, POST_PROMPT_COMMAND, PROMPT_COMMAND, or YASH_AFTER_CD variable.
  • unset (+u) โ€” (enabled by default) when enabled, an undefined parameter is expanded to an empty string in parameter expansion and treated as zero in arithmetic expansion. when disabled, expansion of an undefined parameter results in an error. โš ๏ธ
  • verbose (-v) โ€” when enabled, the shell prints each command line to the standard error before parsing and executing it.
  • vi โ€” this option enables line-editing in the vi mode. this option is enabled by default in an interactive shell if the standard input and error are both terminals.
  • x-trace (-x) โ€” when enabled, the results of expansion are printed to the standard error for each simple command being executed. when printed, each line is prepended with an expansion result of the PS4 variable. see also the trace-all option.

Operands ๐ŸŒธ

if one or more operands are passed to the set built-in, current positional parameters are all removed and the operands are set as new positional parameters. if the -- separator (cf. syntax of command arguments) is passed, the positional parameters are set even when there are no operands, in which case new positional parameters will be nothing. literally nothing. empty. ghosted. ๐Ÿ™ˆ


Exit status โญ

the exit status of the set built-in is zero unless there is any error.


Notes ๐Ÿ’œ

the set built-in is a special built-in.

in the POSIX standard, available shell options are much limited. the standard does not define:

  • long options such as --allexport,
  • prepending no to negate an option,
  • using uppercase letters and/or non-alphanumeric characters in option names

the options defined in the standard are:

  • -a, -o allexport
  • -e, -o errexit
  • -m, -o monitor
  • -C, -o noclobber
  • -n, -o noexec
  • -f, -o noglob
  • -b, -o notify
  • -u, -o nounset
  • -v, -o verbose
  • -x, -o xtrace
  • -h
  • -o ignoreeof
  • -o nolog
  • -o pipefail
  • -o vi

yash does not support the nolog option. this option prevented function definitions from being added to command history in old versions of Korn shell, but is no longer supported by any popular shell. rip nolog, you would've hated it here. ๐Ÿ’€

the behavior of the hash-on-def option in yash is based on POSIX.1-2001. many existing shells implement this option with different behavior, and POSIX.1-2024 no longer imposes any behavior on this option.


๐Ÿฆ‹ SHIFT BUILT-IN ๐Ÿฆ‹

the shift built-in removes some positional parameters or array values. she's the "okay we're done with you, next" of built-ins. ๐Ÿ’…


Syntax โœจ

  • shift [-A array] [count]

Description ๐Ÿ’–

the shift built-in removes the first count positional parameters or array values, where count is specified by the operand.


Options ๐Ÿ‘‘

  • -A array, --array=array โ€” remove first count values of array instead of positional parameters.

Operands ๐ŸŒธ

  • count โ€” the number of positional parameters or array values to be removed.

it is an error if the actual number of positional parameters or array values is less than count. if omitted, the default value is one. if negative, the last -count positional parameters or array values are removed instead of the first ones. yes, negative count is a thing, she's flexible like that. โš ๏ธ


Exit status โญ

the exit status of the shift built-in is zero unless there is any error.


Notes ๐Ÿ’œ

the shift built-in is a special built-in.

the number of positional parameters can be obtained with the # special parameter. the number of array values can be obtained with ${array[#]}.

the POSIX standard defines no options for the shift built-in; the built-in accepts no options in the POSIXly-correct mode.

negative operands are not allowed in the POSIXly-correct mode. ๐Ÿšซ

๐Ÿ’ค SUSPEND BUILT-IN ๐Ÿ’ค

okay bestie, this one is literally just "girl, go to sleep" for your shell. The suspend built-in suspends the shell. That's it. That's the whole vibe. ๐Ÿ˜ด


โœจ Syntax โœจ

suspend [-f]

๐Ÿฆ‹ Description ๐Ÿฆ‹

So here's the tea: the suspend built-in sends a SIGSTOP signal to all processes in the process group that the shell process belongs to. That signal suspends the processes โ€” yes, including the shell itself. They all just freeze mid-sentence like a screenshot. ๐ŸงŠ

The suspended processes resume when they receive a SIGCONT signal. (So something else has to wake them up. Not you. Not the shell. Something else.) ๐Ÿซถ

Now here's the spicy part. If the shell is interactive and its process group ID is equal to the process ID of the session leader, the shell prints a warning message and refuses to send a signal โ€” unless you pass the -f (--force) option. Why? Because in that situation there is no other job-controlling shell that can send a SIGCONT signal to resume the suspended shell, so the shell could never ever be resumed. That's not a nap, that's a coma. ๐Ÿ’€


๐Ÿ’… Options ๐Ÿ’…

  • -f, --force โ€” suppress warnings that would prevent the shell from sending a signal. (Yes queen, force it. But you were warned.) ๐Ÿ”ฅ

โญ Exit status โญ

The exit status is zero if the signal was successfully sent and non-zero otherwise.


๐ŸŽ€ Notes ๐ŸŽ€

  • The suspend built-in is an elective built-in.
  • It cannot be used in the POSIXly-correct mode because POSIX does not define its behavior. (POSIX said "no thoughts, head empty" about this one.) ๐Ÿ™ˆ

๐Ÿงช TEST BUILT-IN ๐Ÿงช

The test built-in evaluates an expression. She's the queen of "is this true tho?" and honestly she has so many little tricks. ๐Ÿ‘‘


โœจ Syntax โœจ

test expression
[ expression ]

The test built-in does not distinguish options and operands โ€” every single command line argument is interpreted as expression. And if the built-in is executed with the name [, then expression must be followed by ]. Yes, that closing bracket is mandatory. She's a package deal. ๐Ÿ’…


๐ŸŒธ Description ๐ŸŒธ

The test built-in evaluates expression as a conditional expression made up of the operators and operands described below. The exit status is 0 if the condition is true and 1 otherwise.

The unary operators below test a file. If the operand file is a symbolic link, the file referred to by the link is tested โ€” except for the -h and -L operators, which test the link itself. ๐Ÿฆ‹

  • -b file โ€” file is a block special file
  • -c file โ€” file is a character special file
  • -d file โ€” file is a directory
  • -e file โ€” file exists
  • -f file โ€” file is a regular file
  • -G file โ€” file's group ID is same as the shell's effective group ID
  • -g file โ€” file's set-group-ID flag is set
  • -h file โ€” same as -L
  • -k file โ€” file's sticky bit is set
  • -L file โ€” file is a symbolic link
  • -N file โ€” file has not been accessed since last modified
  • -O file โ€” file's user ID is same as the shell's effective user ID
  • -p file โ€” file is a FIFO (named pipe)
  • -r file โ€” file is readable
  • -S file โ€” file is a socket
  • -s file โ€” file is not empty
  • -u file โ€” file's set-user-ID flag is set
  • -w file โ€” file is writable
  • -x file โ€” file is executable

The unary operator below tests a file descriptor:

  • -t fd โ€” fd is associated with a terminal

The unary operators below test a string:

  • -n string โ€” string is not empty
  • -z string โ€” string is empty

The unary operator below tests a shell option:

  • -o ?option โ€” option is a valid shell option name
  • -o option โ€” option is a valid shell option name that is enabled

The binary operators below compare files. Non-existing files are considered older than any existing files. (Ghost files are ancient, apparently. ๐Ÿ‘ป)

  • file1 -nt file2 โ€” file1 is newer than file2
  • file1 -ot file2 โ€” file1 is older than file2
  • file1 -ef file2 โ€” file1 is a hard link to file2

The binary operators below compare strings:

  • string1 = string2, string1 == string2 โ€” string1 is the same string as string2
  • string1 != string2 โ€” string1 is not the same string as string2

The binary operators below compare strings according to the alphabetic order in the current locale:

  • string1 === string2 โ€” string1 is equal to string2
  • string1 !== string2 โ€” string1 is not equal to string2
  • string1 < string2 โ€” string1 is less than string2
  • string1 <= string2 โ€” string1 is less than or equal to string2
  • string1 > string2 โ€” string1 is greater than string2
  • string1 >= string2 โ€” string1 is greater than or equal to string2

The binary operator below performs pattern matching:

  • string =~ pattern โ€” extended regular expression pattern matches (part of) string

The binary operators below compare integers:

  • v1 -eq v2 โ€” v1 is equal to v2
  • v1 -ne v2 โ€” v1 is not equal to v2
  • v1 -gt v2 โ€” v1 is greater than v2
  • v1 -ge v2 โ€” v1 is greater than or equal to v2
  • v1 -lt v2 โ€” v1 is less than v2
  • v1 -le v2 โ€” v1 is less than or equal to v2

The binary operators below compare version numbers:

  • v1 -veq v2 โ€” v1 is equal to v2
  • v1 -vne v2 โ€” v1 is not equal to v2
  • v1 -vgt v2 โ€” v1 is greater than v2
  • v1 -vge v2 โ€” v1 is greater than or equal to v2
  • v1 -vlt v2 โ€” v1 is less than v2
  • v1 -vle v2 โ€” v1 is less than or equal to v2

If the expression is a single word without operators, the -n operator is assumed. An empty expression evaluates to false. (Rude, but okay. ๐Ÿ˜ญ)


๐Ÿงš Compound expressions ๐Ÿงš

The operators below can be used to make complex expressions:

  • ! expression โ€” negate (reverse) the result
  • ( expression ) โ€” change operator precedence
  • expression1 -a expression2 โ€” logical conjunction (and)
  • expression1 -o expression2 โ€” logical disjunction (or)
โš ๏ธ However, parentheses and the operators -a and -o are deprecated. It is strongly recommended to use the shell language features instead. Not me being dramatic โ€” this is literally what the page says. ๐Ÿ’€
# Avoid:
[ 1 -eq 1 -a -t = 1 -o ! foo ]

# Prefer:
[ 1 -eq 1 ] && [ -t = 1 ] || ! [ foo ]

๐Ÿ‘‘ Comparison of version numbers ๐Ÿ‘‘

Comparison of version numbers is similar to comparison of strings in alphabetic order. The differences are:

  • Adjacent digits are treated as an integer. Integers are compared in mathematical order rather than alphabetic order.
  • Digits are considered larger than any non-digit characters.

For example, version numbers 0.1.2-3 and 00.001.02-3 are equal and 0.2.1 is smaller than 0.10.0. (The way that 0.2.1 < 0.10.0 trips people up every single time โ€” bestie, it's a version number, not a decimal. ๐Ÿ”ฅ)


โญ Exit status โญ

The exit status of the test built-in is 0 if expression is true and 1 otherwise. The exit status is 2 if expression cannot be evaluated because of a syntax error or any other reasons.


๐ŸŽ€ Notes ๐ŸŽ€

  • The POSIX standard provides that the exit status should be larger than 1 on error.
  • The POSIX standard does not define the following operators: -G, -k, -N, -O, -nt, -ot, -ef, ==, ===, !==, <, <=, >, >=, =~, -veq, -vne, -vgt, -vge, -vlt, -vle, -a, -o, and parentheses.
  • In the POSIXly-correct mode, binary operators -a and -o and parentheses are rejected. No exceptions, no negotiation. ๐Ÿ™…

โฑ๏ธ TIMES BUILT-IN โฑ๏ธ

The times built-in prints CPU time usage. She's the little accountant of your shell and she is obsessed with receipts. ๐Ÿงพ


โœจ Syntax โœจ

times

๐Ÿฆ‹ Description ๐Ÿฆ‹

The times built-in prints the CPU times consumed by the shell process and its child processes to the standard output.

The built-in prints two lines: the first line shows the CPU time of the shell process, and the second one that of its child processes โ€” not including those which have not terminated. Each line shows the CPU times consumed in the user and system mode. ๐Ÿ“Š


โญ Exit status โญ

The exit status of the times built-in is zero unless there is any error.


๐ŸŽ€ Notes ๐ŸŽ€

  • The times built-in is a special built-in. ๐Ÿ’…

๐Ÿ’… TRAP BUILT-IN ๐Ÿ’…

okay bestie, the trap built-in sets or prints signal handlers. yes, you're basically the bouncer for signals now. ๐Ÿ‘‘

Syntax

  • trap
  • trap action signal...
  • trap signal_number [signal...]
  • trap -p [signal...]

Description

The trap built-in sets or prints actions that are taken when the shell receives signals. (Those actions are called traps, and honestly, iconic name.)

When executed with action and one or more signals, the built-in sets the traps for signals to action. If the shell receives one of the signals, the action will be taken.

If the first operand is signal_number instead of action, the built-in resets the traps for signal_number and signals as if action was -. lowkey a "never mind, undo" move. ๐Ÿ™ˆ

When executed with the -p (--print) option or with no operands, the built-in prints currently set traps to the standard output in a format that can be executed as commands that restore the current traps. If one or more signals are specified, only those signals are printed. If no signal is specified but the -p option is given, all signals are printed. If no signal is specified and the -p option is not given, only signals with non-default actions are printed. (In some situations, however, the built-in may print previous trap settings instead of the current. See notes below. ๐Ÿ‘€)

Options

  • -p, --print โ€” Print current trap settings.

Operands

  • action โ€” An action that will be taken when signal is received.

If action is a single hyphen (-), the action is reset to the default action that is defined by the operating system. If action is an empty string, the signal is ignored on receipt. Otherwise, action is treated as a command string: the string is parsed and executed as commands when the signal is received. (If a signal is received while a command is being executed, the action is taken just after the command finishes.)

  • signal โ€” The number or name of a signal.

If signal is number 0 or name EXIT, it is treated as a special imaginary signal that is always received when the shell exits. The action set for this signal is taken when the shell exits normally. fr fr, EXIT is that girl. ๐Ÿ’–

  • signal_number โ€” This is like signal, but must be a number.

Exit status

The exit status of the trap built-in is zero unless there is any error.

Notes

The trap built-in is a special built-in.

The POSIX standard requires that signal names must be specified in uppercase without the SIG-prefix, like INT and QUIT. As an extension, yash accepts SIG-prefixed names like SIGINT and SIGQUIT and treats signal names case-insensitively. so go off, sigint, we see you. โœจ

Reusing output of the built-in

Output of the trap built-in can be saved in a variable, which can be later executed by the eval built-in to restore the traps.

saved_traps=$(trap)
trap '...' INT
eval "$saved_traps"

There are some tricks behind the scenes to allow this idiom. You use a command substitution to save the output of the trap built-in in the variable. The command substitution is executed in a subshell. The subshell resets all traps (except ignored ones) at the beginning of itself. This seemingly would result in (almost) empty output from the built-in that would fail to restore the traps as expected. ๐Ÿ˜ญ a whole setup for nothing??

To avoid that pitfall, POSIX requires the shell to follow one of the two options below:

  • If a command substitution just contains a single trap built-in, traps should not be reset when the subshell is started to execute the built-in; or
  • A subshell always resets the traps but remembers the previous traps. If the trap built-in is executed in the subshell but no other trap built-in has been executed to modify traps in the subshell, then the built-in should print the remembered traps.

Yash obeys the second. ๐Ÿซถ


โœจ TRUE BUILT-IN โœจ

The true built-in does nothing successfully. genuinely the most unbothered icon in the whole shell. ๐Ÿ’…

Syntax

  • true

Description

The true built-in does nothing. Any command line arguments are ignored. not even a little side-eye, they're just gone. ๐Ÿ’€

Exit status

The exit status of the true built-in is zero.

Notes

The true built-in is a substitutive built-in.

The true and colon built-ins have the same effect, but true is a substitutive built-in while colon is a special. twin vibes, different tax brackets. ๐ŸŽ€


๐ŸŒธ TYPE BUILT-IN ๐ŸŒธ

The type built-in identifies a command. it's giving detective energy. ๐Ÿ•ต๏ธโ™€๏ธ

Syntax

  • type [-abefkp] [command...]

Description

The type built-in is equivalent to the command built-in with the -V option.

Notes

The POSIX standard does not define the relation between the type and command built-ins. The standard does not define options for the type built-in.

At least one command operand must be specified in the POSIXly-correct mode.

The type built-in is a mandatory built-in.


๐Ÿฆ‹ TYPESET BUILT-IN ๐Ÿฆ‹

The typeset built-in prints or sets variables or functions. yes, it does both. main character energy, no cap. ๐Ÿ‘‘

Syntax

  • typeset [-gprxX] [variable[=value]...]
  • typeset -f[pr] [function...]

Description

If executed without the -f (--functions) option, the typeset built-in prints or sets variables to the standard output. Otherwise, it prints or sets functions.

If executed with the -p (--print) option, the built-in prints the variables or functions specified by operands. Without the option, it sets variables or functions. If no operands are specified, it prints all existing variables or functions, regardless of whether the -p (--print) option is specified.

Options

  • -f, --functions โ€” Print or set functions rather than variables.
  • -g, --global โ€” When setting a new variable, the variable will be a global variable if this option is specified. Without this option, the variable would be a local variable.

When printing variables, all existing variables including global variables are printed if this option is specified. Without this option, only local variables are printed.

  • -p, --print โ€” Print variables or functions in a form that can be parsed and executed as commands that will restore the currently set variables or functions.
  • -r, --readonly โ€” When setting variables or functions, make them read-only.

When printing variables or functions, print read-only variables or functions only.

  • -x, --export โ€” When setting variables, mark them for export, so that they will be exported to external commands.

When printing variables, print exported variables only.

  • -X, --unexport โ€” When setting variables, cancel exportation of the variables.

Operands

  • variable (without value) โ€” The name of a variable that is to be set or printed.

Without the -p (--print) option, the variable is defined (if not yet defined) but its value is not set nor changed. Variables that are defined without values are treated as unset in parameter expansion. so they exist but also... don't. liminal queen. ๐ŸŒซ๏ธ

  • variable=value โ€” The name of a variable and its new value.

The value is assigned to the variable (regardless of the -p (--print) option).

  • function โ€” The name of an existing function that is to be set or printed.

Exit status

The exit status of the typeset built-in is zero unless there is any error.

Notes

A global variable cannot be newly defined if a local variable has already been defined with the same name. The local variable will be set regardless of the -g (--global) option. the local one ate, sorry. ๐Ÿ“

The typeset built-in is an elective built-in. It cannot be used in the POSIXly-correct mode because POSIX does not define its behavior.

The export and readonly built-ins are equivalent to the typeset built-in with the -gx and -gr options, respectively. The local built-in is equivalent to the typeset built-in except that the -f (--functions) and -g (--global) options cannot be used.


๐Ÿ”ฅ ULIMIT BUILT-IN ๐Ÿ”ฅ

The ulimit built-in sets or prints a resource limit. it's the "know your limits bestie" of built-ins, literally. ๐Ÿซถ

Syntax

  • ulimit -a [-H|-S]
  • ulimit [-H|-S] [-efilnqrstuvx] [limit]

Description

The ulimit built-in sets or prints a resource limit.

If executed with the -a (--all) option, the built-in prints the current limits for all resource types. Otherwise, it sets or prints the limit of a single resource type. The resource type can be specified by the options listed below. The resource limits will affect the current shell process and all commands invoked from the shell.

Each resource type has two limit values: the hard and soft limit. You can change a soft limit freely as long as it does not exceed the hard limit. You can decrease a hard limit but cannot increase it without a proper permission. ๐Ÿšจ no sneaky raising, that's the whole point.

When the -H (--hard) or -S (--soft) option is specified, the built-in sets or prints the hard or soft limit, respectively. If neither of the options is specified, the built-in sets both the hard and soft limit or prints the soft limit.

Options

  • -H, --hard โ€” Set or print a hard limit.
  • -S, --soft โ€” Set or print a soft limit.
  • -a, --all โ€” Print all current limit settings.

The following options specify the type of resources. If none of them is specified, -f is the default. The types of resources that can be set depend on the operating system.

  • -c, --core โ€” Maximum size of core files created (in 512-byte blocks)
  • -d, --data โ€” Maximum size of a process's data segment (in kilobytes)
  • -e, --nice โ€” Maximum scheduling priority ('nice')
  • -f, --fsize โ€” Maximum size of files created by a process (in 512-byte blocks)
  • -i, --sigpending โ€” Maximum number of pending signals
  • -l, --memlock โ€” Maximum memory size that can be locked into RAM (in kilobytes)
  • -m, --rss โ€” Maximum size of a process's resident set (in kilobytes)
  • -n, --nofile โ€” Maximum file descriptor + 1
  • -q, --msgqueue โ€” Maximum size of POSIX message queues
  • -r, --rtprio โ€” Maximum real-time scheduling priority
  • -s, --stack โ€” Maximum size of a process's stack (in kilobytes)
  • -t, --cpu โ€” Maximum CPU time that can be used by a process (in seconds)
  • -u, --nproc โ€” Maximum number of processes for a user
  • -v, --as โ€” Maximum size of memory used by a process (in kilobytes)
  • -x, --locks โ€” Maximum number of file locks

Operands

  • limit โ€” A limit to be set.

The value must be a non-negative integer or one of hard, soft, and unlimited. If value is hard or soft, the new limit is set to the current hard or soft limit. If limit is not specified, the current limit is printed.

Exit status

The exit status of the ulimit built-in is zero unless there is any error.

Notes

The POSIX standard defines no options other than -f. It neither defines hard, soft, or unlimited for limit values. so those are the extra-special yash perks. โญ

The ulimit built-in is a mandatory built-in.

๐Ÿ’… UMASK BUILT-IN ๐Ÿ’…

The umask built-in sets or prints the file mode creation mask. It's lowkey the permission bouncer for all your new files, bestie. ๐Ÿšชโœจ

๐ŸŒธ Syntax ๐ŸŒธ

umask mask
umask [-S]

๐Ÿฆ‹ Description ๐Ÿฆ‹

Okay so if you run it with no mask operand, the built-in just prints the shell's current file mode creation mask to standard output, in a form you can later feed back in as mask to restore the current mask. Iconic little round-trip moment. ๐Ÿ’–

Otherwise, the built-in sets the file mode creation mask to mask. Periodt.

โœจ Options โœจ

  • -S, --symbolic โ€” Print in the symbolic form instead of the octal integer form. Cute and readable fr fr.

๐ŸŽ€ Operands ๐ŸŽ€

  • mask โ€” The new file mode creation mask, either in the symbolic or octal integer form.

๐Ÿ”ข Octal integer form ๐Ÿ”ข

In the octal integer form, the mask is specified as a non-negative octal integer that is the sum of the following permissions:

  • 0400 โ€” read by owner
  • 0200 โ€” write by owner
  • 0100 โ€” execute/search by owner
  • 0040 โ€” read by group
  • 0020 โ€” write by group
  • 0010 โ€” execute/search by group
  • 0004 โ€” read by others
  • 0002 โ€” write by others
  • 0001 โ€” execute/search by others

๐Ÿงš Symbolic form ๐Ÿงš

In the symbolic form, the mask is specified as a symbolic expression that denotes permissions that are not included in the mask. (Yes, it's the opposite of what you'd expect. The way that permissions work is so unhinged but we move. ๐Ÿ˜ญ)

The entire expression is one or more clauses separated by comma. A clause is a sequence of whos followed by one or more actions.

A who is one of:

  • u โ€” owner
  • g โ€” group
  • o โ€” others
  • a โ€” all of owner, group, and others

An empty sequence of whos is equivalent to who a.

An action is an operator followed by permission. An operator is one of:

  • = โ€” set who's permission to permission
  • + โ€” add permission to who's permission
  • - โ€” remove permission from who's permission

and permission is one of:

  • r โ€” read
  • w โ€” write
  • x โ€” execute/search
  • X โ€” execute/search (only if some user already has execute/search permission)
  • s โ€” set-user-ID and set-group-ID
  • u โ€” userโ€™s current permissions
  • g โ€” groupโ€™s current permissions
  • o โ€” others' current permissions

but more than one of r, w, x, X, and s can be specified after a single operand. Stack 'em. ๐Ÿ”ฅ

For example, the command umask u=rwx,go+r-w

  • unmasks the userโ€™s read, write, and execute/search permissions;
  • unmasks the groupโ€™s and others' read permission; and
  • masks the groupโ€™s and others' write permission.

๐Ÿ’€ Exit status ๐Ÿ’€

The exit status of the umask built-in is zero unless there is any error.

๐Ÿ‘‘ Notes ๐Ÿ‘‘

The umask built-in is a mandatory built-in.

The POSIX standard does not require the default output format (used when the -S option is not specified) to be the octal integer form. So don't come for yash about it. ๐Ÿ™ˆ

๐ŸŒˆ UNALIAS BUILT-IN ๐ŸŒˆ

The unalias built-in undefines aliases. It's giving "we don't know her anymore." ๐Ÿ’…

๐ŸŒธ Syntax ๐ŸŒธ

unalias name...
unalias -a

๐Ÿฆ‹ Description ๐Ÿฆ‹

The unalias built-in removes the definition of the aliases specified by operands. Bye bestie. ๐Ÿซถ

โœจ Options โœจ

  • -a, --all โ€” Undefine all aliases. Clean slate, main character energy.

๐ŸŽ€ Operands ๐ŸŽ€

  • name โ€” The name of an alias to be undefined.

๐Ÿ’€ Exit status ๐Ÿ’€

The exit status of the unalias built-in is zero unless there is any error. It is an error to specify the name of a non-existing alias as name. (Like, you can't un-invite someone who was never invited, babe. ๐Ÿ˜ญ)

๐Ÿ‘‘ Notes ๐Ÿ‘‘

The unalias built-in is a mandatory built-in.


๐ŸŒธ UNSET BUILT-IN ๐ŸŒธ

The unset built-in undefines variables or functions. Poof. Gone. โœจ

๐ŸŒธ Syntax ๐ŸŒธ

unset [-fv] [name...]

๐Ÿฆ‹ Description ๐Ÿฆ‹

The unset built-in removes the definition of the variables or functions specified by operands.

It is not an error if any of the specified variables or functions do not exist; they are silently ignored. No drama, no notes. ๐Ÿ’–

โœจ Options โœจ

  • -f, --functions โ€” Undefine functions.
  • -v, --variables โ€” Undefine variables.

These options are mutually exclusive: only the last specified one is effective. If neither is specified, -v is assumed. So the last one to speak wins โ€” very group-chat of it. ๐Ÿ˜Œ

๐ŸŽ€ Operands ๐ŸŽ€

  • name โ€” The name of a variable or function to be undefined.

๐Ÿ’€ Exit status ๐Ÿ’€

The exit status of the unset built-in is zero unless there is any error.

๐Ÿ‘‘ Notes ๐Ÿ‘‘

The unset built-in is a special built-in.

Although yash does not do so, the POSIX standard allows removing a function if neither of the -f and -v options is specified and the specified variable does not exist. ๐Ÿ‘€

At least one name operand must be specified in the POSIXly-correct mode.


โญ WAIT BUILT-IN โญ

The wait built-in waits for jobs to terminate. The "let them cook" built-in, fr fr. ๐Ÿ“

๐ŸŒธ Syntax ๐ŸŒธ

wait [job...]

๐Ÿฆ‹ Description ๐Ÿฆ‹

The wait built-in waits for background jobs to terminate. The built-in can be used to wait for asynchronous commands. iykyk. ๐Ÿ”ฅ

If the shell receives a signal while the built-in is waiting and if a trap has been set for the signal, then the trap is executed and the built-in immediately finishes (without further waiting for the jobs). If the shell receives a SIGINT signal when job control is enabled, the built-in aborts waiting. ๐Ÿ˜ญ

If the shell is interactive, job-controlling, and not in the POSIXly-correct mode, the job status is printed when the job is terminated or stopped.

๐ŸŽ€ Operands ๐ŸŽ€

  • job โ€” The job ID of the job or the process ID of a process in the job.

If no jobs are specified, the built-in waits for all existing jobs.

If the specified job does not exist, the job is considered to have terminated with the exit status of 127.

๐Ÿ’€ Exit status ๐Ÿ’€

If no jobs were specified and the built-in successfully waited for all the jobs, the exit status is zero. If one or more jobs were specified, the exit status is that of the last job.

If the built-in was aborted by a signal, the exit status is an integer (> 128) that denotes the signal. If there was any other error, the exit status is between 1 and 126 (inclusive).

๐Ÿ‘‘ Notes ๐Ÿ‘‘

The wait built-in is a mandatory built-in.

The process ID of the last process of a job can be obtained by the ! special parameter. You can use the jobs built-in as well to obtain process IDs of job processes. Handy! ๐ŸŽ€


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

  • Yuki Watanabe <magicant@wonderwand.net> โ€” Author.

And that's a wrap on the whole YASH(1) glow-up, bestie!! ๐ŸŽ‰๐Ÿ’–โœจ You made it to the very end, which is honestly iconic behavior. This masterpiece is from yash version 2.61, dated 2026-03-05. Now go forth and run some commands โ€” slay. ๐Ÿ’…๐Ÿฆ‹๐ŸŒธ

๐Ÿ“ฆ where this page comes from