Yash-rs Homepage
Yash-rs is a POSIX-compliant command-line shell for Unix-like operating systems, written in Rust. It is designed to be a modern, fast, and extensible shell that provides a rich set of features for users. Currently, it is in the early stages of development and is not yet feature-complete.
Features
- Shell scripting with POSIX-compliant syntax and semantics
- Enhanced scripting features (extension to POSIX)
- Locale support
- Job control and minimal interactivity
- Command-line editing and history
- Tab completion and suggestions
License
This project is licensed under the GNU General Public License v3.0. The full license text can be found at https://github.com/magicant/yash-rs/blob/master/yash-cli/LICENSE-GPL.
Documentation
Navigate the documentation using the left sidebar. If the sidebar is not visible, you can toggle it by clicking the “hamburger” icon in the top-left corner.
Currently, the documentation is a work in progress and may not cover all features or usage scenarios. Contributions are welcome via issues or pull requests to enhance the documentation.
This documentation, hosted at https://magicant.github.io/yash-rs/, is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0). The full license text can be found at https://github.com/magicant/yash-rs/blob/master/docs/LICENSE.
Installation
Downloading precompiled binaries
Precompiled binaries are available for the following platforms:
- aarch64-unknown-linux-gnu
- x86_64-unknown-linux-gnu
- aarch64-apple-darwin
- x86_64-apple-darwin
You can download the latest release from the GitHub releases page.
Download the appropriate binary for your platform and place it in a directory included in your PATH
environment variable.
Building from source
Yash-rs is written in Rust, so you need to have the Rust toolchain installed. The recommended way to install Rust is via rustup.
If you are using Windows Subsystem for Linux (WSL), make sure to install the Linux version of rustup, not the native Windows version. For alternative installation methods, refer to the rustup book.
By default, installing rustup also installs the stable Rust toolchain. If the stable toolchain is not installed, you can add it with the following command:
rustup default stable
To install yash-rs, run:
cargo install yash-cli
Running yash-rs
After installation, you can run yash3
from the command line.
Getting started
Starting the shell
To start the shell, run yash3
from the command line. This starts an interactive shell session.
yash3
You will see a prompt, indicating the shell is ready for commands:
$
Using the shell interactively
Once the shell is running, you can type commands. The shell executes each command you enter, and you will see the output in the terminal.
Most commands run a utility, which is a program that performs a specific task. For example, you can run the echo
utility to print a message:
$ echo "Hello, world!"
Hello, world!
In this example, $
is the shell prompt, and echo "Hello, world!"
is the command you entered. The shell executed the echo
utility, which printed “Hello, world!” to the terminal.
You can also run other utilities, such as ls
, which lists the files in the working directory:
$ ls
Documents Downloads Music Pictures Videos
The output varies depending on the files in your working directory.
Interrupting a command
To interrupt a running command, press Ctrl+C
. This sends an interrupt signal to the running utility, causing it to terminate. For example, if you run a command that takes a long time, you can cancel it with Ctrl+C
:
$ sleep 10
This command sleeps for 10 seconds, but you can interrupt it by pressing Ctrl+C
. This aborts the sleep
utility and returns you to the shell prompt immediately.
Note: Some utilities may not respond to Ctrl+C
if they are designed to ignore or handle the interrupt signal differently.
Exiting the shell
To exit the shell, use the exit
command:
$ exit
This ends the shell session and returns you to your previous shell.
Alternatively, you can press Ctrl+D
to exit the shell. This sends an empty command to the shell, causing it to exit.
Running scripts
You can also run scripts in the shell. To do this, create a script file with the commands you want to run. For example, create a file called script.sh
with the following content:
echo "This is a script"
echo "Running in the shell"
Run this script in the shell by using the .
built-in utility:
$ . ./script.sh
This is a script
Running in the shell
You can also run the script by passing it as an argument to the shell:
$ yash3 ./script.sh
This is a script
Running in the shell
This runs the script in a new shell session. The output will be the same.
If you make the script executable, you can run it directly:
$ chmod a+x script.sh
$ ./script.sh
This is a script
Running in the shell
The chmod
utility makes the script file executable. This allows you to run the script directly, without specifying the shell explicitly, as in the previous example.
Note the ./
in the commands above. This indicates that the script is in the current directory. If you omit ./
, the shell searches for the script in the directories listed in the PATH
environment variable. If the script is not in one of those directories, you will get a “utility not found” error.
Versioning and compatibility
POSIX conformance
POSIX (Portable Operating System Interface) is a set of standards specified by the IEEE to ensure compatibility among Unix-like operating systems. It defines a standard operating system interface and environment, including command-line utilities, shell scripting, and system calls.
As of 2025, the latest version of POSIX is POSIX.1-2024. The requirements for shells are mainly documented in the Shell & Utilities volume. Yash-rs aims to comply with the POSIX standard, providing a consistent and portable environment for shell scripting and command execution.
The shell currently supports running shell scripts and basic interactive features in a POSIX-compliant manner. See the homepage for an overview of implemented features. Progress on POSIX conformance and feature implementation is tracked in GitHub Issues and the GitHub Project.
Many features of yash-rs are still under development, and some may not yet be fully compliant with the POSIX standard. Any non-conforming behavior is described in the Compatibility section of each feature’s documentation. These sections also clarify which aspects of the shell’s behavior are POSIX requirements and which are shell-specific extensions.
Yash and yash-rs
Yash-rs is the successor to yash, which is also a POSIX-compliant shell supporting both scripting and interactive use. Since yash-rs currently lacks advanced interactive features such as command history and line editing, yash is recommended for interactive use at this time.
Current releases of yash use version numbers in the form 2.x
(or sometimes 2.x.y
), where x
increases with each release. Earlier versions used 1.x
or 0.x
. To avoid version number conflicts, the 3.x
series is reserved for yash-rs, and there are no plans for yash to use 3.x
version numbers.
In this manual, “yash” may refer to both yash and yash-rs collectively. When a distinction is needed, we refer to the former as “previous versions of yash”, “older versions of yash”, and so on.
Yash-rs versioning policy
Yash-rs follows Semantic Versioning 2.0.0. Each release has a version number consisting of three parts separated by dots, such as 3.0.1
. The first part is the major version, incremented for breaking changes. The second is the minor version, incremented for new features that do not break compatibility. The third is the patch version, incremented for bug fixes that do not introduce new features.
Semantic versioning applies to the observable behavior of yash-rs as documented in this manual. This includes (but is not limited to) the syntax and semantics of the shell language, shell startup and termination behavior, and the behavior of built-in utilities. The major version will be incremented if any of these behaviors change in a way that is not backward compatible.
We strive to minimize breaking changes by carefully defining the scope of behavior covered by semantic versioning. In this manual, we may declare certain behaviors as “subject to change”, “may change in the future”, and similar phrases to reserve the right to make changes without incrementing the major version. Additionally, the following changes are not considered breaking:
- Changes to undocumented or internal behavior
- New features that were previously documented as “not yet implemented/supported”
- New features that allow commands which previously resulted in errors to succeed
- Changes that make non-conforming behavior compliant with POSIX
See the changelog for a detailed list of changes and updates to yash-rs. Note that the documentation at https://magicant.github.io/yash-rs/ is deployed from the master branch and may include unreleased changes.
Shell language
The shell interprets input as commands written in the shell language. The language has a syntax (how commands are written and structured) and semantics (how commands are executed). This page gives a brief overview of shell commands.
Simple commands
A simple command is the most basic command type. It consists of a sequence of words that are not reserved words or operators. For example:
ls
or:
echo "Hello, world!"
Most simple commands run a utility—a program that performs a specific task. The first word is the utility name; the rest are arguments.
All words (except redirection operators) in a simple command are expanded before the utility runs. See Words, tokens, and fields for details on parsing and expansion.
You can use parameters to change command behavior dynamically. There are three types: variables, special parameters, and positional parameters.
See Simple commands for more on assignments, redirections, and command search.
Other commands
Other command types construct more complex behavior by combining commands. See Commands for the full list. For example:
- Compound commands group commands, control execution, and handle conditions and loops. Examples:
if
,for
,while
,case
. - Pipelines connect the output of one command to the input of another, letting you chain commands.
- And-or lists control execution flow based on command success or failure.
- Lists let you run multiple commands in sequence or in parallel.
Functions
Functions are reusable blocks of code you can define and call in the shell. They help organize scripts and interactive sessions.
Redirections
Redirections control where command input and output go. Use them to save output to files or read input from files.
Aliases
Aliases are shortcuts for longer commands or command sequences. They let you create custom names for commands you use often.
Words, tokens, and fields
In the shell language, a word is a sequence of characters, usually separated by whitespace. Words represent commands, arguments, and other elements in the shell.
In this example, echo
, Hello,
, and world!
are all words:
$ echo Hello, world!
Hello, world!
The first word (echo
) is the name of the utility to run. The other words are arguments passed to that utility.
Before running the utility, the shell expands words. This means the shell processes certain characters and sequences in the words to produce the final command line. For example, $
is used for parameter expansion, letting you access variable values:
$ name="Alice"
$ echo Hello, $name!
Hello, Alice!
Here, $name
is expanded to its value (Alice
) before echo
runs.
To prevent expansion, quote the characters you want to treat literally. For example, to print $name
without expanding it, use single quotes:
$ echo '$name'
$name
Tokens and operators
A token is a sequence of characters processed as a single unit in shell syntax. The shell divides input into tokens, which are then parsed to form commands. A token is either a word or an operator.
The shell recognizes these operators:
- Newline – Command separator
;
– Command separator&
– Asynchronous command&&
– Logical and||
– Logical or|
– Pipeline(
– Start of a subshell)
– End of a subshell;;
– End of a case item;&
– End of a case item;;&
– End of a case item;|
– End of a case item<
– Input redirection<&
– Input redirection<(
– Process redirection<<
– Here document<<-
– Here document<<<
– Here string (not implemented yet)<>
– Input and output redirection>
– Output redirection>&
– Output redirection>|
– Output redirection>(
– Process redirection>>
– Output redirection>>|
– Pipeline redirection
When recognizing operators, the shell matches the longest possible sequence first. For example, &&
is a single operator, not two &
operators, and <<<<
is recognized as <<<
and <
, not two <<
operators.
Blank characters (spaces and tabs) separate tokens unless quoted. Words (non-operator tokens) must be separated by at least one blank character. Operators do not need to be separated by blanks if they are recognized as expected.
These two lines are equivalent:
$ ((echo hello))
hello
$ ( ( echo hello ) )
hello
However, you cannot omit the space between ;
and ;;
in a case command:
$ case foo in (foo) echo foo; ;; esac
foo
$ case foo in (foo) echo foo;;; esac
error: the pattern is not a valid word token
--> <stdin>:2:29
|
2 | case foo in (foo) echo foo;;; esac
| ^ expected a word
|
Word expansion
The shell performs several kinds of word expansion before running a utility, such as replacing parameters with their values or evaluating arithmetic expressions.
The following expansions happen first:
After these, the shell performs these steps in order:
The result is a list of words passed to the utility. Each word resulting from these expansions is called a field.
A subset of these expansions are performed depending on the context. For example, when assigning a variable, the shell performs tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before the assignment. However, field splitting and pathname expansion do not occur during variable assignment, since the value of a variable cannot be split into multiple fields.
Dollar signs
A dollar sign ($
) may introduce a dollar single quote, parameter expansion, command substitution, or arithmetic expansion, depending on the following characters. If the dollar sign is not followed by a valid sequence for any of these expansions, it is treated as a literal character.
Dollar signs that are immediately followed by a non-whitespace character and do not form a valid expansion sequence are currently treated as literal characters, but this usage may change in future versions of the shell. For example, $%
and $+
may be interpreted as special parameters in the future.
Quoting and escaping
Some characters have special meanings in the shell. For example, the dollar sign ($
) is used for parameter expansion, and the asterisk (*
) is used for pathname expansion. To include these characters literally in a command, you need to quote or escape them.
What characters need quoting?
The following characters have special meanings in the shell and may need quoting or escaping:
| & ; < > ( ) $ ` \ " '
Whitespace characters (spaces, tabs, and newlines) also need quoting or escaping if they are part of a command word.
Additionally, the following characters are treated specially in certain contexts:
* ? [ ] ^ - ! # ~ = % { , }
It is best to quote or escape these characters when they are used to stand for themselves in a command.
You also need to quote reserved words (e.g., if
, while
, etc.) to treat them as regular words.
The following subsections explain methods for quoting and escaping characters in the shell.
Single quotes
Single quotes enclose a string and prevent the shell from interpreting special characters. Everything inside single quotes is treated literally, including spaces and special characters.
For example, the following command prints the string "$foo"
without interpreting the $
as a parameter expansion or the "
as a double quote:
$ echo '"$foo"'
"$foo"
Single quotes can contain newline characters:
$ echo 'foo
> bar'
foo
bar
Note that the >
prompt indicates that the command continues on the next line.
You cannot include a single quote character inside a single-quoted string. Use double quotes or a backslash to escape it:
$ echo "'"
'
$ echo \'
'
Double quotes
Double quotes enclose a string. Most characters inside double quotes are treated literally, but some characters are still interpreted by the shell:
$
: Parameter expansion, command substitution, and arithmetic expansion`
: Command substitution\
: Character escape, only before"
,$
,`
,\
, and newline
For example, single quote characters are treated literally and parameter expansion is performed inside double quotes:
$ foo="* *"
$ echo "foo='$foo'"
foo='* *'
Double quotes prevent field splitting and pathname expansion on the result of expansions. If the argument to the echo
utility were not double-quoted in the above example, the output might have been different depending on the result of field splitting and pathname expansion.
Backslash
The backslash escapes special characters, allowing you to include them in a string without interpretation.
Outside double quotes, a backslash can escape any character except newline. For example:
cat My\ Diary.txt
This prints the contents of the file My Diary.txt
.
When used in double quotes, the backslash only escapes the following characters: "
, $
, `
, and \
. For example:
cat "My\ Diary\$.txt"
This will print the contents of the file My\ Diary$.txt
. Note that the backslash before the space is treated literally, and the backslash before the dollar sign is treated as an escape character.
When used in a braced parameter expansion that occurs inside double quotes, the backslash additionally escapes }
:
$ var="{foo}bar"
$ echo "${var#*\}}"
bar
Within backquotes, arithmetic expansions, and unquoted here-document contents, backslashes only escape $
, `
, and \
. If backquotes appear inside double quotes, backslashes also escape "
. See examples in Command substitution and Arithmetic expansion.
Line continuation
Line continuation allows you to split long commands into multiple lines for better readability. Use a backslash followed by a newline to indicate that the command continues on the next line. A backslash-newline pair is ignored by the shell as if it were not there. Line continuation can be used inside and outside double quotes, but not inside single quotes.
$ echo "This is a long command that \
> continues on the next line"
This is a long command that continues on the next line
To treat a newline literally rather than as a line continuation, use single or double quotes.
Dollar single quotes
Dollar single quotes ($'…'
) are used to specify strings with escape sequences, similar to those in C. The content inside the quotes is parsed, and recognized escape sequences are replaced with their corresponding characters.
For example, \n
is replaced with a newline character:
$ echo $'foo\nbar'
foo
bar
The following escape sequences are recognized inside dollar single quotes:
\\
– backslash\'
– single quote\"
– double quote\n
– newline\t
– tab\r
– carriage return\a
– alert (bell)\b
– backspace\e
or\E
– escape\f
– form feed\v
– vertical tab\?
– question mark\cX
– control character (e.g.,\cA
for^A
)\xHH
– byte with hexadecimal valueHH
(1–2 hex digits)\uHHHH
– Unicode character with hexadecimal valueHHHH
(4 hex digits)\UHHHHHHHH
– Unicode character with hexadecimal valueHHHHHHHH
(8 hex digits)\NNN
– byte with octal valueNNN
(1–3 octal digits)
Unrecognized or incomplete escape sequences cause an error.
A backslash followed by a newline is not treated as a line continuation inside dollar single quotes; they are rejected as an error.
Example with Unicode:
$ echo $'\u3042'
あ
Dollar single quotes are useful for specifying strings with special characters.
In the current implementation, escape sequences that produce a byte are treated as a Unicode character with the same value and converted to UTF-8. This means that byte values greater than or equal to 0x80 are converted to two bytes of UTF-8. This behavior does not conform to the POSIX standard and may change in the future.
Quote removal
When a word is expanded, any quotation marks (single quotes, double quotes, or backslashes used for quoting) that were present in the original command are removed. This process is called quote removal.
For example:
$ echo 'Hello, world!' # the single quotes are removed during expansion
Hello, world!
Quote removal only affects quotes that were part of the original input, not those introduced by expansions:
$ x='\*'
$ echo $x # the backslash is not removed because it was introduced by expansion
\*
Reserved words
Some words have special meaning in shell syntax. These reserved words must be quoted to use them literally. The reserved words are:
!
– Negation{
– Start of a grouping}
– End of a grouping[[
– Start of a double bracket commandcase
– Case commanddo
– Start of a loop or conditional blockdone
– End of a loop or conditional blockelif
– Else if clauseelse
– Else clauseesac
– End of a case commandfi
– End of an if commandfor
– For loopfunction
– Function definitionif
– If commandin
– Delimiter for a for loop and case commandthen
– Then clauseuntil
– Until loopwhile
– While loop
Currently, [[
and function
are only recognized as reserved words; their functionality is not yet implemented.
Additionally, the POSIX standard allows for the following optional reserved words:
]]
– End of a double bracket commandnamespace
– Namespace declarationselect
– Select commandtime
– Time command- Any words that end with a colon (
:
)
These words are not reserved in yash-rs now, but may be in the future.
Where are reserved words recognized?
Reserved words are recognized in these contexts:
- As the first word of a command
- As a word following any reserved word other than
case
,for
, orin
in
as the third word in a for loop or case commanddo
as the third word in a for loop
Examples
This example uses the reserved words for
, in
, do
, and done
in a for loop:
$ for i in 1 2 3; do echo $i; done
1
2
3
In the following example, {
, do
, and }
are not reserved words because they are not the first word of the command:
$ echo { do re mi }
{ do re mi }
Reserved words are recognized only when they appear as a whole word. In this example, {
and }
are not reserved words because they are part of {echo
and Hello}
:
$ {echo Hello}
error: cannot execute external utility "{echo"
--> <stdin>:1:1
|
1 | {echo Hello}
| ^^^^^ utility not found
|
To use {
and }
as reserved words, write them as separate words:
$ { echo Hello; }
Hello
Comments
Use comments to add notes or explanations to shell scripts and commands. The shell ignores comments while parsing commands.
A comment starts with the #
character and continues to the end of the line.
$ # This is a comment
$ echo "Hello, world!" # This prints a message
Hello, world!
Always separate the start of a comment from the preceding word with whitespace. If there is no whitespace, the shell treats the #
as part of the word, not as a comment.
$ echo "Hello, world!"# This is not a comment
Hello, world!# This is not a comment
Everything after #
on the same line is ignored by the shell. You cannot use line continuation inside comments.
$ echo one # This backslash is not a line continuation 👉 \
one
$ echo two # So this line is a separate command
two
Tilde expansion
In tilde expansion, the shell replaces a tilde (~
) at the start of a word with the value of the HOME
variable, allowing you to specify paths relative to your home directory.
For example, if HOME
is /home/alice
:
The HOME
variable is usually passed as an environment variable to the shell when the user logs in, so you don’t need to set it manually.
You can also use ~
followed by a username to refer to another user’s home directory:
$ echo ~bob
/home/bob
$ echo ~bob/Documents
/home/bob/Documents
In variable assignments, tilde expansion happens at the start of the value and after each :
character:
$ PATH=~/bin:~bob/bin:~clara/bin:/usr/bin
$ echo "$PATH"
/home/alice/bin:/home/bob/bin:/home/clara/bin:/usr/bin
If tilde expansion produces a pathname ending with /
followed by another /
, one /
is removed:
$ HOME=/
$ echo ~/tmp
/tmp
In older shells, //tmp
may be produced instead of /tmp
, which can refer to a different location. POSIX.1-2024 now requires the behavior shown above.
Tilde expansion is delimited by a slash (/
). If any part of the expansion or delimiter is quoted, the shell treats them literally:
$ echo ~'b'ob
~bob
$ echo ~\/
~/
Currently, the shell ignores any errors during tilde expansion and leaves the tilde as is. This behavior may change in the future.
The shell may support other forms of tilde expansion in the future, e.g., ~+
for the current working directory.
Parameter expansion
Parameter expansion retrieves the value of a parameter when a command is executed. The basic syntax is ${parameter}
.
$ user="Alice" # define a variable
$ echo "Hello, ${user}!" # expand the variable
Hello, Alice!
Unset parameters
If a parameter is unset, the shell expands it to an empty string by default.
$ unset user
$ echo "Hello, ${user}!"
Hello, !
If the nounset
shell option is enabled, expanding an unset parameter is an error:
$ set -o nounset
$ echo "Hello, ${user}!"
error: cannot expand unset parameter
--> <stdin>:2:14
|
2 | echo "Hello, ${user}!"
| ^^^^^^^ parameter `user` is not set
|
= info: unset parameters are disallowed by the nounset option
Using nounset
is recommended to catch typos in variable names.
Omitting braces
Braces are optional if the parameter is:
- a variable name with only ASCII letters, digits, and underscores (e.g.,
$HOME
,$user
) - a special parameter (e.g.,
$?
,$#
) - a single-digit positional parameter (e.g.,
$1
,$2
)
For variable names, the shell uses the longest possible name after $
, regardless of whether the variable exists:
$ user="Alice"
$ unset username
$ echo "Hello, $username!" # $user is not considered
Hello, !
For positional parameters, only a single digit is used, even if followed by more digits:
$ set foo bar baz # set three positional parameters
$ echo "$12" # $1 expands to the first positional parameter
foo2
Modifiers
Modifiers change the value of a parameter during expansion. Modifiers can only be used in braced expansions, and only one modifier is allowed per expansion.
Length
The length modifier ${#parameter}
returns the number of characters in the parameter’s value.
$ user="Alice"
$ echo "Length of user: ${#user}"
Length of user: 5
As an extension, the length modifier can be used with arrays or special parameters *
or @
, applying the modifier to each element:
$ users=(Alice Bob Charlie)
$ echo "Lengths of users: ${#users}"
Lengths of users: 5 3 7
$ set yellow red green blue # set four positional parameters
$ echo "Lengths of positional parameters: ${#*}"
Lengths of positional parameters: 6 3 5 4
Switch
The switch modifier changes the result based on whether a parameter is set or empty. There are eight forms:
${parameter-word}
– Useword
ifparameter
is unset.${parameter:-word}
– Useword
ifparameter
is unset or empty.${parameter+word}
– Useword
ifparameter
is set.${parameter:+word}
– Useword
ifparameter
is set and not empty.${parameter=word}
– Assignword
toparameter
if unset, using the new value.${parameter:=word}
– Assignword
toparameter
if unset or empty, using the new value.${parameter?word}
– Error withword
ifparameter
is unset.${parameter:?word}
– Error withword
ifparameter
is unset or empty.
Examples:
$ user="Alice"
$ echo "Hello, ${user-World}!"
Hello, Alice!
$ unset user
$ echo "Hello, ${user-World}!"
Hello, World!
$ unset PATH
$ PATH="/bin${PATH:+:$PATH}"
$ echo "$PATH"
/bin
$ PATH="/usr/bin${PATH:+:$PATH}"
$ echo "$PATH"
/usr/bin:/bin
$ unset user
$ echo "Hello, ${user=Alice}!"
Hello, Alice!
$ echo "Hello, ${user=Bob}!"
Hello, Alice!
$ user="Alice"
$ echo "Hello, ${user?tell me your name}!"
Hello, Alice!
$ unset user
$ echo "Hello, ${user?tell me your name}!"
error: tell me your name
--> <stdin>:4:14
|
4 | echo "Hello, ${user?tell me your name}!"
| ^^^^^^^^^^^^^^^^^^^^^^^^^ parameter `user` is not set
|
In all cases, the following expansions are performed on word
before use:
- Tilde expansion (unless the parameter expansion is in double quotes)
- Parameter expansion (recursive!)
- Command substitution
- Arithmetic expansion
For the =
and :=
forms, quote removal is also performed before assignment. Assignment only works for variables, not special or positional parameters.
If word
is empty in the ?
and :?
forms, a default error message is used.
The nounset
option does not apply to expansions with a switch modifier.
Trim
The trim modifier removes leading or trailing characters matching a pattern from a parameter’s value. There are four forms:
${parameter#pattern}
– Remove the shortest match ofpattern
from the start.${parameter##pattern}
– Remove the longest match ofpattern
from the start.${parameter%pattern}
– Remove the shortest match ofpattern
from the end.${parameter%%pattern}
– Remove the longest match ofpattern
from the end.
The value is matched against the pattern, and the matching part is removed.
$ var="banana"
$ echo "${var#*a}"
nana
$ echo "${var##*a}"
$ echo "${var%a*}"
banan
$ echo "${var%%a*}"
b
The pattern is expanded before use:
- Tilde expansion
- Parameter expansion (recursive!)
- Command substitution
- Arithmetic expansion
You can quote part or all of the pattern to treat it literally:
$ asterisks="***"
$ echo "${asterisks##*}" # removes the whole value
$ echo "${asterisks##\*}" # removes the first *
**
$ echo "${asterisks##'**'}" # removes the first two *
*
Compatibility
Some modifiers are ambiguous when used with a certain special parameter. Yash and many other shells interpret ${##}
, ${#-}
, and ${#?}
as length modifiers applied to special parameters #
, -
, and ?
, not as switch or trim modifiers applied to #
. The POSIX standard is unclear on this point.
The result is unspecified in POSIX for:
- a length or switch modifier applied to special parameter
*
or@
- a trim modifier applied to special parameter
#
,*
, or@
Command substitution
Command substitution expands to the output of a command. It has two forms: the preferred $(command)
form and the deprecated backquote form `command`
.
For example, this runs dirname -- "$0"
and passes its output to cd
:
$ cd -P -- "$(dirname -- "$0")"
This changes the working directory to the directory containing the script, regardless of the current directory.
Syntax
The $(…)
form evaluates the command inside the parentheses. It supports nesting and is easier to read than backquotes:
$ echo $(echo $(echo hello))
hello
$ echo "$(echo "$(echo hello)")"
hello
In the backquote form, backslashes escape $
, `
, and \
. If backquotes appear inside double quotes, backslashes also escape "
. These escapes are processed before the command is run. A backquote-form equivalent to the previous example is:
$ echo `echo \`echo hello\``
hello
$ echo "`echo \"\`echo hello\`\"`"
hello
The $(…)
form can be confused with arithmetic expansion. Command substitution is only recognized if the code is not a valid arithmetic expression. For example, $((echo + 1))
is arithmetic expansion, but $((echo + 1); (echo + 2))
is command substitution. To force command substitution starting with a subshell, insert a space: $( (echo + 1); (echo + 2))
.
Semantics
The command runs in a subshell, and its standard output is captured. Standard error is not captured unless redirected. Trailing newlines are removed, and the result replaces the command substitution in the command line.
Currently, yash-rs parses the command when the substitution is executed, not when it is parsed. This may change in the future, affecting when syntax errors are detected and when aliases are substituted.
Compatibility
When a command substitution only contains redirections, some shells treat it specially. For example, $(<file)
is equivalent to $(cat file)
in bash and zsh, but yash-rs does not support this yet.
Arithmetic expansion
Arithmetic expansion evaluates an arithmetic expression and replaces it with the result. The syntax is $((expression))
.
$ echo $((1 + 2))
3
$ echo $((2 * 3 + 4))
10
$ echo $((2 * (3 + 4)))
14
Arithmetic expansion works in two steps. First, the expression is processed for parameter expansion, nested arithmetic expansion, command substitution, and quote removal. Then, the resulting string is parsed as an arithmetic expression, and the result replaces the expansion.
$ x=2
$ echo $(($x + $((3 * 4)) + $(echo 5)))
19
Variables
The value of variables appearing as parameter expansions does not have to be numeric, but the resulting arithmetic expression must be valid.
$ seven=7
$ var='6 * sev'
$ echo $((${var}en)) # expands to $((6 * seven))
42
Note the difference between such parameters, which are expanded before the arithmetic expansion is evaluated, and variables that are part of the expression:
$ seven='3 + 4'
$ echo $((2 * $seven)) # expands to $((2 * 3 + 4)), mind the precedence
10
$ echo $((2 * seven))
error: error evaluating the arithmetic expansion
--> <arithmetic_expansion>:1:5
|
1 | 2 * seven
| ^^^^^ invalid variable value: "3 + 4"
|
::: <stdin>:3:6
|
3 | echo $((2 * seven))
| -------------- info: arithmetic expansion appeared here
|
Quoting
Backslash escaping is the only supported quoting mechanism in arithmetic expansion. It can escape $
, `
, and \
. However, escaped characters would never produce a valid arithmetic expression after quote removal, so they are not useful in practice.
$ echo $((\$x))
error: error evaluating the arithmetic expansion
--> <arithmetic_expansion>:1:1
|
1 | $x
| ^ invalid character
|
::: <stdin>:1:6
|
1 | echo $((\$x))
| -------- info: arithmetic expansion appeared here
|
Field splitting
Field splitting breaks a word into fields at delimiters. This happens after parameter expansion, command substitution, and arithmetic expansion, but before pathname expansion and quote removal.
In this example, $flags
is split at the space, so ls
receives two arguments:
$ flags='-a -l'
$ ls $flags
total 10468
drwxr-xr-x 2 user group 4096 Oct 10 12:34 Documents
drwxr-xr-x 3 user group 4096 Oct 10 12:34 Downloads
drwxr-xr-x 3 user group 4096 Oct 10 12:34 Music
drwxr-xr-x 2 user group 4096 Oct 10 12:34 Pictures
drwxr-xr-x 2 user group 4096 Oct 10 12:34 Videos
Field splitting does not occur if the expansion is quoted:
$ flags='-a -l'
$ ls "$flags"
ls: invalid option -- ' '
Try 'ls --help' for more information.
Field splitting only applies to the results of parameter expansion, command substitution, and arithmetic expansion, not to literals or tilde expansions:
$ HOME='/home/user/My Documents'
$ ls ~
Documents Downloads Music Pictures Videos
$ ls "$HOME"
Documents Downloads Music Pictures Videos
$ ls $HOME
ls: cannot access '/home/user/My': No such file or directory
ls: cannot access 'Documents': No such file or directory
Field splitting only happens where words are expected, such as simple command words, for loop words, and array assignments. It does not occur in contexts expecting a single word, like scalar assignments or case patterns.
$ flags='-a -l'
$ oldflags=$flags # no field splitting; oldflags is '-a -l'
$ flags="$flags -r"
$ ls $flags # field splitting; ls receives '-a', '-l', and '-r'
Videos Pictures Music Downloads Documents
$ flags=$oldflags # again, no field splitting
$ echo "Restored flags: $flags"
Restored flags: -a -l
IFS
Field splitting is controlled by the IFS
(Internal Field Separator) variable, which lists delimiter characters. By default, IFS
contains a space, tab, and newline. You can change IFS
to use different delimiters.
If IFS
is unset, the default value is used:
$ unset IFS
$ flags='-a -l'
$ ls $flags
total 10468
drwxr-xr-x 2 user group 4096 Oct 10 12:34 Documents
drwxr-xr-x 3 user group 4096 Oct 10 12:34 Downloads
drwxr-xr-x 3 user group 4096 Oct 10 12:34 Music
drwxr-xr-x 2 user group 4096 Oct 10 12:34 Pictures
drwxr-xr-x 2 user group 4096 Oct 10 12:34 Videos
If IFS
is set to an empty string, no splitting occurs:
$ IFS=''
$ flags='-a -l'
$ ls $flags
ls: invalid option -- ' '
Try 'ls --help' for more information.
Each character in IFS
is a delimiter. How fields are split depends on whether a delimiter is whitespace or not.
Non-whitespace delimiters split the word at their position and may produce empty fields:
$ IFS=':'
$ values='a:b::c:d'
$ for value in $values; do echo "[$value]"; done
[a]
[b]
[]
[c]
[d]
Empty fields are not produced after a trailing non-whitespace delimiter:
$ IFS=':'
$ values='a:b:'
$ for value in $values; do echo "[$value]"; done
[a]
[b]
Whitespace delimiters also split the word, but do not produce empty fields. Multiple whitespace delimiters in a row are treated as one:
$ IFS=' '
$ values=' a b c'
$ for value in $values; do echo "[$value]"; done
[a]
[b]
[c]
Whitespace and non-whitespace delimiters can be combined in IFS
:
$ IFS=' :'
$ values='a:b c : d: :e f '
$ for value in $values; do echo "[$value]"; done
[a]
[b]
[c]
[d]
[]
[e]
[f]
Currently, yash-rs only supports UTF-8 encoded text. This does not fully conform to POSIX, which requires handling arbitrary byte sequences.
Empty field removal
During field splitting, empty fields are removed, except those delimited by non-whitespace IFS
characters.
$ empty='' space=' '
$ for value in $empty; do echo "[$value]"; done # prints nothing
$ for value in $space; do echo "[$value]"; done # prints nothing
Empty fields are removed even if IFS
is empty:
$ IFS=''
$ empty='' space=' '
$ for value in $empty; do echo "[$value]"; done # prints nothing
$ for value in $space; do echo "[$value]"; done # prints one field containing a space
[ ]
To retain empty fields, quote the word to prevent field splitting:
$ empty='' space=' '
$ for value in "$empty"; do echo "[$value]"; done
[]
$ for value in "$space"; do echo "[$value]"; done
[ ]
Pathname expansion (globbing)
Pathname expansion—also known as globbing—lets you use special patterns to match filenames and directories. The shell expands these patterns into a list of matching pathnames.
For example, *.txt
matches all files in the current directory ending with .txt
:
$ echo *.txt
notes.txt todo.txt
When does pathname expansion happen?
Pathname expansion occurs after field splitting and before quote removal. It only applies to unquoted words containing non-literal pattern characters.
If the noglob
shell option is set, pathname expansion is skipped.
Pattern syntax
Pathname expansion uses shell patterns. Patterns may include special characters such as *
, ?
, and bracket expressions. See Pattern matching for a full description of pattern syntax and matching rules.
The following subsections describe aspects of pattern matching specific to pathname expansion.
Unmatched brackets
Unmatched brackets like [a
and b]c
are treated as literals. However, some shells may treat other glob characters as literals if they are used with unmatched open brackets. To avoid this, make sure to quote unmatched open brackets:
$ echo [a
[a
$ echo \[*
[a [b [c
Subdirectories
Globs do not match /
in filenames. To match files in subdirectories, include /
in the pattern:
$ echo */*.txt
docs/readme.txt notes/todo.txt
Brackets cannot contain /
because patterns are recognized for each component separated by /
. For example, the pattern a[/]b
only matches the literal pathname a[/]b
, not a/b
, because the brackets are considered unmatched in the sub-patterns a[
and ]b
.
Hidden files
By default, glob patterns do not match files starting with a dot (.
). To match hidden files, the pattern must start with a literal dot:
$ echo .*.txt
.hidden.txt
Glob patterns never match the filenames .
and ..
, even if a pattern begins with a literal dot.
$ echo .*
.backup.log .hidden.txt
No matches
If a pattern does not match any files, it is left unchanged. If the shell does not have permission to read the directory, the pattern is also left unchanged.
Parameters
A parameter is a name-value pair used to store and retrieve data in a shell script. Parameters can be variables, special parameters, or positional parameters.
Parameter expansion retrieves the value of a parameter when the command is executed.
$ name="Alice" # define a variable
$ echo "Hello, $name!" # expand the variable
Hello, Alice!
Variables
Variables are parameters with alphanumeric names that can be assigned values. Use variable assignment to define a variable by specifying a name and value.
Variable names
Variable names can contain letters, digits, and underscores, but cannot start with a digit. Variable names are case-sensitive, so VAR
and var
are different variables.
It is common to use uppercase letters for environment variables and lowercase for local variables. This avoids accidentally overwriting environment variables.
According to POSIX.1-2024, only ASCII letters, digits, and underscores are portably accepted in variable names. Many shells allow additional characters. Yash-rs currently accepts Unicode letters and digits in variable names, but this may change in the future.
Defining variables
To define a variable, use the assignment syntax:
$ user=Alice
This creates a variable named user
with the value Alice
. There must be no spaces around the =
sign.
Before a value is assigned, the following expansions are performed, if any:
Note that field splitting and pathname expansion do not happen during assignment.
$ star=* # assigns a literal `*` to the variable `star`
$ echo "$star" # shows the value of `star`
*
$ echo $star # unquoted, the value is subject to field splitting and pathname expansion
Documents Downloads Music Pictures Videos
See Simple commands for more on assignment behavior.
Environment variables
Environment variables are variables exported to child processes. To export a variable, use the export
built-in:
$ export user=Alice
$ sh -c 'echo $user'
Alice
When the shell starts, it inherits environment variables from its parent. These are automatically exported to child processes.
Read-only variables
The readonly
built-in makes a variable read-only, preventing it from being modified or unset. This is useful for defining constants.
$ readonly pi=3.14
$ pi=3.14159
error: error assigning to variable
--> <stdin>:2:1
|
2 | pi=3.14159
| ^^^^^^^^^^ cannot assign to read-only variable "pi"
|
::: <stdin>:1:10
|
1 | readonly pi=3.14
| ------- info: the variable was made read-only here
|
Variables are read-only only in the current shell session. Exported environment variables are not read-only in child processes.
Local variables
Variables defined by the typeset
built-in (without --global
) are local to the current function. Local variables are removed when the function returns. This helps avoid name conflicts and keeps temporary variables out of the global namespace.
$ i=0
$ list() {
> typeset i
> for i in 1 2 3; do
> echo "Inside function: $i"
> done
> }
$ list
Inside function: 1
Inside function: 2
Inside function: 3
$ echo "Outside function: $i"
Outside function: 0
The original (global) variable is hidden by the local variable inside the function and restored when the function returns.
Variables have dynamic scope: functions can access local variables defined in the function that called them, as well as global variables.
$ outer() {
> typeset user="Alice"
> inner
> echo "User in outer: $user"
> }
$ inner() {
> echo "User in inner: ${user-not set}"
> user="Bob"
> }
$ outer
User in inner: Alice
User in outer: Bob
$ echo "User in global scope: ${user-not set}"
User in global scope: not set
$ inner
User in inner: not set
$ echo "User in global scope: ${user-not set}"
User in global scope: Bob
In this example, inner
called from outer
accesses the local variable user
defined in outer
. The value is changed in inner
, and this change is visible in outer
after inner
returns. After outer
returns, the local variable no longer exists. When inner
is called directly, it creates a new global variable user
.
Removing variables
The unset
built-in removes a variable.
$ user=Alice
$ echo user=$user
user=Alice
$ unset user
$ echo user=$user
user=
Undefined variables by default expand to an empty string. Use the -u
shell option to make the shell treat undefined variables as an error.
Reserved variable names
Some variable names are reserved for special purposes. These variables may affect or be affected by the shell’s behavior.
-
CDPATH
: A colon-separated list of directories to search in thecd
built-in -
ENV
: The name of a file to be sourced when starting an interactive shell -
HOME
: The user’s home directory, used in tilde expansion -
IFS
: A list of delimiters used in field splitting- The default value is a space, tab, and newline.
-
LINENO
: The current line number in the shell script- This variable is automatically updated as the shell executes commands.
- Currently, yash-rs does not support exporting this variable.
-
OLDPWD
: The previous working directory, updated by thecd
built-in -
OPTARG
: The value of the last option argument processed by thegetopts
built-in -
OPTIND
: The index of the next option to be processed by thegetopts
built-in -
PATH
: A colon-separated list of directories to search for executable files in command search -
PPID
: The process ID of the parent process of the shell- This variable is initialized when the shell starts.
-
PS1
: The primary prompt string, displayed before each command in interactive mode- The default value is
$
(a dollar sign followed by a space). - Many shells set the default value to
#
(a hash sign followed by a space) for the root user. This is not yet implemented in yash-rs.
- The default value is
-
PS2
: The secondary prompt string, displayed when a command is continued on the next line- The default value is
>
(a greater-than sign followed by a space).
- The default value is
-
PS4
: The pseudo-prompt string, used for command execution tracing- The default value is
+
(a plus sign followed by a space).
- The default value is
-
PWD
: The current working directory- This variable is initialized to the working directory when the shell starts and updated by the
cd
built-in when changing directories.
- This variable is initialized to the working directory when the shell starts and updated by the
Arrays
Arrays are variables that can hold multiple values.
Defining arrays
To define an array, wrap the values in parentheses:
$ fruits=(apple banana cherry)
Accessing array elements
Accessing individual elements is not yet implemented in yash-rs.
To access all elements, use the array name in parameter expansion:
Special parameters
Special parameters are predefined parameters that have symbolic names and provide specific information about the shell environment. They are not user-defined variables and cannot be assigned values with the assignment syntax.
Below are the special parameters and their meanings:
-
@
: All positional parameters.- Expands to all positional parameters as separate fields. Useful for passing all arguments as is to a utility or function.
- When expanded outside double quotes, the result is subject to field splitting and pathname expansion. To preserve each parameter as a separate field, use
"$@"
. If there are no positional parameters,"$@"
expands to zero fields. - In contexts where only one field is expected (such as in the content of a here-document),
@
expands to a single field with all positional parameters joined by the first character of theIFS
variable (defaults to space if unset, or no separator ifIFS
is empty).
$ set foo 'bar bar' baz # three positional parameters $ for value in "$@"; do echo "[$value]"; done [foo] [bar bar] [baz] $ for value in $@; do echo "[$value]"; done [foo] [bar] [bar] [baz]
-
*
: All positional parameters.- Similar to
@
, but in double quotes,*
expands to a single field containing all positional parameters joined by the first character ofIFS
.
$ set foo 'bar bar' baz # three positional parameters $ for value in "$*"; do echo "[$value]"; done [foo bar bar baz] $ for value in $*; do echo "[$value]"; done [foo] [bar] [bar] [baz]
- Similar to
-
#
: Number of positional parameters.$ set foo 'bar bar' baz $ echo "$#" 3
-
?
: Exit status of the last command. -
-
: Current shell options.- Expands to the short names of all currently set shell options, concatenated together. Options without a short name are omitted. For example, if
-i
and-m
are set, the value isim
.
- Expands to the short names of all currently set shell options, concatenated together. Options without a short name are omitted. For example, if
-
$
: Process ID of the current shell.- Set when the shell starts and remains constant, even in subshells.
-
!
: Process ID of the last asynchronous command.- Updated when an asynchronous command is started or resumed in the background.
- The value is
0
until any asynchronous command is executed in the current shell environment. However, the behavior may be changed in the future so that it works like an unset parameter.
-
0
: Name of the shell or script being executed.- Set at shell startup and remains constant.
- If neither the
-c
nor-s
shell option is active, the value of0
is the first operand in the shell invocation (the script pathname). - If the
-c
option is used and a second operand is present, that operand is used as0
. - Otherwise,
0
is set to the first argument passed to the shell, usually the shell’s name.
Positional parameters
Positional parameters are parameters identified by their position in the command line. They are commonly used to pass arguments to scripts or functions.
Initializing positional parameters
Positional parameters are set when the shell starts:
-
If neither the
-c
nor-s
shell option is active, positional parameters are set to the operands after the first operand in the shell invocation. For example:yash3 script.sh arg1 arg2 arg3
Here, the positional parameters are
arg1
,arg2
, andarg3
. -
If the
-c
option is used, positional parameters are set to operands after the second operand, if any:yash3 -c 'echo "$1" "$2"' arg0 arg1 arg2
The positional parameters are
arg1
andarg2
. The second operand (arg0
) is used as special parameter0
, not as a positional parameter. -
If the
-s
option is active, positional parameters are set to all operands in the shell invocation:yash3 -s arg1 arg2 arg3
The positional parameters are
arg1
,arg2
, andarg3
.
Modifying positional parameters
To set positional parameters, use the set
built-in:
$ set foo bar baz
$ echo "$1" "$2" "$3"
foo bar baz
To append new parameters without removing existing ones, use set -- "$@"
followed by the new parameters:
$ set old_param1 old_param2
$ set -- "$@" new_param1 new_param2
$ echo "$1" "$2" "$3" "$4"
old_param1 old_param2 new_param1 new_param2
The --
marks the end of options, so parameters starting with -
are not treated as options.
To remove the first N positional parameters, use shift
:
$ set foo bar baz qux
$ echo "$1" "$2" "$3" "$4"
foo bar baz qux
$ shift 2
$ echo "$1" "$2"
baz qux
If set
is called with no operands, positional parameters are unchanged. To clear them, use set --
or shift "$#"
.
When a function is called, positional parameters are set to the function’s arguments. You can modify them within the function using set
or shift
. After the function returns, the original positional parameters are restored.
Expanding positional parameters
In parameter expansion, positional parameters are referenced by their position, starting from 1
:
$ set foo bar baz
$ echo "$3" "$2" "$1"
baz bar foo
For positions above 9
, use braces:
$ set a b c d e f g h i j k l m n o p q r s t u v w x y z
$ echo "${1}" "${10}" "${26}"
a j z
$ echo "$10" # expands as ${1}0
a0
To expand all positional parameters at once, you can use the special parameter @
or *
. Specifically, to pass all positional parameters intact to a utility or function, expand @
in double quotes:
$ set foo 'bar bar' baz
$ printf '[%s]\n' "$@"
[foo]
[bar bar]
[baz]
To get the number of positional parameters, use the special parameter #
:
$ set foo bar baz
$ echo "$#"
3
Parsing positional parameters
To parse positional parameters as options and arguments, use the getopts
built-in. This is useful for scripts that handle command-line options:
$ set -- -a arg1 -b arg2 operand1 operand2
$ while getopts a:b: opt; do
> case "$opt" in
> (a)
> echo "Option -a with argument: $OPTARG"
> ;;
> (b)
> echo "Option -b with argument: $OPTARG"
> ;;
> (*)
> echo "Unknown option: $opt"
> ;;
> esac
> done
Option -a with argument: arg1
Option -b with argument: arg2
$ shift $((OPTIND - 1)) # remove parsed options
$ echo "Remaining operands:" "$@"
Remaining operands: operand1 operand2
Commands
This section summarizes the syntax of commands in the shell language. Commands (in the broad sense) are instructions to the shell to perform actions such as running programs, changing the environment, or controlling execution flow. For details, see the linked sections below.
Whole scripts
A shell script consists of a sequence of lists separated by newlines. The shell reads and parses input line by line until it forms a complete list, executes that list, then continues to the next.
$ echo "Hello, World!"
Hello, World!
$ for fruit in apple banana cherry; do
> echo "I like $fruit"
> done
I like apple
I like banana
I like cherry
Lists
A list is a sequence of and-or lists separated by ;
or &
. Lists let you write multiple commands on one line or run commands asynchronously.
$ echo "Hello"; echo "World"
Hello
World
And-or lists
An and-or list is a sequence of pipelines separated by &&
or ||
. This lets you control execution flow based on the success or failure of previous commands.
$ test -f /nonexistent/file && echo "File exists" || echo "File does not exist"
File does not exist
Pipelines
A pipeline is a sequence of commands connected by |
, where the output of one command is passed as input to the next. Pipelines let you combine commands to process data in a stream.
You can prefix a pipeline with the !
reserved word to negate its exit status:
$ ! tail file.txt | grep TODO
TODO: Fix this issue
Commands
A command (in the narrow sense) is a pipeline component: a simple command, a compound command, or a function definition.
A simple command runs a utility or function, or assigns values to variables.
Compound commands control execution flow and include:
- Grouping commands: Group multiple commands to run as a unit, in the current shell or a subshell.
- If commands: Run commands conditionally based on exit status.
- Case commands: Run commands based on pattern matching a value.
- For loops: Iterate over a list, running commands for each item.
- While loops: Repeat commands while a condition is true.
- Until loops: Repeat commands until a condition becomes true.
A function definition creates a reusable block of code that can be invoked by name.
Simple commands
Simple commands are the basic building blocks of shell commands. They consist of command line words, assignments, and redirections.
Outline
Despite the name, simple commands can have complex behavior. This section summarizes the main aspects before covering details.
Most simple commands run a utility—a program that performs a specific task. A simple command that runs a utility contains a word specifying the utility name, followed by zero or more words as arguments:
$ echo "Hello!"
Hello!
The words are expanded before the utility runs.
A simple command can assign values to variables. To assign a value, join the variable name and value with an equals sign (=
) without spaces:
$ greeting="Hello!"
$ echo "$greeting"
Hello!
If assignments are used with utility-invoking words, they must appear before the utility name and they usually affect only that utility invocation:
$ TZ='America/Los_Angeles' date
Sun Jun 1 06:49:30 AM PDT 2025
$ TZ='Asia/Tokyo' date
Sun Jun 1 10:49:30 PM JST 2025
A simple command can also redirect input and output using redirection operators. For example, to redirect output to a file:
$ echo "Hello!" > output.txt
$ cat output.txt
Hello!
Redirections can appear anywhere in a simple command, but are typically placed at the end.
Syntax
The formal syntax of a simple command, written in Extended Backus-Naur Form (EBNF):
simple_command := normal_utility - reserved_word, [ normal_argument_part ] |
declaration_utility, [ declaration_argument_part ] |
assignment_part, [ utility_part ] |
"command", [ utility_part ];
utility_part := normal_utility, [ normal_argument_part ] |
declaration_utility, [ declaration_argument_part ] |
"command", [ utility_part ];
assignment_part := ( assignment_word | redirection ), [ assignment_part ];
assignment_word := ? a word that starts with a literal variable name
immediately followed by an unquoted equals sign and
optionally followed by a value part ?;
command_name := word - assignment_word;
declaration_utility := "export" | "readonly" | "typeset";
normal_utility := command_name - declaration_utility - "command";
normal_argument_part := ( word | redirection ), [ normal_argument_part ];
declaration_argument_part := ( assignment_word | word - assignment_word | redirection ),
[ declaration_argument_part ];
Key points:
- A simple command cannot start with a reserved word unless it is quoted.
- An assignment word must start with a non-empty variable name, but the value can be empty.
- Assignment words must come before a command name if present.
- To treat a word containing
=
as a command name, quote the variable name or the equals sign. - Redirections can appear anywhere in a simple command.
There must be no space around the equals sign in an assignment word. If you need spaces in the value, quote them:
$ greeting="Hello, world!"
$ echo "$greeting"
Hello, world!
The utility names export
, readonly
, and typeset
are declaration utilities; when used as a command name, following argument words are parsed as assignment words if possible, or as normal words otherwise. This affects how arguments are expanded. The utility name command
is also special; it delegates to the next word the determination of whether it is a declaration utility or a normal utility. (More utility names may be treated as declaration utilities in the future.)
Semantics
A simple command is executed by the shell in these steps:
- Command words (name and arguments) are expanded in order.
- If the command name is a declaration utility, argument words that look like assignment words are expanded as assignments; the rest are expanded as normal words. Otherwise, all arguments are expanded as normal words.
- Command words are expanded before assignments, so assignments do not affect command words in the same command.
- Expansions in assignments and redirections are not yet performed in this step.
- The result is a sequence of words called fields.
- If expansion fails, the error is reported and the command is aborted.
- Redirections are performed, in order.
- If there are any fields, redirections are processed in the current shell environment. If a redirection fails, the error is reported and the command is aborted.
- If there are no fields, redirections are processed in a subshell. In this case, redirections do not affect the current shell, and errors are reported but do not abort the command.
- Assignments are performed, in order.
- Each assignment value is expanded and assigned to the variable in the current shell environment. See Defining variables for details.
- Assigned variables are exported if there are any fields or if the
allexport
option is enabled. - If an assigned variable is read-only, the error is reported and the command is aborted.
- If there are any fields, the shell determines the target to execute based on the first field (the command name), as described in Command search below.
- The shell executes the target:
- If the target is an external utility, it is executed in a subshell with the fields as arguments. If the
execve
call used to execute the target fails withENOEXEC
, the shell tries to execute it as a script in a new shell process. - If the target is a built-in, it is executed in the current shell environment with the fields (except the first) as arguments.
- If the target is a function, it is executed in the current shell environment. When entering a function, positional parameters are set to the fields (except the first), and restored when the function returns.
- If no target is found, the shell reports an error.
- If there was no command name (the first field), nothing is executed.
- If the target is an external utility, it is executed in a subshell with the fields as arguments. If the
Assigned variables are removed unless the target was a special built-in or there were no fields after expansion, in which case the assignments persist.
Redirections are canceled unless the target was the exec
special built-in (or the command
built-in executing exec
), in which case the redirections persist.
Command search
Command search determines the target to execute based on the command name (the first field):
- If the command name contains a slash (
/
), it is treated as a pathname to an executable file target, regardless of whether the file exists or is executable. - If the command name is a special built-in (like
exec
orexit
), it is used as the target. - If the command name is a function, it is used as the target.
- If the command name is a built-in other than a substitutive built-in, it is used as the target.
- The shell searches for the command name in the directories listed in the
PATH
variable. The first matching executable regular file is a candidate target.- The value of
PATH
is treated as a sequence of pathnames separated by colons (:
). An empty pathname inPATH
refers to the current working directory. For example, in the simple commandPATH=/bin:/usr/bin: ls
, the shell searches forls
in/bin
, then/usr/bin
, and finally the current directory. - If
PATH
is an array, each element is a pathname to search.
- The value of
- If a candidate target is found:
- If the command name is a substitutive built-in (like
true
orpwd
), the built-in is used as the target. - Otherwise, the executable file is used as the target.
- If the command name is a substitutive built-in (like
- If no candidate target is found, the command search fails.
An executable file target is called an external utility.
POSIX allows caching pathnames found during command search, but yash-rs does not implement this yet.
Exit status
- If a target was executed, the exit status of the simple command is the exit status of the target.
- If there were no fields after expansion, the exit status is that of the last command substitution in the command, or zero if there were none.
- If the command was aborted due to an error before running a target, the exit status is non-zero. Specifically:
- 127 if command search failed
- 126 if the target was identified but could not be executed (e.g., unsupported file type or permission denied)
Pipelines
A pipeline is a sequence of commands connected by pipes (|
). The output of each command is passed as input to the next, allowing you to chain commands for more complex tasks.
Basic usage
The syntax for a pipeline is:
command1 | command2 | command3 …
For example, to list files and filter the output:
The |
operator may be followed by linebreaks for readability:
Line continuation can also be used to split pipelines across multiple lines:
If a pipeline contains only one command, the shell runs that command directly. For multiple commands, the shell creates a subshell for each and connects them with pipes. Each command’s standard output is connected to the next command’s standard input. The first command’s input and the last command’s output are not changed. All commands in the pipeline run concurrently.
The shell waits for all commands in the pipeline to finish before proceeding. The exit status of the pipeline is the exit status of the last command in the pipeline. (In the future, yash-rs may only wait for the last command to finish.)
Negation
You can negate a pipeline using the !
reserved word:
This runs the pipeline and negates its exit status: if the status is 0 (success), it becomes 1 (failure); if non-zero (failure), it becomes 0 (success). This is useful for inverting the result of a command in a conditional.
Negation applies to the pipeline as a whole, not to individual commands. To negate a specific command, use braces:
Since !
is a reserved word, it must appear as a separate word:
$ !ls | grep .zip
error: cannot execute external utility "!ls"
--> <stdin>:1:1
|
1 | !ls | grep .zip
| ^^^ utility not found
|
Compatibility
POSIX requires that a pipeline waits for the last command to finish before returning an exit status, and it is unspecified whether the shell waits for all commands in the pipeline to finish. yash-rs currently waits for all commands, but this may change in the future.
POSIX allows commands in a multi-command pipeline to be run in the current shell environment rather than in subshells. Korn shell and zsh run the last command in the current shell environment, while yash-rs runs all commands in subshells.
Some shells like Korn shell and mksh assign special meanings to the !
reserved word immediately followed by the (
operator. For maximum compatibility, !
and (
should be separated by a space.
Grouping
A grouping command combines multiple commands so they are treated as a single command. This is useful for running several commands together in a pipeline or an and-or list.
Braces
Commands grouped in braces { … }
run in the current shell environment.
$ { echo "Hello"; echo "World"; }
Hello
World
A group can span multiple lines:
$ {
> echo "Hello"
> echo "World"
> }
Hello
World
Since {
and }
are reserved words, they must appear as separate words. See examples in the Keywords section.
Braces are especially useful for treating several commands as a single unit in pipelines or and-or lists:
Subshells
Commands grouped in parentheses ( … )
run in a subshell—a copy of the current shell environment. Changes made in a subshell do not affect the parent shell.
$ greeting="Morning"
$ (greeting="Hello"; echo "$greeting")
Hello
$ echo "$greeting"
Morning
Since (
and )
are operators, they can be used without spaces.
Compatibility
Some shells treat two adjacent (
characters specially. For best compatibility, separate open parentheses with a space to nest subshells:
$ ( (echo "Hello"))
Hello
Exit status and conditionals
This section describes the exit status of commands and how to use it to control the flow of execution in the shell.
Exit status
The exit status of a command is a number that indicates an abstract result of the command’s execution. It is used to determine whether a command succeeded or failed.
The value of the exit status is a non-negative integer, typically in the range of 0 to 255. The exit status is stored in the special parameter ?
immediately after a command runs.
$ test -f /nonexistent/file
$ echo $? # the exit status of `test`
1
$ echo $? # the exit status of the previous `echo`
0
Before running a command, the initial value of ?
is 0.
While the exact meaning of exit status values is specific to each command, there are some common conventions:
- An exit status of 0 indicates success.
- A non-zero exit status indicates failure or an error condition. The specific value can provide additional information about the type of failure.
- Exit statuses in the range of 1 to 125 are generally used by commands to indicate various types of errors or conditions.
- Exit statuses 126 and greater are reserved by the shell for special purposes.
The following exit statuses are used by the shell to indicate specific conditions:
- Exit status 126 indicates that a command was found but could not be executed.
- Exit status 127 indicates that a command was not found.
- Exit status 128 indicates that the shell encountered an unrecoverable error reading a command.
- When a command is terminated by a signal, the exit status is 384 plus the signal number. For example, if a command is terminated by
SIGINT
(signal number 2), the exit status will be 386.
Compatibility of exit statuses
POSIX specifies exit statuses for certain conditions, but there are still many conditions for which POSIX does not define exact exit statuses. Different shells and commands may use different exit statuses for the same conditions, so it’s important to check the documentation of the specific command you are using. Specifically, the exit status of a command terminated by a signal may vary between shells as POSIX only specifies that the exit status must be greater than 128.
Yash-rs internally handles exit statuses as 32-bit signed integers, but receives only the lower 8 bits from child processes running a subshell or external utility. This means that exit statuses that are not in the range of 0 to 255 are truncated to fit into this range. For example, an exit status of 256 becomes 0, and an exit status of 1000 becomes 232.
Exit status of the shell
When exiting a shell, the exit status of the shell itself is determined by the exit status of the last command executed in the shell. If no commands have been executed, the exit status is 0.
If the exit status of the last command indicates that the command was terminated by a signal, the shell sends the same signal to itself to terminate. The parent process (which may or may not be a shell) will observe that the shell process was terminated by a signal, allowing it to handle the termination appropriately. Specifically, if the parent process is also yash, the value of the special parameter ?
in the child shell process is reproduced in the parent shell process without modification.
This signal-passing behavior is not supported by all shells; in shells that do not support it, the lower 8 bits of the exit status are passed to the parent process instead. The parent process is likely to interpret this as an ordinary exit status, which may not accurately reflect the original command’s termination by a signal.
The true
and false
utilities
The true
and false
utilities simply return an exit status of 0 and 1, respectively. They are often used as placeholders in conditional statements or loops. See the examples in the And-or lists section below.
Inverting exit status
You can invert a command’s exit status using the !
reserved word. This treats a successful command as a failure, and vice versa.
$ test -f /nonexistent/file
$ echo $?
1
$ ! test -f /nonexistent/file
$ echo $?
0
See Negation for more details.
And-or lists
An and-or list is a sequence of commands that are executed based on the success or failure of previous commands. It allows you to control the flow of execution based on the exit status of commands.
An and-or list consists of commands separated by &&
(and) or ||
(or) operators. The &&
operator executes the next command only if the previous command succeeded (exit status 0), while the ||
operator executes the next command only if the previous command failed (non-zero exit status).
$ test -f /nonexistent/file && echo "File exists" || echo "File does not exist"
File does not exist
Unlike many other programming languages, the &&
and ||
operators have equal precedence with left associativity in the shell language:
$ false && echo foo || echo bar
bar
$ { false && echo foo; } || echo bar
bar
$ false && { echo foo || echo bar; }
$ true || echo foo && echo bar
bar
$ { true || echo foo; } && echo bar
bar
$ true || { echo foo && echo bar; }
The &&
and ||
operators can be followed by linebreaks for readability:
$ test -f /nonexistent/file &&
> echo "File exists" ||
> echo "File does not exist"
File does not exist
Line continuation can also be used to split and-or lists across multiple lines:
$ test -f /nonexistent/file \
> && echo "File exists" \
> || echo "File does not exist"
File does not exist
The exit status of an and-or list is the exit status of the last command executed in the list.
If commands
An if command is a conditional command that executes a block of commands based on the exit status of a test command. It allows you to perform different actions depending on whether a condition is true or false.
The minimal form of an if command uses the if
, then
, and fi
reserved words that surround commands:
For readability, each reserved word can be on a separate line:
You can also use the elif
reserved word to add additional conditions:
$ if [ -f /dev/tty ]; then
> echo "/dev/tty is a regular file"
> elif [ -d /dev/tty ]; then
> echo "/dev/tty is a directory"
> elif [ -c /dev/tty ]; then
> echo "/dev/tty is a character device"
> fi
/dev/tty is a character device
The else
reserved word can be used to provide a default action if none of the conditions are met:
$ file=/nonexistent/file
$ if [ -e "$file" ]; then
> echo "$file exists"
> elif [ -L "$file" ]; then
> echo "$file is a symbolic link to a nonexistent file"
> else
> echo "$file does not exist"
> fi
/nonexistent/file does not exist
The exit status of an if command is the exit status of the last command executed in the then
or else
clause. If no condition is met and there is no else
clause, the exit status is 0 (success).
For repeating commands depending on a condition, see While and until loops.
Exiting on errors
By default, the shell continues running commands even if one fails (returns a non-zero exit status). This can cause later commands to run when they shouldn’t. If you enable the errexit
shell option, the shell will exit immediately when any command fails, stopping further execution.
$ set -o errexit # or: set -e
$ test -e /dev/null
$ echo "Ok, continuing..."
Ok, continuing...
$ test -e /nonexistent/file
$ echo "This will not be printed"
In this example, after test -e /nonexistent/file
fails, the shell exits right away, so you won’t see any more prompts or output.
The errexit
option only applies to the result of pipelines. It is ignored in these cases:
- When the pipeline is negated with the
!
reserved word. - When the pipeline is the left side of an
&&
or||
operator. - When the pipeline is part of the condition in an
if
command or awhile
oruntil
loop.
Although errexit
does not catch every error, it is recommended for scripts to avoid unexpected results from failed commands. To skip errexit
for a specific command, append && true
:
$ set -o errexit
$ test -e /nonexistent/file && true
$ echo "The exit status was $?"
The exit status was 1
Pattern-based branching
The case command performs pattern matching on a value and executes commands for the first matching pattern. This is useful for branching logic based on specific values or patterns.
Case command basics
A case
command begins with the case
reserved word, followed by the value to match. After the in
reserved word, each branch specifies a pattern in parentheses, followed by a block of commands. Each block ends with ;;
, and the command ends with esac
.
For example, this command matches the value of foo
and runs the corresponding commands:
$ case foo in
> (foo)
> echo "Matched foo"
> ;;
> (bar)
> echo "Matched bar"
> ;;
> esac
Matched foo
Patterns
Patterns can use wildcards and bracket expressions for flexible matching. For example, to match any string starting with f
:
$ case foo in
> (f*)
> echo "Starts with f"
> ;;
> (b*)
> echo "Starts with b"
> ;;
> esac
Starts with f
To match multiple patterns, separate them with a pipe |
:
$ case foo in
> (foo|bar)
> echo "Matched foo or bar"
> ;;
> esac
Matched foo or bar
Word expansion
Both the value and patterns undergo word expansion:
- Tilde expansion
- Parameter expansion
- Command substitution
- Arithmetic expansion
- Quote removal (applies only to the value)
$ value="Hello" pattern="[Hh]*"
$ case $value in
> ($pattern)
> echo "Matched pattern"
> ;;
> esac
Matched pattern
The value is always expanded first. Patterns are expanded only when the shell needs to match them. Once a pattern matches, remaining patterns are not expanded.
Quote special characters in values or patterns to avoid unwanted expansion or matching:
$ case ? in
> ('?')
> echo "Matched a single question mark"
> ;;
> (?)
> echo "Matched any single character"
> ;;
> esac
Matched a single question mark
Continuing to the next branch
Instead of ;;
, use ;&
to continue execution with the next branch, regardless of whether its pattern matches. This allows multiple branches to run in sequence:
$ case foo in
> (foo)
> echo "Matched foo"
> ;&
> (bar)
> echo "Matched bar, or continued from foo"
> ;;
> (baz)
> echo "Matched baz"
> ;;
> esac
Matched foo
Matched bar, or continued from foo
Use ;;&
or ;|
to continue pattern matching in subsequent branches, so commands in multiple matching branches can run:
$ case foo in
> (foo)
> echo "Matched foo"
> ;;&
> (bar)
> echo "Matched bar"
> ;;
> (f*)
> echo "Matched any string starting with f"
> ;;
> esac
Matched foo
Matched any string starting with f
The ;;&
and ;|
terminators are extensions to POSIX. yash-rs supports both, but other shells may support only one or neither.
Miscellaneous
If no branch matches, or there are no branches, the shell skips the case
command without error.
Use *
as a catch-all pattern:
$ case foo in
> (bar)
> echo "Matched bar"
> ;;
> (*)
> echo "Matched anything else"
> ;;
> esac
Matched anything else
Use ''
or ""
as an empty value or pattern:
$ case "" in
> ('')
> echo "Matched empty string"
> ;;
> esac
Matched empty string
The opening parenthesis (
can be omitted if the first pattern is not literally esac
, but parentheses are recommended for clarity:
$ case foo in
> foo)
> echo "Matched foo"
> ;;
> esac
Matched foo
Branches can have empty command blocks:
$ case bar in
> (foo)
> ;;
> (bar)
> echo "Matched bar"
> ;;
> esac
Matched bar
The ;;
terminator can be omitted for the last branch:
$ case foo in
> (foo)
> echo "Matched foo"
> ;;
> (bar)
> echo "Matched bar"
> esac
Matched foo
Exit status
The exit status of case
is that of the last command executed in the last executed branch. If the last executed branch has no commands, or no pattern matches, the exit status is 0.
Formal syntax
The formal syntax of the case
command, in Extended Backus-Naur Form (EBNF):
case_command := "case", word, { newline }, "in", { newline },
{ branch }, [ last_branch ], "esac";
newline := "\n";
branch := pattern_list, branch_body, terminator, { newline };
last_branch := pattern_list, branch_body;
pattern_list := "(", word, { "|" , word }, ")"
| (word - "esac"), { "|" , word }, ")";
branch_body := { newline }, [ list, [ newline, branch_body ] ];
terminator := ";;" | ";&" | ";;&" | ";|";
Loops
Loops repeatedly execute a sequence of commands, either by iterating over a list or while a condition holds. They are useful for automating repetitive tasks or processing multiple items.
For loops
A for
loop iterates over a list of strings, executing a block of commands for each string. In each iteration, the string is assigned to a variable for use in the commands.
$ for user in alice bob charlie; do
> echo "Hello, $user!"
> done
Hello, alice!
Hello, bob!
Hello, charlie!
The word after for
is the loop variable, assigned to each string in the list after in
. The do
reserved word starts the command block, and done
ends the loop.
The semicolon after the list is optional if do
is on a new line:
$ for user in alice bob charlie
> do
> echo "Hello, $user!"
> done
Hello, alice!
Hello, bob!
Hello, charlie!
The in
reserved word can also be on a separate line:
$ for user
> in alice bob charlie; do
> echo "Hello, $user!"
> done
Hello, alice!
Hello, bob!
Hello, charlie!
Word expansion is performed on the list:
If the list is empty, the loop does not run:
$ for user in; do
> echo "Hello, $user!"
> done
If in
and the list are omitted, the loop iterates over the positional parameters as if in "$@"
were specified:
$ set alice bob charlie
$ for user do
> echo "Hello, $user!"
> done
Hello, alice!
Hello, bob!
Hello, charlie!
The exit status of a for
loop is the exit status of the last command run in the loop, or 0 if the loop does not run.
While and until loops
A while
loop executes commands as long as a condition is true. An until
loop is similar, but continues until the condition becomes true. The do
reserved word separates the condition from the loop body, and done
ends the loop.
$ count=1
$ while [ $count -le 5 ]; do
> echo "Count: $count"
> count=$((count + 1))
> done
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
$ count=1
$ until [ $count -gt 3 ]; do
> echo "Count: $count"
> count=$((count + 1))
> done
Count: 1
Count: 2
Count: 3
See Exit status and conditionals for details on how exit status affects loop conditions.
The exit status of a while
or until
loop is that of the last command run in the loop body, or 0 if the loop body does not run. Note that the exit status of the condition does not affect the exit status of the loop.
Break and continue
The break
utility exits the current loop. The continue
utility skips to the next iteration.
$ for i in 1 2 3 4 5; do
> if [ $i -eq 3 ]; then
> echo "Breaking at $i"
> break
> fi
> echo "Iteration $i"
> done
Iteration 1
Iteration 2
Breaking at 3
$ for i in 1 2 3 4 5; do
> if [ $i -eq 3 ]; then
> echo "Skipping $i"
> continue
> fi
> echo "Iteration $i"
> done
Iteration 1
Iteration 2
Skipping 3
Iteration 4
Iteration 5
By default, break
and continue
affect the innermost loop. You can specify a numeric operand to affect the n’th outer loop:
$ for i in 1 2; do
> for j in a b c; do
> if [ "$j" = "b" ]; then
> echo "Breaking outer loop at $i, $j"
> break 2
> fi
> echo "Inner loop: $i, $j"
> done
> done
Inner loop: 1, a
Breaking outer loop at 1, b
Lists and asynchronous commands
A list is a sequence of and-or lists separated by semicolons (;
) or ampersands (&
). Lists let you write multiple commands on a single line, and control whether they run synchronously or asynchronously.
Synchronous commands
When an and-or list is separated by a semicolon (;
), it runs synchronously: the shell waits for the command to finish before running the next one.
$ echo "First command"; echo "Second command";
First command
Second command
The semicolon can be omitted after the last command:
$ echo "First command"; echo "Second command"
First command
Second command
Asynchronous commands
When an and-or list is separated by an ampersand (&
), it runs asynchronously: the shell does not wait for the command to finish before running the next one.
$ echo "First async command" & echo "Second async command" & echo "Synchronous command"
Second async command
Synchronous command
First async command
Here, the commands run in parallel, so their output may appear in any order.
Each asynchronous command runs in a subshell. Changes to the shell environment (like variable assignments) made in an asynchronous command do not affect the parent shell. In an interactive shell, starting an asynchronous command prints its job number and process ID:
$ echo "Async command" &
[1] 12345
Async command
Because the shell does not wait for asynchronous commands, they may keep running while the shell reads new commands or even after the shell exits. To wait for them to finish, use the wait
utility (see below).
Input redirection
By default, an asynchronous command’s standard input is redirected to /dev/null
to prevent it from interfering with synchronous commands that read from standard input. This does not apply in job-controlling shells, where background reads from the terminal typically cause the job to be stopped by SIGTTIN
.
$ echo Input | {
> cat &
> read -r line
> echo "Read line: $line"
> }
Read line: Input
In this example, the asynchronous cat
reads from /dev/null
, while read
reads from standard input.
The !
special parameter
The !
special parameter gives the process ID of the last asynchronous command started in the shell. This is useful for tracking or waiting for background jobs.
The wait
utility
The wait
utility waits for asynchronous commands to finish. With no operands, it waits for all asynchronous commands started in the current shell. With operands, it waits for the specified process IDs.
Here, the shell starts an asynchronous command that writes to a file. wait $!
waits for it to finish before reading the file.
Job control
In yash-rs, all asynchronous commands start as background jobs. If the monitor
shell option is enabled, you can use job control commands to manage these jobs. See the job control documentation for details.
Functions
A function is a named block of code you can call by name. Functions let you organize and reuse code in scripts and interactive sessions.
$ greet() {
> echo "Hello, $1!"
> }
$ greet Alice
Hello, Alice!
$ greet Bob
Hello, Bob!
Defining functions
To define a function, write the function name followed by parentheses ()
and a compound command as the body:
$ greet() {
> echo "Hello, $1!"
> }
You can also write the parentheses separately, and put the body on the next line:
$ cleanup ( )
> if [ -d /tmp/myapp ]; then
> rm -rf /tmp/myapp
> fi
Function names are case-sensitive and do not share a namespace with variables.
By POSIX.1-2024, function names must use ASCII letters, digits, and underscores, and not start with a digit. As an extension, yash-rs allows any word as a function name. The function name is expanded when defined:
$ "$(echo foo)"() { echo "This function is named foo."; }
$ foo
This function is named foo.
A function is defined when the definition command is executed, not when parsed. For example, greet
is only defined if the current year is 2001:
$ if [ "$(date +%Y)" = 2001 ]; then
> greet() { echo "Happy millennium!"; }
> fi
$ greet
error: cannot execute external utility "greet"
--> <stdin>:4:1
|
4 | greet
| ^^^^^ utility not found
|
Redirections in a function definition apply when the function is called, not when it is defined:
$ dumb() { echo "Hello, $1!"; } > /dev/null
$ dumb Alice
You can redefine a function by defining it again with the same name. The new definition replaces the old one.
The exit status of a function definition is 0 if successful. It is non-zero if the function name expansion fails or if a read-only function with the same name exists.
Defining functions with the function
reserved word is not POSIX and is not yet implemented in yash-rs.
Read-only functions
Make a function read-only with the typeset
built-in. Read-only functions cannot be redefined or removed.
$ greet() { echo "Hello, World!"; }
$ typeset -fr greet
$ greet() { echo "Hello again!"; }
error: cannot redefine read-only function `greet`
--> <stdin>:3:1
|
3 | greet() { echo "Hello again!"; }
| ^^^^^ failed function redefinition
|
::: <stdin>:1:1
|
1 | greet() { echo "Hello, World!"; }
| ----- info: existing function was defined here
|
::: <stdin>:2:13
|
2 | typeset -fr greet
| ----- info: existing function was made read-only here
|
The readonly
built-in does not yet support making functions read-only in yash-rs.
Showing function definitions
To display the definition of a function, use the typeset
built-in with the -f
and -p
options:
$ greet() {
> echo "Hello, World!"
> }
$ typeset -fp greet
greet() { echo "Hello, World!"; }
Executing functions
To run a function, specify its name as a command name in a simple command.
$ greet() { echo "Hello, World!"; }
$ greet
Hello, World!
A function cannot be executed as a simple command if its name matches a special built-in or contains a slash. (See command search.)
Function parameters
Fields after the function name are passed as positional parameters. The original positional parameters are restored when the function returns.
$ foo() {
> echo "The function received $# arguments, which are: $*"
> }
$ set alice bob charlie
$ echo "Positional parameters before calling foo: $*"
Positional parameters before calling foo: alice bob charlie
$ foo andrea barbie cindy
The function received 3 arguments, which are: andrea barbie cindy
$ echo "Positional parameters after calling foo: $*"
Positional parameters after calling foo: alice bob charlie
Returning from functions
A function runs until the end of its body or until the return
built-in is called. return
can exit the function early and set the exit status.
$ is_positive() {
> if [ "$1" -le 0 ]; then
> echo "$1 is not positive."
> return 1
> fi
> echo "$1 is positive."
> return
> }
$ is_positive 5
5 is positive.
$ echo "Exit status: $?"
Exit status: 0
$ is_positive -3
-3 is not positive.
$ echo "Exit status: $?"
Exit status: 1
Removing functions
Remove a function with the unset
built-in and the -f
option:
$ greet() { echo "Hello, World!"; }
$ unset -f greet
$ greet
error: cannot execute external utility "greet"
--> <stdin>:3:1
|
3 | greet
| ^^^^^ utility not found
|
Replacing existing utilities
You can override existing utilities (except special built-ins) by defining a function with the same name. This is useful for customizing or extending utility behavior. To run the original utility from within your function, use the command
built-in:
$ ls() {
> command ls --color=auto "$@"
> }
$ ls
Documents Downloads Music Pictures Videos
Related topics
See Local variables for temporary variables that are removed when the function returns.
See Aliases and functions for comparison between aliases and functions.
Aliases
Alias substitution replaces part of a command with a predefined string while parsing the command. Aliases are useful for creating shortcuts or customizing command behavior.
Basic usage
Define an alias with the alias
built-in. When the first word in a simple command matches an alias, the shell replaces it with the alias definition before parsing the rest of the command.
Aliases can include multiple words, redirections, and delimiters. They can reference other aliases, which are expanded recursively.
$ alias dumb='> /dev/null'
$ dumb echo "Hello, World!"
Here, the second line becomes > /dev/null echo "Hello, World!"
, so nothing is printed.
$ alias 2001='test "$(date +%Y)" = 2001 &&'
$ 2001 echo "Happy millennium!"
This expands to test "$(date +%Y)" = 2001 && echo "Happy millennium!"
, printing the message if the year is 2001.
Alias names
By POSIX.1-2024, alias names can use ASCII letters, digits, and !
, %
, ,
, -
, @
, _
. Yash-rs allows any literal word as an alias name (no quotes or expansions). Alias names are case-sensitive.
Recursion
Aliases can reference other aliases, creating a chain of substitutions. The shell expands aliases recursively until no more aliases are found. An alias is not substituted in the result of its own expansion, preventing infinite loops.
$ alias ll='ls -l'
$ alias l='ll -h'
$ l
total 40K
drwxr-xr-x 6 alice users 4.0K Jun 22 11:36 book
-rw-r--r-- 1 alice users 397 May 14 21:57 book.toml
-rwxr-xr-x 1 alice users 4.7K Jun 16 22:22 doctest.sh
-rw-r--r-- 1 alice users 20K May 28 00:04 LICENSE
drwxr-xr-x 3 alice users 4.0K May 31 02:11 src
$ alias ls='ls -F'
$ ls
book/ book.toml doctest.sh* LICENSE src/
Continued substitution
If an alias definition ends with a blank, the next word is also checked for alias substitution, even if it is not the first word of the command. This is useful for utilities that take another command as an argument.
$ alias greet='echo Hello,'
$ alias time='time -p '
$ time greet World
Hello, World
real 0.00
user 0.00
sys 0.00
If the time
alias does not end with a blank, the next word is not substituted:
$ alias greet='echo Hello,'
$ alias time='time -p'
$ time greet World
time: cannot run greet: No such file or directory
real 0.01
user 0.00
sys 0.00
Note: In yash and many other shells, this behavior only applies if the next word is a whole word, not a part of a word. In the following example, a
follows a blank resulting from alias substitution for q
, but it is inside quotes, so it is not substituted:
$ alias echo='echo ' q="'[ " a=b
$ echo q a ]'
[ a ]
Miscellaneous
To prevent alias substitution for a word, quote it.
Aliases become effective after the defining command is executed. Since commands are parsed and executed line by line, aliases defined in the current line are not available in the same line.
Remove an alias with the unalias
built-in.
Aliases and functions
Functions are similar to aliases in that both let you define names for command sequences. Functions are better for complex logic—they can take parameters, use local variables, and include conditionals, loops, and other compound commands. Aliases are better for syntactic manipulation, such as inserting a pipeline or redirection, because they are expanded as the command is parsed.
Redirections
Redirections control where command input and output go. They let you save output to files, read input from files, or otherwise manipulate how commands perform I/O operations.
What are file descriptors?
A file descriptor is a non-negative integer that identifies an open file or I/O channel in a process. When a process opens a file, the operating system assigns it a file descriptor, which the process uses to read from or write to that file.
The first three file descriptors have standard meanings:
- 0: Standard input – the source of input data
- 1: Standard output – the destination for command results
- 2: Standard error – the destination for error messages and diagnostics
By default, these are connected to the terminal, but they can be redirected to files or other destinations.
Redirection syntax
A redirection consists of a special operator followed by a target (such as a file or file descriptor). Redirections can appear anywhere in a simple command, or after the body of a compound command.
For example, the >
operator redirects standard output to a file:
The <
operator redirects standard input from a file:
(The read
built-in reads a line from standard input into a variable.)
Redirection operators
Yash-rs supports these redirection operators:
-
<
(file): Redirects standard input from a file. -
>
(file): Redirects standard output to a file.- If the
clobber
shell option is set (default),>
behaves like>|
. - If
clobber
is not set,>
fails if the file exists and is a regular file or a symlink to a non-existent file. Otherwise, it creates a new regular file or opens the existing non-regular one. This is useful for preventing accidental overwriting of files.
- If the
-
>|
(file): Redirects standard output to a file, overwriting it if it exists.- Always overwrites existing files, regardless of the
clobber
option. - Truncates the file if it exists, or creates it if not.
- Always overwrites existing files, regardless of the
-
>>
(file): Redirects standard output to a file, appending to it if it exists.- Appends to the file if it exists, or creates it if not.
-
<>
(file): Opens a file for both reading and writing.- Opens the file if it exists, or creates it if not.
-
<&
: Duplicates or closes standard input, depending on the target word:- If the word is a file descriptor number, standard input becomes a copy of that descriptor (which must be open for reading).
- If the word is
-
, standard input is closed. No error is reported if it is already closed.- If standard input is closed, commands that read from it will fail. To provide empty input, use
< /dev/null
instead.
- If standard input is closed, commands that read from it will fail. To provide empty input, use
-
>&
: Duplicates or closes standard output, depending on the target word:-
If the word is a file descriptor number, standard output becomes a copy of that descriptor (which must be open for writing).
-
If the word is
-
, standard output is closed. No error is reported if it is already closed.- If standard output is closed, commands that write to it will fail. To discard output, use
> /dev/null
instead.
- If standard output is closed, commands that write to it will fail. To discard output, use
-
For example,
>&2
redirects standard output to standard error:$ echo "error: please specify a user" >&2 error: please specify a user
-
-
<<
(delimiter): Opens a here-document. -
<<-
(delimiter): Opens a here-document with automatic removal of leading tabs.
Specifying file descriptors
Redirection operators starting with <
default to standard input; those starting with >
default to standard output. To redirect a different descriptor, prefix the operator with its number (no space):
For example, to redirect standard error to a file:
$ grep "pattern" input.txt 2> error.log
If you insert a space, the number is treated as a command argument, not a file descriptor:
Some shells allow using a variable name in braces {}
instead of a file descriptor. For file-opening redirections, the shell allocates a new descriptor and assigns it to the variable. For descriptor-copying redirections, the shell uses the descriptor stored in the variable.
This is not yet implemented in yash-rs, but would look like:
$ exec {fd}> output.txt
$ echo "Hello, World!" >&$fd
$ cat output.txt
Hello, World!
Target word expansion
Except for here-documents, the word after a redirection operator is expanded before use. The following expansions are performed:
Pathname expansion may be supported in the future.
Persistent redirections
By default, redirections only apply to the command they are attached to. To make a redirection persist across multiple commands, use the exec
built-in without arguments:
You can use the >&
operator to save a file descriptor before redirecting it, and restore it later:
Semantic details
Applying a redirection to a compound command is different from applying it to a simple command inside the compound command. Each use of <
opens a new file descriptor at the start of the file. If the redirection is inside a loop, the file descriptor is reset to the beginning on each iteration:
$ printf 'One\nTwo\nThree\n' > input.txt
$ while read -r line < input.txt; do
> echo "Read: $line"
> done
Read: One
Read: One
Read: One
Read: One
Read: One
(The loop never ends…)
If a command has multiple redirections, they are applied in order. If several affect the same file descriptor, the last one takes effect:
Note the difference between > /dev/null 2>&1
and 2>&1 > /dev/null
:
> /dev/null 2>&1
redirects both standard output and standard error to/dev/null
, discarding both.2>&1 > /dev/null
redirects standard error to standard output, and then redirects standard output to/dev/null
. This means standard error is still printed to the terminal (or wherever standard output was originally directed).
$ cat /nonexistent/file > /dev/null 2>&1
$ cat /nonexistent/file 2>&1 > /dev/null
cat: /nonexistent/file: No such file or directory
Here-documents
Here-documents are a type of redirection that lets you provide multi-line input directly within a script or command line. They are useful for supplying input to commands or scripts without creating a separate file.
Syntax
A here-document starts with the <<
operator followed by a delimiter word. After the next newline operator, the shell reads lines until it finds a line containing only the delimiter (with no trailing blanks). The lines read become the standard input for the command.
$ cat <<EOF
> Hello,
> World!
> EOF
Hello,
World!
In this example, EOF
is the delimiter. The cat
utility receives the lines between <<EOF
and the final EOF
.
POSIX allows any word as a delimiter, but for portability, use only alphanumeric characters and underscores. Delimiters with special characters or whitespace can cause unexpected behavior, especially if not quoted. See also Quoting the delimiter and expanding the content below for the effects of quoting the delimiter.
Multiple here-documents
You can use multiple here-document operators in a single command or across multiple commands on the same line. After the next newline, the shell reads lines for each here-document in order, stopping at each delimiter.
$ cat <<EOF; cat <<END <<EOF
> Hello,
> EOF
> This is the first here-document for the second command.
> END
> World!
> EOF
Hello,
World!
Here-documents in command substitution
When using a here-document inside command substitution, the content must be included within the substitution syntax:
$ echo $(cat <<EOF
> Hello,
> World!
> EOF
> )
Hello, World!
It is not supported to place the here-document content outside the command substitution, as in:
echo $(cat <<EOF)
Hello,
World!
EOF
Automatic removal of leading tabs
If you use <<-
instead of <<
, all leading tab characters are removed from the here-document content and the delimiter line. This allows you to indent here-documents in your scripts for readability, without affecting the output.
$ cat <<-EOF
> Hello,
> World!
> EOF
Hello,
World!
Note: Only leading tabs are removed, not spaces.
Quoting the delimiter and expanding the content
If the delimiter after the redirection operator is quoted, quote removal is performed on the delimiter, and the result is used to find the end of the here-document. In this case, the content is not subject to any expansions and is treated literally.
$ user="Alice"
$ cat <<'EOF'
> Hello, $user!
> 1 + 1 = $((1 + 1)).
> EOF
Hello, $user!
1 + 1 = $((1 + 1)).
If the delimiter is not quoted, the following are handled in the here-document content when the redirection is performed:
- Backslash escapes work only before
$
,`
, and\
. Other backslashes are literal. - Line continuations are removed.
- Parameter expansion, command substitution, and arithmetic expansion are performed.
$ user="Alice"
$ cat <<EOF
> Hello, $user!
> 1 + 1 = $((1 + 1)).
> EOF
Hello, Alice!
1 + 1 = 2.
Single and double quotes in the here-document content are treated literally.
$ user="Alice"
$ cat <<EOF
> Hello, '$user'!
> EOF
Hello, 'Alice'!
Shell environment and subshells
The shell execution environment is the set of state the shell maintains to control its behavior. It consists of:
- File descriptors
- Working directory
- File creation mask
- Resource limits
- Variables
- Positional parameters
- Values of these special parameters:
?
: exit status of the last command$
: process ID of the shell!
: process ID of the last asynchronous command0
: name of the shell or script
- Functions
- Aliases
- Shell options
- Traps
- Job list
Subshells
A subshell is a separate environment created as a copy of the current shell environment. Changes in a subshell do not affect the parent shell. A subshell starts with the same state as the parent, except that traps with custom commands are reset to default behavior.
Create a subshell using parentheses. Subshells are also created implicitly when running an external utility, a command substitution, an asynchronous command, or a multi-command pipeline.
Subshells of an interactive shell are not themselves interactive, even if the interactive
option is set.
Yash-rs currently implements subshells using the fork
system call, which creates a new process. This may change in the future for greater efficiency.
External utilities run by the shell inherit the following from the shell environment:
- File descriptors
- Working directory
- File creation mask
- Resource limits
- Environment variables
- Traps, except those with a custom command
Shell options
Shell options control the behavior of the shell. You can enable (set) or disable (unset) them using command line arguments at startup or with the set
built-in during a shell session.
Enabling and disabling options
You can specify shell options as command line arguments when starting the shell, or with the set
built-in. In yash, all options have a long name, and some also have a short name.
Options set at startup take effect before the shell reads and executes commands. Options set with set
affect the current shell session. Some options are only available at startup; others can be changed at any time. The syntax is the same in both cases.
Long option names
Long options start with --
. For example, to enable the allexport
option at startup:
yash3 --allexport
You can also specify long options with the -o
option:
yash3 -o allexport
Only alphanumeric characters matter in long option names, and they are case-insensitive. For example, --all-export
, --ALLEXPORT
, and ---All*Ex!PorT
all enable allexport
.
Long option names can be abbreviated if unambiguous. For example, --cl
enables clobber
:
$ set --cl
$ set --c
error: ambiguous option name "--c"
--> <stdin>:2:5
|
2 | set --c
| --- ^^^ --c
| |
| info: executing the set built-in
|
Note: Future versions may add more options, so abbreviations that work now may become ambiguous later. For forward compatibility, use full option names.
To disable a long option, prepend no
to the name:
yash3 --noallexport
Or use ++
instead of --
:
yash3 ++allexport
Or use +o
instead of -o
:
yash3 +o allexport
If you use both +
and no
, it is a double negation and enables the option:
yash3 +o noallexport
Short option names
Some options have short names, specified as a single character. For example, to enable allexport
with its short name:
yash3 -a
To disable it:
yash3 +a
You can combine multiple short options in one argument:
yash3 -aex
Some short options negate long options. For example, -C
is the same as --noclobber
(disables clobber
). To enable clobber
with its short name, use +C
.
Viewing current options
To see current shell options, use set -o
with no arguments:
$ set -o
allexport off
clobber on
cmdline off
errexit off
exec on
glob on
hashondefinition off
ignoreeof off
interactive off
log on
login off
monitor off
notify off
posixlycorrect off
stdin on
unset on
verbose off
vi off
xtrace off
set +o
prints options in a format that can be used to restore them:
$ set +o
set +o allexport
set -o clobber
#set +o cmdline
set +o errexit
set -o exec
set -o glob
set +o hashondefinition
set +o ignoreeof
#set +o interactive
set -o log
set +o login
set +o monitor
set +o notify
set +o posixlycorrect
#set -o stdin
set -o unset
set +o verbose
set +o vi
set +o xtrace
$ set +o allexport
$ savedoptions=$(set +o)
$ set -o allexport
$ eval "$savedoptions"
$ set -o | grep allexport
allexport off
The -
special parameter contains the currently set short options. For example, if -i
and -m
are set, the value of -
is im
. Options without a short name are not included. Short options that negate long options are included when the long option is unset.
$ set -a -o noclobber
$ echo "$-"
aCs
Option list
Below is a list of all shell options in yash-rs, with their long and short names, and a brief description. Unless noted, all options are disabled by default.
-
allexport
(-a
): If set, all variables assigned in the shell are exported. -
clobber
(+C
): If set (default), the>
redirection operator overwrites existing files. If unset,>
fails if the file exists. The>|
operator always overwrites files. -
cmdline
(-c
): If set, the shell executes the first operand from the command line as a command. Mutually exclusive withstdin
, and only settable at startup. -
errexit
(-e
): If set, the shell exits if a command fails. Useful for scripts to stop on errors. See Exiting on errors for details. -
exec
(+n
): If set (default), the shell executes commands. If unset, it only parses commands (useful for syntax checking).- Once unset, it cannot be set again in the same session.
- In interactive shells, this option is ignored and commands are always executed.
-
glob
(+f
): If set (default), the shell performs pathname expansion on words containing metacharacters. If unset, pathname expansion is skipped. -
hashondefinition
(-h
): Deprecated and has no effect. Remains for compatibility.- The short name
-h
is currently a synonym for--hashondefinition
, but this may change. - Many shells implement
-h
differently, so behavior may vary.
- The short name
-
ignoreeof
: If set, the shell ignores end-of-file (usuallyCtrl+D
) and does not exit. See Preventing accidental exits.- Only takes effect if the shell is interactive and input is a terminal.
-
interactive
(-i
): If set, the shell is interactive.- Enabled on startup if
stdin
is enabled and standard input and error are terminals.
- Enabled on startup if
-
log
: Deprecated and has no effect. Remains for compatibility. -
login
(-l
): If set, the shell behaves as a login shell. Only settable at startup.- ⚠️ Currently has no effect in yash-rs. In the future, login shells will read extra initialization files.
-
monitor
(-m
): If set, the shell performs job control (allows managing background and foreground jobs).- Enabled by default in interactive shells.
-
notify
(-b
): If set, the shell notifies you of background job completions and suspensions as soon as they occur. If unset, notifications are delayed until the next prompt. See Job status change notifications for details.- ⚠️ Currently has no effect in yash-rs. In the future, it will enable immediate notifications for background jobs.
- Only takes effect if
interactive
andmonitor
are enabled.
-
pipefail
: If set, the shell returns the exit status of the last command in a pipeline that failed, instead of the last command’s exit status. See Catching errors across pipeline components for details.- ⚠️ Not yet implemented in yash-rs.
-
posixlycorrect
: If set, the shell behaves as POSIX-compliant as possible. Useful for portable scripts.- Enabled on startup if the shell is started as
sh
. - When unset, yash-rs may deviate from POSIX in some areas.
- Enabled on startup if the shell is started as
-
stdin
(-s
): If set, the shell reads commands from standard input. Mutually exclusive withcmdline
, and only settable at startup.- Enabled if
cmdline
is not set and the shell is started with no operands.
- Enabled if
-
unset
(+u
): If set (default), the shell expands unset variables to an empty string. If unset, expanding an unset variable raises an error. See Unset parameters (in parameter expansion) and Variables (in arithmetic expression) for details. -
verbose
(-v
): If set, the shell prints each command before executing it. See Reviewing command input for details. -
vi
: If set, the shell uses vi-style keybindings for command line editing.- ⚠️ Currently has no effect in yash-rs. In the future, it will enable vi-style editing in interactive shells.
-
xtrace
(-x
): If set, the shell prints each field after expansion, before executing it. See Tracing command execution for details.
Compatibility
The syntax and options specified in POSIX.1-2024 are much more limited than those in yash-rs. For portable scripts, use only POSIX-specified syntax and options.
POSIX.1-2024 syntax:
- Enable a long option:
set -o optionname
(no--
prefix). - Disable a long option:
set +o optionname
(no++
prefix). - Long options are case-sensitive, must be spelled out in full, and cannot contain extra symbols.
- No support for
no
-prefix inversion of long options. - Enable a short option:
-
followed by the option character. - Disable a short option:
+
followed by the option character. - Short options can be combined after the
-
or+
prefix. - View current options:
set -o
orset +o
.
POSIX.1-2024 options:
-a
,-o allexport
-b
,-o notify
-C
,-o noclobber
-c
-e
,-o errexit
-f
,-o noglob
-h
-i
-m
,-o monitor
-n
,-o noexec
-s
-u
,-o nounset
-v
,-o verbose
-x
,-o xtrace
-o ignoreeof
-o nolog
-o pipefail
-o vi
Working directory
The working directory is the directory from which relative paths are resolved. It is a property of the shell environment and is inherited by subshells and external utilities.
Many commands use the working directory as the default location for file operations. For example, specifying a relative path in a redirection creates the file in the working directory. The ls
utility lists files in the working directory if no operand is given.
Viewing the current working directory
The PWD
and OLDPWD
variables hold the absolute pathnames of the current and previous working directories, respectively. These variables are updated automatically when the working directory changes. If you modify or unset them manually, automatic updates are no longer guaranteed.
$ echo "$PWD"
/home/user
You can also use the pwd
built-in to print the current working directory.
$ pwd
/home/user
Changing the working directory
Use the cd
built-in to change the working directory.
$ cd /tmp
$ pwd
/tmp
$ cd /dev
$ echo "$PWD" "$OLDPWD"
/dev /tmp
Signals and traps
Signals are a method of inter-process communication used to notify a process that a specific event has occurred. The POSIX standard defines a set of signals that have well-defined meanings and behaviors across Unix-like systems. Traps are the shell’s mechanism for handling signals and other events by executing custom commands when specific conditions occur.
What are signals?
Signals are asynchronous notifications sent to a process to inform it of an event. Common signals include:
SIGINT
: Interrupt signal, typically sent when the user pressesCtrl+C
SIGTERM
: Termination request, used to ask a process to exit gracefullySIGQUIT
: Quit signal, typically sent when the user pressesCtrl+\
SIGHUP
: Hangup signal, originally sent when a terminal connection was lostSIGKILL
: Kill signal that cannot be caught or ignoredSIGSTOP
: Stop signal that cannot be caught or ignoredSIGTSTP
: Terminal stop signal, typically sent when the user pressesCtrl+Z
SIGCHLD
: Child process terminated or stoppedSIGUSR1
andSIGUSR2
: User-defined signals for custom applications
Available signals may vary by system. For a complete list, refer to your system’s documentation or use kill -l
.
When a process receives a signal, it can respond in one of three ways:
- Default action: Follow the system’s default behavior for that signal (usually termination)
- Ignore: Do nothing when the signal is received
- Custom action: Execute a custom signal handler
What are traps?
Traps are the shell’s way of defining custom responses to signals and other events. When you set a trap, you specify:
- A condition that triggers the trap (such as a signal or shell exit), and
- An action to perform when the condition occurs.
The shell checks for pending signals and executes corresponding trap actions at safe points during execution, typically before and after executing commands. This ensures that trap actions run in a consistent shell state.
Special conditions
In addition to signals, the shell supports the EXIT
condition, which is triggered when the shell exits (but not when killed by a signal). This allows you to run cleanup commands or perform other actions when the shell session ends.
More conditions may be supported in future versions of the shell.
Trap inheritance and subshells
When the shell creates a subshell:
- Traps set to ignore are inherited by the subshell.
- Traps with custom actions are reset to default behavior.
External utilities inherit the signal dispositions from the shell, but not custom trap actions.
Setting traps
Use the trap
built-in to configure traps or view current traps.
Restrictions
SIGKILL
andSIGSTOP
cannot be caught or ignored.- If a non-interactive shell inherited an ignored signal, that signal cannot be trapped. Interactive shells can modify signals that were initially ignored.
How and when traps are executed
Signal traps run when signals are caught.
- When a signal is caught while the shell is running a command, the shell waits for the command to finish before executing the trap action.
- If a signal is caught while the shell is reading input, the shell waits for the input to complete before executing the trap action. This behavior may change in future versions so that traps can run immediately.
- While executing a signal trap action, other signal traps are not processed (no reentrance), except in subshells.
EXIT
traps run when the shell exits normally, after all other commands complete.
The exit status is preserved across trap action execution, but trap actions can use the exit
built-in to terminate the shell with a specific exit status.
Auto-ignored signals
In an interactive shell, certain signals are automatically ignored by default to prevent the shell from being terminated or stopped unintentionally. Specifically:
SIGINT
,SIGTERM
, andSIGQUIT
are always ignored.- If job control is enabled,
SIGTSTP
,SIGTTIN
, andSIGTTOU
are also ignored.
This ensures the shell remains responsive and in control, even if these signals are sent. You can still set traps for these signals if needed. In subshells, which are non-interactive, this automatic ignoring does not apply.
Startup
This section describes how yash-rs is started and configured.
Command-line arguments
Start the shell by running the yash3
executable. The general syntax is:
yash3 [options] [file [arguments…]]
yash3 [options] -c command [command_name [arguments…]]
yash3 [options] -s [arguments…]
The shell’s behavior is determined by the options and operands you provide. See Command line argument syntax conventions for how arguments are parsed into options and operands.
Options
The shell accepts shell options to control its behavior. The following options are only available at startup:
-c
(--cmdline
): Read and execute commands from thecommand
operand.-s
(--stdin
): Read and execute commands from standard input.-i
(--interactive
): Force the shell to be interactive.-l
(--login
): Make the shell a login shell. This can also be triggered by a leading hyphen in the command name (e.g.,-yash3
).--profile <file>
: Specify a profile file to execute.--noprofile
: Do not execute any profile file.--rcfile <file>
: Specify an rcfile to execute.--norcfile
: Do not execute any rcfile.
Modes of operation
The shell has three modes:
- File mode: If neither
-c
nor-s
is specified, the first operand is treated as the path to a script file to execute. Any following operands become positional parameters for the script. - Command string mode: With
-c
, the shell executes the command string given as the first operand. Ifcommand_name
is specified, it sets the special parameter0
. Remaining operands become positional parameters. - Standard input mode: With
-s
, the shell reads commands from standard input. Any operands are set as positional parameters.
If no operands are given and -c
is not specified, the shell assumes -s
.
Initialization files
When the shell starts, it may execute one or more initialization files to configure the environment.
Login shell
If the shell is a login shell (started with -l
or a leading hyphen in its name), it executes a profile file. The path can be set with --profile
. Use --noprofile
to skip the profile file.
⚠️ Profile file execution is not yet implemented.
Interactive shell
If the shell is interactive, it executes an rcfile. The path can be set with --rcfile
. Use --norcfile
to skip the rcfile.
If no rcfile is specified, the shell checks the ENV
environment variable. If set, its value is expanded for parameter expansion, command substitution, and arithmetic expansion, and used as the rcfile path.
The rcfile is only executed if:
- The shell is interactive,
- The real user ID matches the effective user ID, and
- The real group ID matches the effective group ID.
Compatibility
Options for initialization files (--profile
, --noprofile
, --rcfile
, --norcfile
) are not part of POSIX.1-2024 and may not be available in other shells. See Compatibility in the options documentation for portable shell options.
POSIX.1-2024 does not specify login shells or profile files. The behavior described here is specific to yash-rs and may differ from other shells.
Using the ENV
environment variable for initialization files is POSIX-specified. In the future, yash-rs may support a different default rcfile location depending on the command name and shell options.
Termination
A shell session terminates in the following cases:
- When the shell reaches the end of input.
- When you use the
exit
built-in. - When the shell receives a signal that causes it to terminate, such as
SIGINT
orSIGTERM
, and no trap is set to handle that signal. - When a non-interactive shell is interrupted by a shell error.
- When a command fails and the
errexit
shell option is enabled. (See Exiting on errors.)
Preventing accidental exits
When the input to the shell is a terminal, you can signal an end-of-file with the eof
sequence (usually Ctrl+D
). However, you might not want the shell to exit immediately when this happens, especially if you often hit the sequence by mistake. Enable the ignoreeof
shell option to prevent the shell from exiting on end-of-file and let it wait for more input.
$ set -o ignoreeof
$
# Type `exit` to leave the shell when the ignore-eof option is on.
$ exit
This option is only effective in interactive shells and only when the input is a terminal. As an escape, entering 50 eof sequences in a row will still cause the shell to exit, regardless of the ignoreeof
option.
Exiting subshells
When one of the above conditions occurs in a subshell, the subshell exits. It does not directly cause the parent shell to exit, but the exit status of the subshell may affect the parent shell’s behavior, conditionally causing it to exit if the errexit
option is set.
EXIT
trap
You can set a trap for the EXIT
condition to run commands when the shell exits. This can be useful for cleanup tasks or logging. The trap is executed regardless of how the shell exits, whether due to an error, end-of-file, or explicit exit
command, except when the shell is killed by a signal, in which case the trap is not executed (in yash-rs).
$ trap 'rm -f temporary.txt; echo "Temporary file removed."' EXIT
$ echo "Some data" > temporary.txt
$ cat temporary.txt
Some data
$ exit
Temporary file removed.
The EXIT
trap is run at most once per shell session. Modifying the EXIT
trap while it is running does not have any effect on trap execution. (However, POSIX allows the shell to execute the new trap if the EXIT
trap is redefined while it is running.)
Exit status
If the shell exits due to end of input, the exit
built-in, or the errexit
option, it returns the exit status of the last command executed. See Exit status of the shell for details.
If the shell exits because of a shell error, the exit status is a non-zero value indicating the error.
Shell errors
The following shell errors set the exit status to a non-zero value and may cause the shell to exit, depending on the situation:
- Unrecoverable errors reading input
- The shell exits immediately.
- This does not apply to scripts read by the
source
built-in.
- Command syntax errors
- The shell exits if non-interactive.
- If interactive, the shell ignores the current command and resumes reading input.
- Errors in special built-in utilities
- The shell exits if non-interactive or if the
errexit
option is set. Otherwise, it aborts the current command and resumes reading input. - This includes redirection errors for special built-ins.
- This does not apply to special built-ins run via the
command
built-in.
- The shell exits if non-interactive or if the
- Variable assignment errors and expansion errors
- The shell exits if non-interactive or if
errexit
is set. Otherwise, it aborts the current command and resumes reading input.
- The shell exits if non-interactive or if
- Redirection errors (except for special built-ins)
- The shell exits if
errexit
is set. Otherwise, it continues with the next command.
- The shell exits if
POSIX.1-2024 allows shells to exit on command search errors, but many shells, including yash-rs, do not.
Dynamic command evaluation
Two built-in utilities support dynamic command evaluation.
Evaluating command strings
The eval
built-in evaluates a command string. This is useful for constructing and executing commands dynamically.
For example, you can use eval
to assign a value to a variable whose name is chosen at runtime:
echo "Type a variable name:"
read -r varname
eval "$varname='Hello, world!'"
eval "echo 'The value of $varname is:' \$$varname"
Reading and executing files
The .
(dot) built-in reads and executes commands from a file. This is useful for organizing scripts and reusing code.
For example, you can use .
to source a file containing variable definitions:
# contents of vars.sh
greeting="Hello, world!"
farewell="Goodbye, world!"
# main script
. ./vars.sh
echo "$greeting"
echo "$farewell"
source
is a non-POSIX synonym for the .
built-in.
Script debugging
Yash-rs offers several features to help debug scripts.
Exiting on errors
Many utilities return a non-zero exit status when they fail, but by default the shell continues executing the next command, which can lead to unexpected results. To stop the script when a command fails, enable the errexit
option. For details, see Exiting on errors.
Catching errors across pipeline components
By default, the exit status of a pipeline reflects only the last command, ignoring failures in earlier commands. To make the pipeline fail if any command fails, enable the pipefail
shell option. With pipefail
, the pipeline’s exit status is that of the last command that returned a non-zero status, or zero if all returned zero. This helps catch errors in pipelines.
The `pipefail` option is not yet implemented in yash-rs.
Blocking unset parameters
Unset parameters expand to an empty string by default, which can silently hide misspelled parameter names, potentially leading to unexpected results if the intended value is unused. To catch such errors early, enable the nounset
shell option. With nounset
, the shell raises an error whenever an unset parameter is expanded. See Unset parameters for more information.
This option also detects unset variables in arithmetic expressions.
Reviewing command input
When the verbose
shell option is enabled, the shell prints each command to standard error as it reads it, before executing. This is useful for reviewing commands being executed, especially in scripts.
$ set -o verbose
$ echo "Hello, world!"
echo "Hello, world!"
Hello, world!
$ set -o verbose
$ greet() {
greet() {
> echo "Hello, world!"
echo "Hello, world!"
> }
}
$ greet
greet
Hello, world!
Tracing command execution
If you enable the xtrace
shell option, the shell prints expanded fields in each command to standard error before executing it. This is useful for reviewing actual commands being executed.
Each line of output is prefixed with the value of the PS4
variable, which defaults to +
. Parameter expansion, command substitution, and arithmetic expansion are performed on the PS4
value before printing it.
$ PS4='$((i=i+1))+ '; set -o xtrace
$ while getopts n option -n foo; do
> case $option in
> (n) n_option=true ;;
> (*) echo "Unknown option: $option" ;;
> esac
> done
1+ getopts n option -n foo
2+ case n in
3+ n_option=true
4+ getopts n option -n foo
Checking syntax
If the exec
shell option is unset, the shell only parses commands without executing them. This is useful for checking syntax errors in scripts without running them.
$ set +o exec
$ echo "Hello, world!"
$ echo "Oops, a syntax error";;
error: the compound command delimiter is unmatched
--> <stdin>:3:28
|
3 | echo "Oops, a syntax error";;
| ^^ not in a `case` command
|
# Invoke the shell with the `exec` option unset to check a script file
yash3 +o exec my_script.sh
Interactive shell
When the interactive
shell option is enabled, the shell behaves in a way that is more suitable for interactive use. Such a shell is called an interactive shell. Currently, only the essential features are implemented, but more will be added in the future.
Enabling interactive mode
When you start the shell without arguments in a terminal, it usually enables interactive mode by default:
yash3
Specifically, interactive mode is enabled if:
- you do not specify
+i
, - the
-s
option is active, either explicitly or implicitly, and - standard input and standard error are terminals.
To force the shell to be interactive, use the -i
option:
yash3 -i
Interactive mode can only be set at startup. To change the interactive mode, you must restart the shell.
Telling if the shell is interactive
To determine if the shell is running in interactive mode, check whether the -
special parameter contains i
:
case $- in
*i*) echo "Interactive shell" ;;
*) echo "Non-interactive shell" ;;
esac
See Viewing current options for additional methods.
Note that subshells of an interactive shell are not interactive, even if the interactive
option is set.
What happens in interactive mode
When the shell is interactive:
- The shell executes an rcfile during startup.
- The
-
special parameter includesi
. - The
exec
shell option is always considered set. - The
ignoreeof
shell option is honored. - Starting an asynchronous command prints its job number and process ID.
- The shell does not exit immediately on most shell errors.
- Some signals are automatically ignored.
- Signals ignored on entry can be trapped.
- Command prompts are displayed when reading input.
- Job status changes are reported before prompting for input if job control is enabled.
- The
read
built-in displays a prompt when reading a second or subsequent line of input.
Command prompt
When an interactive shell reads input, it displays a command prompt—a string indicating that the shell is ready to accept commands. The prompt can be customized to display information such as the current working directory, username, or hostname.
Customizing the command prompt
The command prompt is controlled by the PS1
and PS2
variables:
PS1
defines the primary prompt, shown when the shell is ready for a new command.PS2
defines the secondary prompt, shown when the shell expects more input to complete a command (i.e., when a command spans multiple lines).
Each time the shell displays a prompt, it performs parameter expansion, command substitution, and arithmetic expansion on the prompt strings. This allows prompts to include dynamic information, such as the working directory or username.
After these expansions, the shell performs exclamation mark expansion (see below) on the PS1
prompt. PS2
is not subject to exclamation mark expansion.
The default values for these variables are:
PS1='$ '
PS2='> '
Many shells change the default PS1
to #
for the root user, but yash-rs does not yet support this.
Custom prompts are usually set in the rcfile. For example, to include the username, hostname, and working directory in the prompt, add this to your rcfile:
PS1='${LOGNAME}@${HOSTNAME}:${PWD} $ '
You do not need to export PS1
or PS2
for them to take effect.
Exclamation mark expansion
Exclamation mark expansion replaces an exclamation mark (!
) in the PS1
prompt with the history number of the next command. However, yash-rs does not yet support command history, so this feature is currently non-functional.
To include a literal exclamation mark in the prompt, use a double exclamation mark (!!
).
Compatibility
POSIX.1-2024 allows shells to perform exclamation mark expansion before other expansions, in which case exclamation marks produced by those expansions are not replaced.
Additional special notation that starts with a backslash (\
), supported by earlier versions of yash, is not yet implemented in yash-rs.
Job control
Job control lets users selectively stop (suspend) commands and resume them later. It also allows users to interrupt running commands.
Job control is intended for use in interactive shells. Unless otherwise noted, the descriptions below assume the shell is interactive.
Overview
Let’s first get a general idea of job control.
Why is job control useful?
Suppose you start a command to download a file, but realize the network is down and want to cancel the download. With job control, you can interrupt the command by pressing Ctrl-C
, which usually terminates it:
$ curl 'http://example.com/largefile.zip' > largefile.zip
^C
$
Job control also lets you move running commands between the foreground and background, making it easier to manage multiple tasks. For example, suppose you run a command to remove many files:
$ rm -rf ~/.my_cache_files
If the command is taking too long, you might wish you had started it as an asynchronous command so you could run other commands at the same time. With job control, you can turn this running command into an asynchronous one without stopping and restarting it. First, press Ctrl-Z
to suspend the command and return to the prompt. The shell displays a message indicating the command has been stopped:
^Z[1] + Stopped(SIGTSTP) rm -rf ~/.my_cache_files
You can then resume the command in the background with the bg
built-in:
$ bg
[1] rm -rf ~/.my_cache_files
Now the command runs asynchronously, and you can continue using the shell for other tasks. If you want to bring the command back to the foreground (synchronous execution), use the fg
built-in:
$ fg
rm -rf ~/.my_cache_files
Another common scenario is when you use an editor to work on source code and want to build and test your code while keeping the editor open. You can suspend the editor with Ctrl-Z
, run your build command, and then return to the editor with fg
:
$ vi main.rs
^Z[1] + Stopped(SIGTSTP) vi main.rs
$ cargo build
$ fg
vi main.rs
(This example only shows the shell output. In practice, you would also see the editor screen and the build command’s output.)
How job control works
The shell implements job control using the operating system’s process management features. It manages processes and their states, allowing users to control their execution.
When the shell starts a subshell, it runs the subshell in a separate process. This process is placed in a new process group, so any processes created during the subshell’s execution can be managed together. Process groups allow the shell and other utilities to send signals to all relevant processes at once.
If the subshell runs synchronously (in the foreground), the shell sets its process group as the terminal’s foreground process group. This lets you interact with the subshell’s processes while they’re running. When you press Ctrl-C
or Ctrl-Z
, the terminal sends a SIGINT
or SIGTSTP
signal to the foreground process group. Typically, SIGINT
terminates a process, and SIGTSTP
suspends it.
When a foreground process is suspended, the shell displays a message and returns to the command prompt. The shell keeps a list of remaining subshells (jobs) so you can manage them later. When you use the fg
built-in, the shell makes the specified job the foreground process group again and sends it a SIGCONT
signal to resume execution. If you use bg
, the shell sends SIGCONT
but leaves the job running in the background.
All commands in a pipeline run in the same process group, so you can manage the entire pipeline as a single job.
When the shell is reading input, it makes itself the terminal’s foreground process group. This means key sequences like Ctrl-C
and Ctrl-Z
send signals to the shell itself. However, the shell ignores these signals to avoid being interrupted or suspended unintentionally.
Enabling job control
By default, job control is enabled only if the shell is interactive. You can enable or disable job control at startup or during a shell session by specifying the monitor
shell option:
yash3 -o monitor
Creating and managing jobs
Job control is complex. yash-rs implements it mostly according to the POSIX.1-2024 standard, with some deviations. Non-POSIX behavior is marked with ⚠️.
Job control concepts
A process is a running instance of a program, such as the shell or an external utility. Each process belongs to a process group, which is a collection of processes managed together. Each process group belongs to a session, which is a collection of process groups.
When a process creates another process, the new process is its child process, and the original is the parent process. Child processes inherit certain attributes, such as process group and session, but can also create new ones.
In the context of job control, a terminal is an abstract interface managed by the operating system, which provides the necessary mechanisms for shells to implement job control. A terminal can be associated with a session, making it a controlling terminal. A process group can be selected as the terminal’s foreground process group, which receives signals from key sequences like Ctrl-C
and Ctrl-Z
. Other process groups in the session are background process groups.
For this document, we assume all terminals are controlling terminals, since non-controlling terminals aren’t useful for job control.
A job is a subshell implemented as a child process of the shell. (⚠️This differs from POSIX, which uses “job” for a broader set of commands, including lists.) Each job has a unique job number, a positive integer assigned by the shell when the job is created. The shell maintains a job list with information about each job’s number, status, etc.
A job ID starts with %
and is used to specify jobs in built-ins like fg
, bg
, and jobs
. For example, %1
refers to job number 1.
Subshells and process groups
When job control is enabled, the shell manages each subshell as a job in a new process group, allowing independent control. A multi-command pipeline is treated as a single job, with all commands in the same process group. ⚠️Subshells created for command substitutions are not treated as jobs and do not create new process groups, because yash-rs does not support suspending and resuming entire commands containing command substitutions.
Job control does not affect nested subshells recursively. However, if a subshell starts another shell that supports job control, that shell can manage jobs independently.
You can view job process groups using the ps
utility:
$ sleep 60 && echo "1 minute elapsed!"&
[1] 10068
$ ps -j
PID PGID SID TTY TIME CMD
10012 10012 10012 pts/1 00:00:00 yash3
10068 10068 10012 pts/1 00:00:00 yash3
10069 10068 10012 pts/1 00:00:00 sleep
10076 10076 10012 pts/1 00:00:00 ps
Foreground and background jobs
Unless starting an asynchronous command, the shell runs jobs as the terminal’s foreground process group. This directs signals from key sequences like Ctrl-C
and Ctrl-Z
to the job, not the shell or background jobs.
For example, pressing Ctrl-C
interrupts a foreground job (the signal is invisible, but ^C
shows you pressed Ctrl-C
):
$ sleep 60
^C$
When a foreground job terminates or suspends, the shell returns itself to the foreground so it can continue running commands and reading input. The shell can only examine the status of its direct child processes; descendant processes do not affect job control.
Here’s how to suspend a foreground job with Ctrl-Z
(^Z
shows you pressed Ctrl-Z
):
$ sleep 60
^Z[1] + Stopped(SIGTSTP) sleep 60
$
An asynchronous command creates a background job, which runs alongside the shell and other jobs. The shell shows the job number and process (group) ID when the background job is created:
$ sleep 60&
[1] 10068
$
Background jobs are not affected by Ctrl-C
or Ctrl-Z
. To send signals to background jobs, use the kill
built-in (see Signaling jobs). You can also bring a background job to the foreground with fg
and then use Ctrl-C
or Ctrl-Z
.
Suspending foreground jobs
Pressing Ctrl-Z
sends a SIGTSTP
signal to the foreground process group. Processes may respond differently, but typically suspend execution.
When a foreground job suspends, the shell displays a message and discards any pending commands that have been read but not yet executed. This prevents the shell from running commands that might depend on the suspended job’s result. (⚠️POSIX.1-2024 allows discarding only up to the next asynchronous command, but yash-rs discards all pending commands.)
For example, sleep
is suspended and the following echo
is discarded:
$ sleep 60 && echo "1 minute elapsed!"
^Z[1] + Stopped(SIGTSTP) sleep 60
$
To avoid discarding remaining commands, run the sequence in a subshell. Here, the subshell is suspended during sleep
, and echo
runs after sleep
resumes and finishes:
$ (sleep 60 && echo "1 minute elapsed!")
^Z[1] + Stopped(SIGTSTP) sleep 60 && echo "1 minute elapsed!"
$ fg
sleep 60 && echo "1 minute elapsed!"
1 minute elapsed!
After suspension, the ?
special parameter shows the exit status of the suspended job as if it had been terminated by the signal that suspended it:
$ sleep 60
^Z[1] + Stopped(SIGTSTP) sleep 60
$ echo "Exit status $? corresponds to SIG$(kill -l $?)"
Exit status 404 corresponds to SIGTSTP
Resuming jobs
The fg
built-in brings a job to the foreground and sends it a SIGCONT
signal to resume execution. The job continues as the terminal’s foreground process group, letting you interact with it again:
$ sleep 60 && echo "1 minute elapsed!"&
[1] 10051
$ fg
sleep 60 && echo "1 minute elapsed!"
^Z[1] + Stopped(SIGTSTP) sleep 60 && echo "1 minute elapsed!"
$ fg
sleep 60 && echo "1 minute elapsed!"
1 minute elapsed!
The bg
built-in sends SIGCONT
to a job without bringing it to the foreground, letting it continue in the background while you use the shell for other tasks.
For example, echo
prints a message while the shell is in the foreground:
$ (sleep 60 && echo "1 minute elapsed!")
^Z[1] + Stopped(SIGTSTP) sleep 60 && echo "1 minute elapsed!"
$ bg
[1] sleep 60 && echo "1 minute elapsed!"
$ echo "Background job running"
Background job running
$ 1 minute elapsed!
Signaling jobs
The kill
built-in sends a signal to a process or process group. It accepts job IDs (see below) to specify jobs as targets. This allows you to control jobs at a low level, such as suspending or terminating them. For example, use kill -s STOP %1
to suspend job 1, or kill -s KILL %2
to terminate job 2:
$ sleep 60 && echo "1 minute elapsed!"&
[1] 10053
$ kill %1
[1] + Killed(SIGTERM) sleep 60 && echo "1 minute elapsed!"
$
Job list
The job list includes each job’s number, process (group) ID, status, and command string. The shell updates this list as jobs are created, suspended, resumed, or terminated. The process group ID of a job equals the process ID of its main process, so they are not distinguished in the job list.
Use the jobs
built-in to display the current job list:
$ rm -r foo& rm -r bar& rm -r baz&
[1] 10055
[2] 10056
[3] 10057
$ jobs
[1] + Running rm -r foo
[2] - Running rm -r bar
[3] Running rm -r baz
When a foreground job terminates, the shell removes it from the job list. If a job terminates in the background, the shell keeps it in the list so you can see its status and retrieve its exit status later. Such jobs are removed when their result is retrieved using jobs
or wait
.
Job numbers
When a job is created, the shell assigns it a unique job number, regardless of whether job control is enabled. Job numbers are assigned sequentially, starting from 1. After a job is removed, its number may be reused.
Current and previous jobs
The shell automatically selects two jobs as the current job and previous job from the job list. These can be referred to with special job IDs (see below). Some built-ins operate on the current job by default, making it easy to specify jobs without typing a job number or command string.
In job IDs and jobs
output, the current job is marked with +
, and the previous job with -
.
The current job is usually the most recently suspended job, or another job if none are suspended. When a job is suspended, it becomes the current job, and the previous current job becomes the previous job. When a suspended job is resumed or removed, the current and previous jobs are updated so the current job is always a suspended job if any exist, and the previous job is another suspended job if possible. If there is only one job, there is no previous job. These rules ensure built-ins like fg
and bg
operate on the most relevant jobs by default.
Job IDs
Built-in utilities that operate on jobs use job IDs to specify them. A job ID matches one of these formats:
%
,%%
, or%+
: the current job.%-
: the previous job.%n
: job numbern
.%foo
: job with a command string starting withfoo
.%?foo
: job with a command string containingfoo
.
Job status change notifications
When a background job’s status changes (suspended, resumed, or terminated), the shell automatically notifies you before the next command prompt, so you can see job status changes without checking manually. The notification format matches the jobs
built-in output.
$ rm -r foo& # remove a directory in the background
[1] 10059
$ rm -r bar # remove another directory in the foreground
[1] - Done rm -r foo
$
In this example, the rm -r foo
job finishes while rm -r bar
runs in the foreground. The background job’s status change is automatically shown before the next prompt.
Note that automatic notifications do not remove the reported job from the job list; jobs are only removed after their status is retrieved using the jobs
or wait
built-ins.
Additional details
The following sections cover special cases and extra features of job control you may not need in everyday use.
Terminal setting management
⚠️Not yet implemented in yash-rs: Some utilities, like less
and vi
, change terminal settings for interactive use and complex UI. If suspended, they may leave the terminal in a state unsuitable for other utilities to run. To prevent this, the shell should restore the terminal settings when a foreground job is suspended, and again when the job is resumed in the foreground.
Job control in non-interactive shells
You can enable job control in non-interactive shells, but it’s rarely useful. Job control is mainly for interactive use, where users manage jobs dynamically. In non-interactive shells, there’s no user interaction, so features like suspending and resuming jobs don’t apply.
When job control is enabled in a non-interactive shell:
- The shell does not ignore
SIGINT
,SIGTSTP
, or other job control signals by default. The shell itself may be interrupted or suspended withCtrl-C
orCtrl-Z
. - The shell does not automatically notify you of job status changes. You must use the
jobs
built-in to check status.
Jobs without job control
Each asynchronous command started when job control is disabled is also managed as a job, but runs in the same process group as the shell. Signals from key sequences like Ctrl-C
and Ctrl-Z
are sent to the whole process group, including the shell and the asynchronous command. This means jobs cannot be interrupted, suspended, or resumed independently. The shell still assigns job numbers and maintains the job list so you can see status and retrieve exit status later.
Background shells
When a shell starts job control in the background, it suspends itself until brought to the foreground by another process. This prevents the shell from interfering with the current foreground process group. (⚠️POSIX.1-2024 requires using SIGTTIN
for this, but yash-rs uses SIGTTOU
instead. See Issue #421 for details.)
⚠️POSIX.1-2024 requires the shell to become a process group leader—the initial process in a process group—when starting job control. Yash-rs does not currently implement this. See Issue #483 for why this is not straightforward.
Configuring key sequences for signals
You can configure key sequences that send signals to the foreground process group using the stty
utility. The table below shows parameter names, default key sequences, and corresponding signals:
Parameter | Key | Signal |
---|---|---|
intr | Ctrl-C | SIGINT (interrupt) |
susp | Ctrl-Z | SIGTSTP (suspend) |
quit | Ctrl-\ | SIGQUIT (quit) |
For example, to change the intr
key to Ctrl-X
:
$ stty intr ^X
If your terminal uses different key sequences, press the appropriate keys instead of Ctrl-C
or Ctrl-Z
to send signals to the foreground process group.
View the current configuration with stty -a
.
Compatibility
POSIX.1-2024 defines job control but allows for implementation-defined behavior in many areas. Yash-rs follows the standard closely, with some deviations (marked with ⚠️). Job control is complex, and implementations differ. Perfect POSIX compliance is not expected in any shell, including yash-rs.
The job ID %
is a common extension to POSIX.1-2024. The strictly portable way to refer to the current job is %%
or %+
.
In yash-rs, jobs are currently defined as subshells, whereas POSIX.1-2024 treats a wider range of commands as jobs. At present, yash-rs does not support suspending built-ins, since they run within the current shell environment rather than in a subshell. Future versions of yash-rs may expand the definition of jobs to include this capability.
Built-in utilities
Built-in utilities (or built-ins) are utilities built into the shell, not separate executables. They run directly in the shell process, making them faster and more efficient for certain tasks.
Types of built-in utilities
Yash provides several types of built-in utilities.
Special built-ins
Special built-ins have special meaning or behavior in the shell. They are used for control flow, variable manipulation, and other core tasks. Notable properties:
- Command search always finds special built-ins first, regardless of
PATH
. - Functions cannot override special built-ins.
- Assignments in a simple command running a special built-in persist after the command.
- Errors in special built-ins cause the shell to exit if non-interactive. (See Shell errors)
POSIX.1-2024 defines these special built-ins:
As an extension, yash-rs also supports source
as an alias for .
(dot).
Mandatory built-ins
Mandatory built-ins must be implemented by all POSIX-compliant shells. They provide essential scripting and command features.
Like special built-ins, they are found regardless of PATH
in command search, but they can be overridden by functions.
POSIX.1-2024 defines these mandatory built-ins:
alias
bg
cd
command
fc
(not yet implemented)fg
getopts
hash
(not yet implemented)jobs
kill
read
type
ulimit
umask
unalias
wait
Elective built-ins
Elective built-ins work like mandatory built-ins but are not required by POSIX.1-2024. They provide extra features for scripting or interactive use.
Elective built-ins can be overridden by functions and are found in command search regardless of PATH
.
In yash-rs, the following elective built-in is implemented:
More may be added in the future.
Substitutive built-ins
Substitutive built-ins replace external utilities to avoid process creation overhead for common tasks.
Substitutive built-ins behave like external utilities: they are located during command search and can be overridden by functions. However, the built-in is only available if the corresponding external utility exists in PATH
. If the external utility is missing from PATH
, the built-in is also unavailable, ensuring consistent behavior with the absence of the utility.
Yash-rs implements these substitutive built-ins:
More may be added in the future.
Compatibility
POSIX.1-2024 reserves many names for shell-specific built-ins. Yash-rs implements some of these, and may add more in the future. Other shells may implement these differently:
alloc
autoload
bind
bindkey
builtin
bye
caller
cap
chdir
clone
comparguments
compcall
compctl
compdescribe
compfiles
compgen
compgroups
complete
compound
compquote
comptags
comptry
compvalues
declare
dirs
disable
disown
dosh
echotc
echoti
enum
float
help
hist
history
integer
let
local
login
logout
map
mapfile
nameref
popd
print
pushd
readarray
repeat
savehistory
shopt
source
stop
suspend
typeset
whence
Command line argument syntax conventions
Arguments are string parameters passed to built-in utilities. The syntax varies between built-ins, but most follow common conventions. The description below applies to yash-rs built-ins unless otherwise noted.
Operands
Operands are main arguments specifying objects or values for the built-in. For example, in cd
, the operand is the directory:
$ cd /dev
Options
Options are supplementary arguments that modify the behavior of the built-in. They start with a hyphen (-
) followed by one or more characters. Short options are named with a single character (e.g., -P
), while long options are more descriptive and start with two hyphens (e.g., --physical
). For example, the cd
built-in uses -P
or --physical
to force the shell to use the physical directory structure instead of preserving symbolic links:
$ cd -P /dev
With a long option:
$ cd --physical /dev
Multiple short options can be combined. For example, cd -P -e /dev
can be written as:
$ cd -Pe /dev
Long options must be specified separately.
Long option names can be abbreviated if unambiguous. For example, --p
is enough for --physical
in cd
:
$ cd --p /dev
However, future additions may make abbreviations ambiguous, so use the full name in scripts.
POSIX.1-2024 only specifies short option syntax. Long options are a yash-rs extension.
Option arguments
Some options require an argument. For short options, the argument can follow immediately or as a separate argument. For example, -d
in read
takes a delimiter argument:
If the argument is non-empty, it can be attached: -d+
. If empty, specify separately: -d ''
:
For long options, use =
or a separate argument:
Separators
To treat an argument starting with -
as an operand, use the --
separator. This tells the shell to stop parsing options. For example, to change to a directory named -P
:
Note that a single hyphen (-
) is not an option, but an operand. It can be used without --
:
Argument order
Operands must come after options. All arguments after the first operand are treated as operands, even if they start with a hyphen:
$ cd /dev -P
error: unexpected operand
--> <stdin>:1:9
|
1 | cd /dev -P
| -- ^^ -P: unexpected operand
| |
| info: executing the cd built-in
|
Specifying options after operands may be supported in the future.
Colon (:) built-in
The colon (:
) built-in does nothing.
Synopsis
: […]
Description
The colon built-in is a dummy command that does nothing. Any arguments are ignored.
Errors
None.
Exit status
Zero.
Compatibility
The colon built-in is specified in the POSIX standard.
Alias built-in
The alias
built-in defines aliases or prints alias definitions.
Synopsis
alias [name[=value]…]
Description
The alias
built-in defines aliases or prints existing alias definitions, depending on the operands. With no operands, it prints all alias definitions in a quoted assignment form suitable for reuse as input to alias
.
Options
None.
Non-POSIX options may be added in the future.
Operands
Each operand must be of the form name=value
or name
. The first form defines an alias named name that expands to value. The second form prints the definition of the alias named name.
Errors
It is an error if an operand without =
refers to an alias that does not exist.
Exit status
Zero unless an error occurs.
Examples
See Aliases.
Compatibility
The alias
built-in is specified by POSIX.1-2024.
Some shells have predefined aliases that are printed even if you have not defined any explicitly.
Bg built-in
The bg
built-in resumes suspended jobs in the background.
Synopsis
bg [job_id…]
Description
See Job control for an overview of job control in yash-rs. The built-in resumes the specified jobs by sending the SIGCONT
signal to them.
The (last) resumed job’s process ID is set to the !
special parameter.
Options
None.
Operands
Operands specify jobs to resume as job IDs. If omitted, the built-in resumes the current job.
Standard output
The built-in writes the job number and name of each resumed job to the standard output.
Errors
It is an error if:
- the specified job is not found,
- the specified job is not job-controlled, that is, job control was off when the job was started, or
- job control is off in the current shell environment.
Exit status
Zero unless an error occurs.
Examples
See Job control.
Compatibility
Many implementations allow omitting the leading %
from job IDs, though it is not required by POSIX.
Previous versions of yash allowed it, but this is not yet supported in yash-rs.
Some implementations (including the previous version of yash, but not this version) regard it is an error to resume a job that has already terminated.
Break built-in
The break
built-in terminates the execution of a loop.
Synopsis
break [n]
Description
break n
quits the execution of the nth innermost for
, while
, or until
loop. The specified loop must lexically enclose the break command, that is:
- The loop is running in the same execution environment as the break command; and
- The break command appears inside the condition or body of the loop but not in the body of a function definition command appearing inside the loop.
It is an error if there is no loop enclosing the break command. If n is greater than the number of enclosing loops, the built-in exits the outermost one.
Options
None.
Operands
Operand n specifies the nest level of the loop to exit. If omitted, it defaults to 1. It is an error if the value is not a positive decimal integer.
Exit status
Zero if the built-in successfully breaks the loop; non-zero otherwise.
Examples
See Break and continue.
Compatibility
The break
built-in is specified by POSIX.1-2024.
The behavior is unspecified in POSIX when the break
built-in is used without an enclosing loop, in which case the current implementation returns an error.
POSIX allows the built-in to break a loop running in the current execution environment that does not lexically enclose the break command. Our implementation does not do that.
In some shells, the break
built-in lacks support for the --
separator.
Previous versions of yash supported the non-standard -i
option, but this is not yet supported in yash-rs.
Cd built-in
The cd
built-in changes the working directory.
Synopsis
cd [-L|-P [-e]] [directory]
Specifying directory
The built-in by default changes the working directory to the user’s home directory, which is contained in the HOME
variable:
$ cd
An operand, if given, specifies the directory to change to:
$ cd /usr/bin
To return to the previous working directory, pass -
as the operand:
$ cd -
Resolving relative pathnames
If the new working directory is specified as a relative pathname, it is resolved against the current working directory by default. You can make it resolved against other directories by defining the CDPATH
variable. For example, if the variable is defined as CDPATH=/usr
, running cd bin
will change the working directory to /usr/bin
. See the security implications of CDPATH
before using it.
Handling symbolic links
The built-in provides two modes for handling symbolic links in computing the new working directory. See Options and Examples.
Description
The built-in changes the working directory to the specified directory. The new working directory is determined from the option and operand as follows:
- If the operand is omitted, the value of the
HOME
variable is used for the operand. If the operand is a single hyphen (-
), the value of theOLDPWD
variable is used for the operand. If the variable is not set or empty, it is an error. Otherwise, the operand is used as is. - If the operand does not start with a slash (
/
) and the first pathname component in the operand is neither dot (.
) nor dot-dot (..
), the built-in searches the directories specified by theCDPATH
variable for a first directory that contains the operand as a subdirectory. If such a directory is found, the operand is replaced with the path to the subdirectory, that is, the concatenation of the pathname contained inCDPATH
and the previous operand. If no such directory is found, the operand is used as is.- The value of
CDPATH
is a colon-separated list of directories, searched in order. If it includes an empty item, it is treated as the current working directory. It is similar to including.
in the list, but it suppresses printing the new working directory. - Directories listed in
CDPATH
may be relative, in which case they are resolved against the current working directory. This behavior may be confusing, so use with caution. - Note the security implications of
CDPATH
.
- The value of
- If the
-L
option is effective, the operand is canonicalized as follows:- If the operand does not start with a slash (
/
), the value of thePWD
variable is prepended to the operand. - Dot (
.
) components in the operand are removed. - Dot-dot (
..
) components in the operand are removed along with the preceding component. However, if such a preceding component refers to a non-existent directory, it is an error. - Redundant slashes in the operand are removed.
- If the operand does not start with a slash (
The working directory is changed to the operand after the above processing. If the change is successful, the value of the PWD
variable is updated to the new working directory:
- If the
-L
option is effective, the final operand value becomes the new value ofPWD
. - If the
-P
option is effective, the newPWD
value is recomputed in the same way aspwd -P
does, so it does not include symbolic links.
The previous PWD
value is assigned to the OLDPWD
variable.
Options
With the -L
(--logical
) option, the operand is resolved logically, that is, the canonicalization is performed as above and symbolic link components are preserved in the new PWD
value.
With the -P
(--physical
) option, the operand is resolved physically; the operand pathname is passed to the underlying system call without the canonicalization. The new PWD
value is recomputed from the actual new working directory, so it does not include symbolic link components.
These two options are mutually exclusive. The last specified one applies if given both. The default is -L
.
When -P
is effective, the built-in may fail to determine the new working directory pathname to assign to PWD
. By default, the exit status does not indicate the failure. If the -e
(--ensure-pwd
) option is given together with -P
, the built-in returns exit status 1 in this case. This allows callers to detect that PWD
could not be determined and handle the condition.
Operands
The built-in takes a single operand that specifies the directory to change to. If omitted, the value of HOME
is used. If the operand is a single hyphen (-
), the value of OLDPWD
is used.
Standard output
If the new working directory is based on a non-empty item in CDPATH
or the operand is a single hyphen (-
), the built-in prints the new value of PWD
followed by a newline to the standard output.
Errors
This built-in fails if the working directory cannot be changed, for example, in the following cases:
- The operand does not resolve to an existing accessible directory.
- The operand is omitted and
HOME
is not set or empty. - The operand is a single hyphen (
-
) andOLDPWD
is not set or empty. - The resolved pathname of the new working directory is too long.
It is also an error if a given operand is an empty string.
If the -P
option is effective, the built-in may fail to determine the new working directory pathname to assign to PWD
, for example, in the following cases:
- The new pathname is too long.
- Some ancestor directories of the new working directory are not accessible.
- The new working directory does not belong to the filesystem tree.
In these cases, the working directory remains changed, OLDPWD
is updated to the previous value of PWD
, PWD
is left empty, and the exit status depends on the -e
option.
The built-in may also fail if PWD
or OLDPWD
is read-only. In this case, the working directory remains changed, but the variable is not updated.
If the new working directory name cannot be printed to the standard output, the built-in prints a warning message to the standard error, but this does not affect the working directory change or the exit status.
Exit Status
- If the working directory is changed successfully, the exit status is zero, except in the following cases where the exit status is one:
- The
-P
and-e
options are effective and the new working directory pathname cannot be determined. - The
PWD
orOLDPWD
variable is read-only.
- The
- If the working directory cannot be changed because of an error in the underlying
chdir
system call, the exit status is two. - If the
-L
option is effective and canonicalization fails because of a..
component referring to a non-existent directory, the exit status is three. - If the operand cannot be processed because of an unset or empty
HOME
orOLDPWD
, the exit status is four. - If the command arguments are invalid, the exit status is five.
Examples
Compare how -L
and -P
handle symbolic links:
$ ln -s /usr/bin symlink
$ cd -L symlink
$ pwd
/home/user/symlink
$ cd -L ..
$ pwd
/home/user
$ ln -s /usr/bin symlink
$ cd -L symlink
$ pwd
/home/user/symlink
$ cd -P ..
$ pwd
/usr
See how CDPATH
affects determining the new working directory:
Security considerations
Although CDPATH
can be helpful if used correctly, it can catch unwary users off guard, leading to unintended changes in the behavior of shell scripts. If a shell script is executed with the CDPATH
environment variable set to a directory crafted by an attacker, the script may change the working directory to an unexpected one. To ensure that the cd
built-in behaves as intended, shell script writers should unset the variable at the beginning of the script. Users can configure CDPATH
in their shell sessions, but should avoid exporting the variable to the environment. Users are advised to include an empty item as the first item in CDPATH
to ensure that the current working directory is searched before other CDPATH
directories are considered.
Because the built-in treats -
as a special operand, running cd -
does not necessarily change the working directory to a directory literally named -
. This can produce unexpected results, especially when the operand is supplied via a parameter. For more information, see the Application usage section for the cd
utility in POSIX. You can also use cd ./-
to change to a directory literally named -
.
By default, the built-in resolves pathnames logically (-L
), while many other utilities resolve pathnames physically (as with cd -P
). If you intend to use a pathname with both cd
and other utilities, use the -P
option to ensure consistent resolution.
Compatibility
POSIX-1.2024 defines the cd
utility with the -L
, -P
, and -e
options.
The shell sets PWD
on the startup and modifies it in the cd
built-in. If PWD
is modified or unset otherwise, the behavior of cd
and pwd
is unspecified.
The error handling behavior and the exit status do not agree between existing implementations when the built-in fails because of a write error or a read-only variable error.
Other implementations may return different non-zero exit statuses in cases where this implementation would return exit statuses between 2 and 4.
POSIX allows the shell to convert the pathname passed to the underlying chdir
system call to a shorter relative pathname when the -L
option is in effect. This conversion is mandatory if:
- the original operand was not longer than
PATH_MAX
bytes (including the terminating nul byte), - the final operand is longer than
PATH_MAX
bytes (including the terminating nul byte), and - the final operand starts with
PWD
and hence can be considered to be a subdirectory of the current working directory.
POSIX does not specify whether the shell should perform the conversion if
the above conditions are not met. The current implementation does it if and
only if the final operand starts with PWD
.
Command built-in
The command
built-in executes a utility bypassing functions.
This is useful when you want to execute a utility that has the same name as a function. The built-in also has options to search for the location of the utility.
Synopsis
command [-p] name [arguments…]
command -v|-V [-p] name
Description
Without the -v
or -V
option, the command
built-in executes the utility specified by the name with the given arguments. This is similar to the execution of a simple command, but the functions are not searched for the name.
With the -v
or -V
option, the built-in identifies the type of the name
and, optionally, the location of the utility. The -v
option prints the
pathname of the utility, if found, and the -V
option prints a more
detailed description of the utility.
Options
The -p
(--path
) option causes the built-in to search for the utility in the
standard search path instead of the current PATH
.
The -v
(--identify
) option identifies the type of the command name and prints the
pathname of the utility, if found.
The -V
(--verbose-identify
) option identifies the type of the command name and prints a
more detailed description of the utility.
Operands
The name operand specifies the name of the utility to execute or identify. The arguments are passed to the utility when executing it.
Standard output
When the -v
option is given, the built-in prints the following:
- The absolute pathname of the utility, if found in the search path.
- The utility name itself, if it is a non-substitutive built-in, function, or reserved word, hence not subject to search.
- A command line that would redefine the alias, if the name is an alias.
When the -V
option is given, the built-in describes the utility in a more
descriptive, human-readable format. The exact format is not specified here
as it is subject to change.
Nothing is printed if the utility is not found.
Errors
It is an error if the specified utility is not found or cannot be executed.
With the -v
option, no error message is printed for the utility not found.
Exit status
Without the -v
or -V
option, the exit status is that of the utility
executed. If the utility is not found, the exit status is 127. If the
utility is found but cannot be executed, the exit status is 126.
With the -v
or -V
option, the exit status is 0 if the utility is found
and 1 if not found.
Examples
See Replacing existing utilities.
Compatibility
POSIX requires that the name operand be specified, but many implementations allow it to be omitted, in which case the built-in does nothing.
When the utility is not found with the -v
or -V
option, some
implementations return a non-zero exit status other than 1, especially 127.
When the utility is not found with the -V
option, some implementations
print an error message to the standard output while others to the standard
error.
Continue built-in
The continue
built-in skips the execution of a loop to the next iteration.
Synopsis
continue [n]
Description
continue n
interrupts the execution of the nth innermost for
, while
, or until
loop and resumes its next iteration. The specified loop must lexically enclose the continue command, that is:
- The loop is running in the same execution environment as the continue command; and
- The continue command appears inside the condition or body of the loop but not in the body of a function definition command appearing inside the loop.
It is an error if there is no loop enclosing the continue command. If n is greater than the number of enclosing loops, the built-in affects the outermost one.
If the affected loop is a for
loop, the loop variable is updated to the next value in the list. The loop ends if there are no more values to iterate over.
If the affected loop is a while
or until
loop, the condition is re-evaluated.
Options
None.
Operands
Operand n specifies the nest level of the affected loop. If omitted, it defaults to 1. It is an error if the value is not a positive decimal integer.
Exit status
Zero if the built-in successfully continues the loop; non-zero otherwise.
Examples
See Break and continue.
Compatibility
The continue
built-in is specified by POSIX.1-2024.
The behavior is unspecified in POSIX when the continue
built-in is used without an enclosing loop, in which case the current implementation returns an error.
POSIX allows the built-in to restart a loop running in the current execution environment that does not lexically enclose the continue command. Our implementation declines to do that.
In some shells, the continue
built-in lacks support for the --
separator.
Previous versions of yash supported the non-standard -i
option, but this is not yet supported in yash-rs.
Eval built-in
The eval
built-in evaluates the arguments as shell commands.
Synopsis
eval [command…]
Description
This built-in parses and executes the argument as a shell script in the current shell environment.
Options
None.
Operands
The operand is a command string to be evaluated. If more than one operand is given, they are concatenated with spaces between them to form a single command string.
Errors
During parsing and execution, any syntax error or runtime error may occur.
Exit status
The exit status of the eval
built-in is the exit status of the last
command executed in the command string.
If there is no command in the string, the exit status is zero.
In case of a syntax error, the exit status is 2.
Examples
See Evaluating command strings.
Security considerations
The eval
built-in can be dangerous if used with untrusted input, as it can execute arbitrary commands. It is recommended to avoid using eval
with user input or to sanitize the input before passing it to eval
.
Compatibility
The eval
built-in is specified by POSIX.1-2024.
In some shells, the eval
built-in lacks support for the --
separator, treating it as an operand.
Previous versions of yash supported the non-standard -i
option, but this is not yet supported in yash-rs.
Exec built-in
The exec
built-in replaces the current shell process with an external utility invoked by treating the specified operands as a command. Without operands, the built-in makes redirections applied to it permanent in the current shell environment.
Synopsis
exec [name [arguments...]]
Description
When invoked with operands, the exec
built-in replaces the currently executing shell process with a new process image, regarding the operands as command words to start the external utility. The first operand identifies the utility, and the other operands are passed to the utility as command-line arguments.
Without operands, the built-in does not start any utility. Instead, it makes any redirections performed in the calling simple command permanent in the current shell environment. (This is done even if there are operands, but the effect can be observed only when the utility cannot be invoked and the shell does not exit.)
Options
POSIX defines no options for the exec
built-in.
The following non-portable options are yet to be implemented:
--as
--clear
--cloexec
--force
--help
Operands
The operands are treated as a command to start an external utility. If any operands are given, the first is the utility name, and the others are its arguments.
If the utility name contains a slash character, the shell will treat it as a
path to the utility.
Otherwise, the shell will search $PATH
for the utility.
Errors
If the name operand is given, the named utility cannot be invoked, and the shell is not interactive, the current shell process will exit with an error.
Exit status
If the external utility is invoked successfully, it replaces the shell executing the built-in, so there is no exit status of the built-in. If the built-in fails to invoke the utility, the exit status will be 126. If there is no utility matching the first operand, the exit status will be 127.
If no operands are given, the exit status will be 0.
Examples
To make the current shell process run echo
:
$ exec echo "Hello, World!"
Hello, World!
Note that the echo
executed here is not the built-in, but the external utility found in the $PATH
. The shell process is replaced by the echo
process, so you don’t return to the shell prompt after the command.
See Persistent redirections for examples of using the exec
built-in to make redirections permanent.
Compatibility
The exec
built-in is part of POSIX.1-2024.
Exit built-in
The exit
built-in causes the currently executing shell to exit.
Synopsis
exit [exit_status]
Description
exit exit_status
makes the shell exit from the currently executing
environment with the specified exit status.
The shell executes the EXIT trap, if any, before exiting, except when the built-in is invoked in the trap itself.
Options
None. (TBD: non-portable extensions)
Operands
The optional exit_status operand, if given, should be a non-negative decimal integer and will be the exit status of the exiting shell process.
Errors
If the exit_status operand is given but not a valid non-negative integer, it is a syntax error.
This implementation treats an exit_status value greater than 2147483647 as a syntax error.
Exit status
The exit_status operand specifies the exit status of the exiting shell.
If the operand is not given, the shell exits with the current exit status
($?
). If the built-in is invoked in a trap, the exit status will be the
value of $?
before entering the trap.
In case of an error, the exit status is 2.
If the exit status indicates a signal that caused the process of the last command to terminate, the shell terminates with the same signal. See Exit status of the shell for details.
Examples
To exit the shell with exit status 42:
$ exit 42
Compatibility
The exit
built-in is specified by POSIX.1-2024.
In some shells, the exit
built-in lacks support for the --
separator.
The behavior is undefined in POSIX if exit_status is greater than 255. The current implementation passes such a value as is in the result, but this behavior may change in the future.
Export built-in
The export
built-in exports variables to the environment.
Synopsis
export [-p] [name[=value]…]
Description
The export
built-in (without the -p
option) exports variables of the specified names to the environment, with optional values. If no names are given, or if the -p
option is given, the names and values of all exported variables are displayed. If the -p
option is given with operands, only the specified variables are displayed.
Options
The -p
(--print
) option causes the shell to display the names and
values of all exported variables in a format that can be reused as input to
restore the state of these variables. When used with operands, the option
limits the output to the specified variables.
Operands
The operands are the names of variables to be exported or printed. When exporting, each name may optionally be followed by =
and a value to assign to the variable.
Standard output
When exporting variables, the export
built-in does not produce any output.
When printing variables, the built-in prints simple commands that invoke the export
built-in to reexport the variables with the same values. Note that the commands do not include options to restore the attributes of the variables, such as the -r
option to make variables read-only.
For array variables, the export
built-in invocation is preceded by a separate assignment command since the export
built-in does not support assigning values to array variables.
Errors
When exporting a variable with a value, it is an error if the variable is read-only.
When printing variables, it is an error if an operand names a non-existing variable.
Exit status
Zero unless an error occurs.
Examples
Compatibility
This built-in is part of the POSIX standard. Printing variables is portable
only when the -p
option is used without operands.
Previous versions of yash supported the non-standard -r
and -X
options, but these are not yet supported in yash-rs.
False built-in
The false
built-in does nothing, unsuccessfully.
Synopsis
false
Description
The false
built-in does nothing and returns a non-zero exit
status.
Options
None.
Operands
None.
Errors
None.
In the future, the built-in may detect unexpected options or operands.
Exit Status
1.
Examples
See And-or lists for examples of using false
in and-or lists. The examples of the getopts
built-in also use false
to indicate that an option is not specified.
Compatibility
The false
utility is specified by POSIX.1-2024.
POSIX allows false
to return any non-zero exit status, but most implementations return 1.
Most implementations ignore any arguments, but some implementations may respond to them. For example, the GNU coreutils implementation accepts the --help
and --version
options. For maximum portability, avoid passing arguments to false
.
Fg built-in
The fg
resumes a suspended job in the foreground.
Synopsis
fg [job_id]
Description
See Job control for an overview of job control in yash-rs. The built-in brings the specified job to the foreground and resumes its execution by sending the SIGCONT
signal to it. The built-in then waits for the job to finish (or suspend again).
If the resumed job finishes, it is removed from the job list. If the job gets suspended again, it is set as the current job.
Options
None.
Operands
Operand job_id specifies which job to resume. See Job IDs for the operand format. If omitted, the built-in resumes the current job.
Standard output
The built-in writes the selected job name to the standard output.
Errors
It is an error if:
- the specified job is not found,
- the specified job is not job-controlled, that is, job control was off when the job was started, or
- job control is off in the current shell environment.
Exit status
The built-in returns with the exit status of the resumed job. If the job is suspended, the exit status is as if the job had been terminated with the signal that suspended it. (See also Suspending foreground jobs.)
On error, it returns a non-zero exit status.
Examples
See Job control.
Compatibility
Many implementations allow omitting the leading %
from job IDs and specifying multiple job IDs at once, though this is not required by POSIX and not yet supported in yash-rs.
Getopts built-in
The getopts
built-in is used to parse options in shell scripts.
Synopsis
getopts option_spec variable_name [argument…]
Description
The getopts
built-in parses single-character options in the specified arguments according to the specified option specification, and assigns the parsed options to the specified variable. This built-in is meant to be used in the condition of a while
loop to iterate over the options in the arguments. Every invocation of the built-in parses the next option in the arguments. The built-in returns a non-zero exit status when there are no more options to parse.
The shell uses the OPTIND
variable to keep track of the current position
in the arguments. When the shell starts, the variable is initialized to 1
.
The built-in updates the variable to the index of the next argument to parse.
When all arguments are parsed, the built-in sets the variable to the index of
the first operand after the options, or to the number of arguments plus one
if there are no operands.
When the built-in parses an option, it sets the specified variable to the
option name. If the option takes an argument, the built-in also sets the
OPTARG
variable to the argument.
If the built-in encounters an option that is not listed in the option
specification, the specified variable is set to ?
. Additionally, if the
option specification starts with a colon (:
), the built-in sets the
OPTARG
variable to the encountered option character. Otherwise, the
built-in unsets the OPTARG
variable and prints an error message to the
standard error describing the invalid option.
If the built-in encounters an option that takes an argument but the argument
is missing, the error handling is similar to the case of an invalid option.
If the option specification starts with a colon, the built-in sets the
the specified variable to :
(not ?
) and sets the OPTARG
variable to
the option character. Otherwise, the built-in sets the specified variable to
?
, unsets the OPTARG
variable, and prints an error message to the
standard error describing the missing argument.
When there are no more options to parse, the specified variable is set to
?
and the OPTARG
variable is unset.
In repeated invocations of the built-in, you must pass the same arguments to
the built-in. You must not modify the OPTIND
variable between
invocations, either. Otherwise, the built-in may not be able to parse the
options correctly.
To start parsing a new set of options, you must reset the OPTIND
variable
to 1
before invoking the built-in.
Options
None.
Operands
The first operand (option_spec) is the option specification. It is a
string that contains the option characters the built-in parses. If a
character is followed by a colon (:
), the option takes an argument. If the
option specification starts with a colon, the built-in does not print an
error message when it encounters an invalid option or an option that is
missing an argument.
The second operand (variable_name) is the name of the variable to
which the built-in assigns the parsed option. In case of an invalid option
or an option that is missing an argument, the built-in assigns ?
or :
to
the variable (see above).
The remaining operands (argument…) are the arguments to parse. If there are no operands, the built-in parses the positional parameters.
Errors
The built-in may print an error message to the standard error when it encounters an invalid option or an option that is missing an argument (see the description above). However, this is not considered an error of the built-in itself.
The following conditions are considered errors of the built-in:
- The built-in is invoked with less than two operands.
- The second operand is not a valid variable name.
OPTIND
,OPTARG
, or the specified variable is read-only.- The built-in is re-invoked with different arguments or a different value
of
OPTIND
than the previous invocation (except whenOPTIND
is reset to1
). - The value of
OPTIND
is not1
on the first invocation.
Exit status
The built-in returns an exit status of zero if it parses an option, regardless of whether the option is valid or not. When there are no more options to parse, the exit status is one.
The exit status is two on error.
Examples
In the following example, the getopts
built-in parses three kinds of options
(-a
, -b
, and -c
), of which only -b
takes an argument. In case of an
error, the built-in prints an error message to the standard error, so the
script just exits with a non-zero exit status when $opt
is set to ?
.
a=false c=false
while getopts ab:c opt; do
case "$opt" in
a) a=true ;;
b) b="$OPTARG" ;;
c) c=true ;;
'?') exit 1 ;;
esac
done
shift "$((OPTIND - 1))"
if "$a"; then printf 'The -a option was specified\n'; fi
if [ "${b+set}" ]; then printf 'The -b option was specified with argument %s\n' "$b"; fi
if "$c"; then printf 'The -c option was specified\n'; fi
printf 'The remaining operands are: %s\n' "$*"
If you prefer to print an error message yourself, put a colon at the beginning of the option specification like this:
while getopts :ab:c opt; do
case "$opt" in
a) a=true ;;
b) b="$OPTARG" ;;
c) c=true ;;
'?') printf 'Invalid option: -%s\n' "$OPTARG" >&2; exit 1 ;;
:) printf 'Option -%s requires an argument\n' "$OPTARG" >&2; exit 1 ;;
esac
done
Compatibility
The getopts
built-in is specified by POSIX. Only ASCII alphanumeric
characters are allowed for option names, though this implementation allows
any characters but :
.
The current implementation considers variable names containing a =
as
invalid names. However, more names many be considered invalid in the future.
For best forward-compatibility and portability, only use portable name
characters (ASCII alphanumerics and underscore).
Although POSIX requires the built-in to support the Utility Syntax Guidelines 3 to 10, some implementations do not support the --
separator placed before operands to the built-in itself, that is, between the built-in name getopts
and the first operand option_spec.
The value of the OPTIND
variable is not portable until the built-in
finishes parsing all options. In this implementation, the value may
temporarily contain two integers separated by a colon. The first integer is
the index of the next argument to parse, and the second is the index of the
character in the argument to parse. Other implementations may use a
different scheme. Some sets OPTIND
to the index of the just-parsed
argument and uses a hidden variable to keep track of the character index.
The behavior is unspecified if you modify the OPTIND
variable between
invocations of the built-in or to a value other than 1
.
In other implementations, the exit status may be greater than two on error.
Jobs built-in
The jobs
built-in reports job status.
Synopsis
jobs [-l|-p] [job_id…]
Description
See Job control for an overview of job control in yash-rs. The jobs
built-in prints information about jobs the shell is currently controlling, one line for each job. The results follow the format specified by the POSIX.
When the built-in reports a finished job (either exited or signaled), it removes the job from the job list.
Format
An example output of the built-in in the default format is:
[1] - Running cargo build
[2] + Stopped(SIGTSTP) vim
[3] Done rm -rf /tmp/foo
The first column is the job number in brackets. The second column indicates the current job (+
) or the previous job (-
). The third column is the job state, which can be one of:
Running
: the job is running in the backgroundStopped(signal)
: the job is stopped by signalDone
: the job has finished with an exit status of zeroDone(n)
: the job has finished with non-zero exit status nKilled(signal)
: the job has been killed by signalKilled(signal: core dumped)
: the job has been killed by signal and a core dump was produced
The last column is the command line of the job.
Options
You can use two options to change the output.
The -l
(--verbose
) option displays additional details by inserting the process ID before each job state. The -p
(--pgid-only
) option outputs only the process ID of each job. In both cases, the process ID shown is that of the main process in the job, which is also the process group ID if the job is under job control.
Operands
Each operand is parsed as a job ID that specifies which job to report. If no operands are given, the built-in prints all jobs.
Errors
If an operand does not specify a valid job, the built-in prints an error message to the standard error and returns a non-zero exit status. An ambiguous job ID (matching multiple jobs) is also an error.
Exit status
Zero if successful, non-zero if an error occurred.
Examples
Job list includes an example of using the jobs
built-in to list jobs.
The built-in with different arguments:
$ vim
[1] + Stopped(SIGTSTP) vim
$ sleep 60 && echo "1 minute elapsed!"&
[2] 38776
$ jobs
[1] + Stopped(SIGTSTP) vim
[2] - Running sleep 60 && echo "1 minute elapsed!"
$ jobs -l
[1] + 37424 Stopped(SIGTSTP) vim
[2] - 38776 Running sleep 60 && echo "1 minute elapsed!"
$ jobs -p %2
38776
Compatibility
The output format may differ between shells. Specifically:
- For a job stopped by
SIGTSTP
, other shells may omit the signal name and simply printStopped
. - Other shells may report stopped jobs as
Suspended
instead ofStopped
. - The job state format for jobs terminated by a signal is implementation-defined.
- With the
-l
option, shells that manage more than one process per job may print an additional line containing the process ID and name for each process in the job.
The current implementation of this built-in removes finished jobs from the job list after reporting all jobs. This behavior should not be relied upon. The following script shows a “job not found” error in many other shells because the built-in removes the job when processing the first operand so the job is gone when the second is processed:
$ sleep 0&
$ jobs %sleep %sleep
When the built-in is used in a subshell, the built-in reports not only jobs that were started in the subshell but also jobs that were started in the parent shell. This behavior is not portable and is subject to change.
The POSIX standard only defines the -l
and -p
options. Previous versions of yash supported additional options, which are not yet implemented in yash-rs.
POSIX does not define the behavior when both -l
and -p
options are used together. In most other shells, the option specified last takes effect. In yash-rs, the -p
option takes precedence over -l
, but this may change in future versions. Later releases of yash-rs might instead reject conflicting options.
A portable job ID must start with a %
. If an operand does not have a leading %
, the built-in assumes one silently, which is not portable.
Kill built-in
The kill
built-in sends a signal to processes.
Synopsis
kill [-s SIGNAL|-n SIGNAL|-SIGNAL] target…
kill -l|-v [SIGNAL|exit_status]…
Description
Without the -l
or -v
option, the built-in sends a signal to processes.
With the -l
or -v
option, the built-in lists signal names or
descriptions.
Options
The -s
or -n
option specifies the signal to send. The signal name is case-insensitive, but must be specified without the SIG
prefix. The default signal is SIGTERM
. (Specifying a signal name with the SIG
prefix may be allowed in the future.)
The signal may be specified as a number instead of a name. If the number is zero, the built-in does not send a signal, but instead checks whether the shell can send the signal to the target processes.
The obsolete syntax allows the signal name or number to be specified
directly after the hyphen like -TERM
and -15
instead of -s TERM
and
-n 15
.
The -l
option lists signal names. The names are printed one per line,
without the SIG
prefix.
The -v
option implies and extends the -l
option by displaying the signal number before each name. The output format for -v
may be changed in the future to include signal descriptions as well.
Operands
Without the -l
or -v
option, the built-in takes one or more operands
that specify the target processes. Each operand is one of the following:
- A positive decimal integer, which should be a process ID
- A negative decimal integer, which should be a negated process group ID
0
, which means the current process group-1
, which means all processes- A job ID, which means the process group of the job
With the -l
or -v
option, the built-in may take operands that limit the
output to the specified signals. Each operand is one of the following:
- The exit status of a process that was terminated by a signal
- A signal number
- A signal name without the
SIG
prefix
Without operands, the -l
and -v
options list all signals.
Errors
It is an error if:
- The
-l
or-v
option is not specified and no target processes are specified. - A specified signal is not supported by the shell.
- A specified target process does not exist.
- The target job specified by a job ID operand is not job-controlled, that is, job control was off when the job was started.
- The signal cannot be sent to any of the target processes specified by an operand.
- An operand specified with the
-l
or-v
option does not identify a supported signal.
Exit status
The exit status is zero unless an error occurs. The exit status is zero if the signal is sent to at least one process for each operand, even if the signal cannot be sent to some of the processes.
Usage notes
When a target is specified as a job ID, the built-in cannot tell whether the job process group still exists. If the job process group has been terminated and another process group has been created with the same process group ID, the built-in will send the signal to the new process group.
Examples
Sending SIGTERM
to the last started asynchronous command and showing the signal name represented by the exit status:
$ sleep 10&
$ kill $!
$ wait $!
$ echo "Exit status $? corresponds to SIG$(kill -l $?)"
Exit status 399 corresponds to SIGTERM
Specifying a signal name and job ID:
$ set -m
$ sleep 10&
$ kill -s INT %1
$ wait %1
$ echo "Exit status $? corresponds to SIG$(kill -l $?)"
Exit status 386 corresponds to SIGINT
Specifying a signal number:
$ sleep 10&
$ kill -n 1 $!
$ wait $!
$ echo "Exit status $? corresponds to SIG$(kill -l $?)"
Exit status 385 corresponds to SIGHUP
The --
separator is needed if the first operand starts with a hyphen (a negated process group ID):
$ set -m
$ sleep 10&
$ kill -n 15 -- -$!
$ wait $!
$ echo "Exit status $? corresponds to SIG$(kill -l $?)"
Exit status 399 corresponds to SIGTERM
Compatibility
The kill
built-in is specified by POSIX.1-2024.
Specifying a signal number other than 0
to the -s
option is a
non-standard extension.
Specifying a signal number to the -n
option is a ksh extension. This
implementation also supports the -n
option with a signal name.
The kill -SIGNAL target…
form may not be parsed as expected by other
implementations when the signal name starts with an s
. For example, kill -stop 123
may try to send the SIGTOP
signal instead of the SIGSTOP
signal.
POSIX defines the following signal numbers:
0
(a dummy signal that can be used to check whether the shell can send a signal to a process)1
(SIGHUP
)2
(SIGINT
)3
(SIGQUIT
)6
(SIGABRT
)9
(SIGKILL
)14
(SIGALRM
)15
(SIGTERM
)
Other signal numbers are implementation-defined.
Using the -l
option with more than one operand is a non-standard
extension. Specifying a signal name operand to the -l
option is a
non-standard extension.
The -v
option is a non-standard extension.
Some implementations print 0
or EXIT
for kill -l 0
or kill -l EXIT
while this implementation regards them as invalid operands.
On some systems, a signal may have more than one name. There seems to be no
consensus whether kill -l
should print all names or just one name for each
signal. This implementation currently prints all names, but this behavior
may change in the future.
Pwd built-in
The pwd
built-in prints the working directory path.
Synopsis
pwd [-L|-P]
Description
The built-in prints the pathname of the current working directory followed by a newline to the standard output.
Options
With the -L
(--logical
) option, the printed path is the value of the PWD
variable if it is correct. The path may contain symbolic link components, but not .
or ..
components.
With the -P
(--physical
) option (or if PWD
is not correct), the built-in recomputes and prints the actual path to the working directory. The output excludes symbolic link components as well as .
and ..
components.
These two options are mutually exclusive. The last specified one applies if given both. The default is -L
.
Operands
None.
Errors
This built-in may fail for various reasons. For example:
- The working directory has been removed from the file system.
- You lack permission to access one or more ancestor directories required to determine the working directory’s path.
- The standard output is not writable.
Exit Status
Zero if the path was successfully printed; non-zero otherwise.
Compatibility
POSIX-1.2024 defines the pwd
utility with the -L
and -P
options.
POSIX allows pwd
to apply the -P
option if the -L
option is specified and PWD
is longer than PATH_MAX
.
The shell sets PWD
on the startup and modifies it in the cd
built-in. If PWD
is modified or unset otherwise, the behavior of cd
and pwd
is unspecified.
Read built-in
The read
built-in reads a line into variables.
Synopsis
read [-d delimiter] [-r] variable…
Description
The read
built-in reads a line from the standard input and assigns it to the variables named by the operands. Field splitting is performed on the line read to produce as many fields as there are variables. If there are fewer fields than variables, the remaining variables are set to empty strings. If there are more fields than variables, the last variable receives all remaining fields, including the field separators, but not trailing whitespace separators.
Non-default delimiters
By default, the built-in reads a line up to a newline character. The -d
option changes the delimiter to the character specified by the delimiter
value. If the delimiter
value is empty, the built-in reads a line up to the first nul byte.
Escaping
By default, backslashes in the input are treated as quoting characters that prevent the following character from being interpreted as a field separator. Backslash-newline pairs are treated as line continuations.
The -r
option disables this behavior.
Prompting
By default, the built-in does not display a prompt before reading a line.
When reading lines after the first line, the built-in displays the value of the PS2
variable as a prompt if the shell is interactive and the input is from a terminal. See Command prompt for details.
Options
The -d
(--delimiter
) option takes an argument and changes the
delimiter to the character specified by the argument. If the delimiter
value is empty, the read
built-in reads a line up to the first nul byte.
Multibyte characters are not supported.
The -r
(--raw-mode
) option disables the interpretation of
backslashes.
Operands
One or more operands are required. Each operand is the name of a variable to be assigned.
Errors
This built-in fails if:
- The standard input is not readable.
- The delimiter is not a single-byte character.
- The delimiter is not a nul byte and the input contains a nul byte.
- A variable name is not valid.
- A variable to be assigned is read-only.
Exit status
The exit status is zero if a line was read successfully and non-zero otherwise. If the built-in reaches the end of the input before finding a delimiter, the exit status is one, but the variables are still assigned with the line read so far. On other errors, the exit status is two or higher.
Examples
Reading a line from a file:
Reading all lines from a file:
Note that the < users.txt
redirection must be applied to the while loop, not to the read command. See redirection Semantic details for why.
Using a non-default field separator:
Reading a nul-terminated string:
Use the -r
option and an empty IFS
to read a line literally:
Compatibility
POSIX.1-2024 defines the read
built-in with the -d
and -r
options. Previous versions of yash supported additional options, which are not yet implemented in yash-rs.
In this implementation, a line continuation is always a backslash followed by a newline. Other implementations may allow a backslash followed by a delimiter to be a line continuation if the delimiter is not a newline.
When a backslash is specified as the delimiter, no escape sequences are recognized. Other implementations may recognize escape sequences in the input line, effectively never recognizing the delimiter.
In this implementation, the value of the PS2
variable is subject to parameter expansion, command substitution, and arithmetic expansion. Other implementations may not perform these expansions.
The current implementation considers variable names containing a =
as invalid names. However, more names may be considered invalid in the future. For best forward-compatibility and portability, only use portable name characters (ASCII alphanumerics and underscore).
In yash-rs and many other implementations, the read
built-in does not read more than needed to find a delimiter, so that a next command can read the remaining input without loss. POSIX.1-2024 requires this behavior only when the built-in is reading from a seekable input.
Readonly built-in
The readonly
built-in provides several operations related to read-only variables:
Making variables read-only
If the -p
(--print
) option is not specified and there are any operands, the built-in makes the specified variables read-only.
Synopsis
readonly name[=value]…
Options
None.
Operands
Operands specify the names and values of the variables to be made read-only. If an operand contains an equal sign (=
), the operand is split into the name and value at the first equal sign. The value is assigned to the variable named by the name. Otherwise, the variable named by the operand is created without a value unless it is already defined, in which case the existing value is retained.
If no operands are given, the built-in prints variables (see below).
Standard output
None.
Printing read-only variables
If the -p
(--print
) option is specified, the built-in prints the names and values of the variables named by the operands in the format that can be evaluated as shell code to recreate the variables. If there are no operands, the built-in prints all read-only variables in the same format.
Synopsis
readonly -p [name…]
readonly
Options
The -p
(--print
) option must be specified to print variables unless there are no operands.
Operands
Operands specify the names of the variables to be printed. If no operands are given, all read-only variables are printed.
Standard output
A command string that invokes the readonly
built-in to recreate the variable is printed for each read-only variable. Note that the command does not include options to restore the attributes of the variable, such as the -x
option to make variables exported.
Also note that evaluating the printed commands in the current shell session will fail (unless the variable is declared without a value) because the variable is already defined and read-only.
For array variables, the built-in invocation is preceded by a separate assignment command since the built-in does not support assigning values to array variables.
Errors
When making a variable read-only with a value, it is an error if the variable is already read-only.
When printing variables, it is an error if an operand names a non-existing variable.
Exit status
Zero if successful, non-zero if an error occurred.
Examples
$ readonly foo='Hello, world!'
$ echo "$foo"
Hello, world!
$ readonly
readonly foo='Hello, world!'
$ foo='Goodbye, world!'
error: error assigning to variable
--> <stdin>:4:1
|
4 | foo='Goodbye, world!'
| ^^^^^^^^^^^^^^^^^^^^^ cannot assign to read-only variable "foo"
|
::: <stdin>:1:10
|
1 | readonly foo='Hello, world!'
| ------------------- info: the variable was made read-only here
|
Compatibility
This built-in is part of the POSIX standard. Printing variables is portable only when the -p
option is used without operands.
Return built-in
The return
built-in quits the currently executing innermost function or script.
Synopsis
return [-n] [exit_status]
Description
return exit_status
makes the shell return from the currently executing
function or script with the specified exit status.
If the shell is not executing a function or script, the built-in will work like exit
, but this behavior may change in the future.
Options
The -n
(--no-return
) option makes the built-in not actually quit
a function or script. This option will be helpful when you want to set the
exit status to an arbitrary value without any other side effect.
Operands
The optional exit_status operand, if given, should be a non-negative decimal integer and will be the exit status of the built-in.
Errors
If the exit_status operand is given but not a valid non-negative integer, it is a syntax error.
This implementation treats an exit_status value greater than 2147483647 as a syntax error.
Exit status
The exit_status operand will be the exit status of the built-in.
If the operand is not given, the exit status will be the current exit status ($?
). If the built-in is invoked in a trap executed in a function or script and the built-in returns from that function or script, the exit status will be the value of $?
before entering the trap.
In case of an error, the exit status is 2.
Examples
Compatibility
The return
built-in is specified by POSIX.1-2024.
POSIX only requires the return
built-in to quit a function or sourced script.
The behavior for other kinds of scripts is a non-standard extension.
In some shells, the return
built-in lacks support for the --
separator.
The -n
(--no-return
) option is a non-standard extension.
The behavior is unspecified in POSIX if exit_status is greater than 255. The current implementation passes such a value as is in the result, but this behavior may change in the future.
Set built-in
The set
built-in modifies shell options and positional parameters. It also can print a list of current options or variables.
Description
The built-in behaves differently depending on the invocation syntax.
Printing variables
When executed without any arguments, the built-in prints a list of variables visible in the current execution environment. The list is formatted as a sequence of simple commands performing an assignment that would restore the present variables if executed (unless the assignment fails because of a read-only variable). The list is ordered alphabetically.
$ set
HOME=/home/alice
OPTIND=1
LOGNAME=alice
PATH=/usr/local/bin:/usr/bin:/bin
PWD=/home/alice/my_project
TERM=xterm
Some built-ins allow creating variables that do not have a valid name. For example, export 1a=foo
defines a variable named 1a
, which cannot be assigned to with the normal assignment syntax of the simple command. Such variables are not printed by the set
built-in.
Printing shell options
If you specify the -o
option as a unique argument to the set built-in, it
prints the current shell option settings in a human-readable format:
$ set -o
allexport off
clobber on
cmdline off
errexit off
exec on
...
If you use the +o
option instead, the printing lists shell commands that
would restore the current option settings if executed:
$ set +o
set +o allexport
set -o clobber
#set +o cmdline
set +o errexit
set -o exec
...
Modifying shell options
Other command line options modify shell option settings. They can be specified in the short form like -e
or the long form like -o errexit
and --errexit
.
You can also specify options starting with +
in place of -
, as in +e
, +o errexit
, and ++errexit
. The -
options turn on the corresponding shell options while the +
options turn off.
See Enabling and disabling options for the full details on the option syntax. Available options are listed in the Option list.
You cannot modify the following options with the set
built-in:
cmdline
(-c
)interactive
(-i
)stdin
(-s
)
Modifying positional parameters
If you specify one or more operands, they will be new positional parameters in the current shell environment, replacing any existing positional parameters.
See Modifying positional parameters for examples of how to set positional parameters.
Option-operand separator
As with other utilities conforming to POSIX XBD Utility Syntax Guidelines, the set built-in accepts --
as a separator between options and operands. Additionally, yash-rs also accepts -
as a separator.
$ set -o errexit -- foo bar
$ echo "$1" "$2"
foo bar
If you place a separator without any operands, the built-in will clear all positional parameters.
$ set --
$ echo $#
0
Exit status
- 0: successful
- 1: error printing output
- 2: invalid options
Compatibility
The set
built-in is specified by POSIX.1-2024. See Compatibility for the compatibility of the option syntax and available options.
The output format of set -o
and set +o
depends on the shell.
The semantics of -
as an option-operand separator is unspecified in POSIX. You should prefer --
. Older versions of yash treated -
simply as an operand.
Many (but not all) shells specially treat +
, especially when it appears in
place of an option-operand separator. Yash does not treat +
specially, so it can be used as an operand without another separator.
Other implementations may return different non-zero exit statuses for errors.
Shift built-in
The shift
built-in removes some positional parameters.
Synopsis
shift [n]
Description
The built-in removes the first n positional parameters from the list of
positional parameters. If n is omitted, it is assumed to be 1
.
Options
None.
Operands
The operand specifies the number of positional parameters to remove. It must be a non-negative decimal integer less than or equal to the number of positional parameters.
Errors
It is an error to try to remove more than the number of existing positional parameters.
Exit status
Zero if successful, non-zero if an error occurred.
Examples
Modifying positional parameters includes an example of the shift
built-in.
Compatibility
The shift
built-in is part of POSIX.1-2024.
In some shells, the shift
built-in lacks support for the --
separator.
Source (.) built-in
The source
(.
) built-in reads and executes commands from a file.
Synopsis
. file [arguments…]
source file [arguments…]
Description
The .
built-in reads and executes commands from the specified file in the
current shell environment.
If the filename does not contain a slash, the shell searches the directories in the PATH
variable for the file. The search is similar to command search, but the file does not need to be
executable; any readable file found first is used.
Options
None.
Operands
The first operand file must be given and is the pathname of the file to be executed. If it does not contain a slash, it is subject to the search described above.
Any additional arguments are currently ignored. Future versions may support assigning these to the positional parameters.
Errors
It is an error if the file cannot be found or read. During parsing and execution, any syntax error or runtime error may occur.
Exit status
The exit status of the built-in is the exit status of the last command executed in the file. If there is no command in the file, the exit status is zero.
If the file cannot be found or read, the exit status is 1. In case of a syntax error in the file, the exit status is 2.
Examples
See Reading and executing files.
Compatibility
The .
built-in is specified in the POSIX standard. The built-in name source
is a non-standard extension that is also available in some other shells.
POSIX defines no options for the .
built-in, but previous versions of yash supported additional options, which are not yet implemented in yash-rs.
If the pathname of the file does not contain a slash and the file is not found in the command search, some shells may fall back to the file in the current working directory. This is a non-portable extension that is not specified in POSIX. The portable way to specify a file in the current working directory is to prefix the filename with ./
as in . ./foo.sh
.
Setting the positional parameters with additional operands is a non-standard extension that is supported by some other shells. The behavior about the local variable context may differ in other shells.
Other implementations may return a different non-zero exit status for an error.
According to POSIX.1-2024, “An unrecoverable read error while reading from the file operand of the dot special built-in shall be treated as a special built-in utility error.” This means that if you use the .
built-in through the command
built-in and an unrecoverable read error occurs, the shell should not exit immediately. However, yash-rs does not currently support this behavior: if an unrecoverable read error happens and the shell is not running interactively, yash-rs will always exit. See Shell errors for the conditions under which the shell should exit.
Times built-in
The times
built-in is used to display the accumulated user and system
times for the shell and its children.
Synopsis
times
Description
The built-in prints the accumulated user and system times for the shell and its children.
Options
None.
Operands
None.
Standard output
Two lines are printed to the standard output, each in the following format:
1m2.345678s 3m4.567890s
The first field of each line is the user time, and the second field is the system time. The first line shows the times consumed by the shell itself, and the second line shows the times consumed by its child processes.
Errors
It is an error if the times cannot be obtained or the standard output is not writable.
Exit status
Zero unless an error occurred.
Compatibility
The times
built-in is defined in POSIX.
POSIX requires each field to be printed with six digits after the decimal point, but many implementations print less. Note that the number of digits does not necessarily indicate the precision of the times.
Trap built-in
The trap
built-in sets or prints traps.
Synopsis
trap [action] condition…
trap [-p [condition…]]
Description
The trap
built-in can be used to either set or print traps. To set traps, pass an action and one or more conditions as operands. To print the currently configured traps, invoke the built-in with no operands or with the -p
option.
Setting traps
When setting traps, the built-in sets the action for each condition in the current shell environment. To set different actions for multiple conditions, invoke the built-in for each action.
Printing traps
When the built-in is invoked with no operands, it prints the currently configured traps in the format trap -- action condition
where action and condition are properly quoted so that the output can be read by the shell to restore the traps. By default, the built-in prints traps that have non-default actions. To print all traps, use the -p
option with no operands.
When the -p
option is used with one or more conditions, the built-in prints the traps for the specified conditions.
When a subshell is entered, traps with a custom action are reset to the default action. This behavior would make it impossible to save the current traps by using a command substitution as in traps=$(trap)
. To make this work, when the built-in is invoked in a subshell and no traps have been modified in the subshell, it prints the traps that were configured in the parent shell.
Options
The -p
(--print
) option prints the traps configured in the shell environment.
Operands
An action specifies what to do when the trap condition is met. It may be one of the following:
-
(hyphen) resets the trap to the default action.- An empty string ignores the trap.
- Any other string is treated as a command to execute.
The action may be omitted if the first condition is a non-negative decimal integer. In this case, the built-in resets the trap to the default action.
A condition specifies when the action is triggered. It may be one of the following:
- A symbolic name of a signal without the
SIG
prefix (e.g.INT
,QUIT
,TERM
)- Signal names must be specified in uppercase. Lowercase names and the
SIG
prefix may be supported in the future.
- Signal names must be specified in uppercase. Lowercase names and the
- A positive decimal integer representing a signal number
- The number
0
or the symbolic nameEXIT
representing the termination of the main shell process- This condition is not triggered when the shell exits due to a signal.
Errors
Traps cannot be set to SIGKILL
or SIGSTOP
.
Invalid conditions are reported with a non-zero exit status, but it is not a shell error.
If a non-interactive shell inherits a signal disposition that ignores a signal, it does not permit modifying the trap for that signal. In yash-rs, this restriction is enforced silently and does not affect the exit status of the built-in.
Exit status
Zero if successful, non-zero if an error is reported.
Examples
Setting a trap and reacting to a self-sent signal:
$ trap 'echo "Caught SIGINT"; exit' INT
$ kill -s INT $$
Caught SIGINT
Ignoring a signal:
$ trap '' INT
$ kill -s INT $$
$ echo "The shell survived the INT signal."
The shell survived the INT signal.
Showing traps:
$ trap '' INT QUIT; trap 'echo "Caught SIGTERM"' TERM
$ trap
trap -- '' INT
trap -- '' QUIT
trap -- 'echo "Caught SIGTERM"' TERM
$ trap -p INT
trap -- '' INT
Compatibility
The trap
built-in is specified by POSIX.1-2024.
Portable scripts should specify signals in uppercase letters without the SIG
prefix. Specifying signals by numbers is discouraged as signal numbers vary among systems.
The result of setting a trap to SIGKILL
or SIGSTOP
is undefined by POSIX.
The mechanism for the built-in to print traps configured in the parent shell may vary among shells. This implementation remembers the old traps internally when starting a subshell and prints them when the built-in is invoked in the subshell. POSIX allows another scheme: When starting a subshell, the shell checks if the subshell command contains only a single invocation of the trap
built-in, in which case the shell skips resetting traps on the subshell entry so that the built-in can print the traps configured in the parent shell. The check may be done by a simple literal comparison, so you should not expect the shell to recognize complex expressions such as cmd=trap; traps=$($cmd)
.
In other shells, the EXIT
condition may be triggered when the shell is terminated by a signal.
True built-in
The true
built-in does nothing, successfully.
Synopsis
true
Description
The true
built-in does nothing, successfully. It is useful as a placeholder when a command is required but no action is needed.
Options
None.
Operands
None.
Errors
None.
In the future, the built-in may detect unexpected options or operands.
Exit Status
Zero.
Examples
See And-or lists for examples of using true
in and-or lists. The examples of the getopts
built-in also use true
to indicate that an option is specified.
Compatibility
The true
utility is specified by POSIX.1-2024.
Most implementations ignore any arguments, but some implementations respond to them. For example, the GNU coreutils implementation accepts the --help
and --version
options. For maximum portability, avoid passing arguments to true
. To pass and ignore arguments, use the :
(colon) built-in instead.
Type built-in
The type
built-in identifies the type of commands.
Synopsis
type [name…]
Description
The type
built-in prints the description of the specified command names.
Options
None.
Operands
The name operands specify the command names to identify.
Standard output
The command descriptions are printed to the standard output.
Errors
It is an error if the name is not found.
Exit status
The exit status is zero if all the names are found, and non-zero otherwise.
Examples
Compatibility
The type
built-in is specified by POSIX.1-2024.
POSIX defines no options for the type
built-in, but previous versions of yash supported additional options, which are not yet implemented in yash-rs.
In dash, the --
separator is treated as an operand.
POSIX requires that the name operand be specified, but many implementations allow it to be omitted, in which case the built-in does nothing.
The format of the output is unspecified by POSIX. In this implementation, the type
built-in is equivalent to the command
built-in with the -V
option.
Typeset built-in
The typeset
built-in provides several operations related to variables and functions:
Defining variables
If neither the -p
(--print
) nor the -f
(--functions
) option is specified and there are operands, the built-in defines variables named by the operands.
You can specify additional options to set the scope and attributes of the variables.
Synopsis
typeset [-grx] [+rx] name[=value]…
Options
By default, the built-in creates or updates variables locally within the current function. If the -g
(--global
) option is specified, the built-in affects existing variables visible in the current scope (which may be outside the current function) or creates new variables globally.
The following options set variable attributes:
-r
(--readonly
): Makes the variables read-only.-x
(--export
): Exports the variables to the environment.
To remove these attributes, use the corresponding option with a plus sign (+
) instead of a minus sign (-
). For example, the following commands stop exporting the variable foo
:
typeset +x foo
typeset ++export foo
Note: The read-only attribute cannot be removed. Using the +r
option to read-only variables causes an error.
Operands
Operands specify the names and values of the variables to define. If an operand contains an equal sign (=
), it is split at the first equal sign into a name and a value. The value is assigned to the variable with that name. If an operand does not contain an equal sign, the variable is created without a value unless it already exists, in which case its value is retained.
If no operands are given, the built-in prints variables (see below).
Standard output
None.
Examples
See Local variables for examples of defining local variables.
The following example demonstrates defining a local read-only variable:
$ greet() {
> typeset -r user=Alice
> echo "Hello, $user!"
> }
$ greet
Hello, Alice!
$ user=Bob
$ echo "Now the user is $user."
Now the user is Bob.
Printing variables
If the -p
(--print
) option is specified and the -f
(--functions
) option is not, the built-in prints the attributes and values of the variables named by the operands, using a format that can be evaluated as shell code to recreate the variables. If there are no operands and the -f
(--functions
) option is not specified, the built-in prints all variables in the same format, in alphabetical order.
Synopsis
typeset -p [-grx] [+rx] [name…]
typeset [-grx] [+rx]
Options
The -p
(--print
) option must be specified to print variables when operands are given. Otherwise, the built-in defines variables. If there are no operands, the option may be omitted.
By default, the built-in prints variables in the current context. If the -g
(--global
) option is specified, it prints variables visible in the current scope (which may be outside the current function).
The following options filter which variables are printed. Variables that do not match the criteria are ignored.
-r
(--readonly
): Prints only read-only variables.-x
(--export
): Prints only exported variables.
If these options are negated by using a plus sign (+
) instead of a minus sign (-
), the built-in prints variables that do not have the corresponding attribute. For example, the following command shows all non-exported variables:
typeset -p +x
Operands
Operands specify the names of the variables to print. If no operands are given, the built-in prints all variables that match the selection criteria.
Standard output
For each variable, a command string that invokes the typeset
built-in to recreate the variable is printed. For array variables, a separate assignment command precedes the typeset
command, since the built-in does not support assigning values to arrays. In this case, the typeset
command is omitted if no options are applied to the variable.
Note: Evaluating the printed commands in the current context may fail if variables are read-only, since read-only variables cannot be assigned values.
Examples
$ foo='some value that contains spaces'
$ bar=(this is a readonly array)
$ typeset -r bar
$ typeset -p foo bar
typeset foo='some value that contains spaces'
bar=(this is a readonly array)
typeset -r bar
Modifying function attributes
If the -f
(--functions
) option is specified, the -p
(--print
) option is not specified, and there are operands, the built-in modifies the attributes of functions named by the operands.
Synopsis
typeset -f [-r] [+r] name…
Options
The -f
(--functions
) option is required to modify functions. Otherwise, the built-in defines variables.
The -r
(--readonly
) option makes the functions read-only. If this option is not specified, the built-in does nothing.
The built-in accepts the +r
(++readonly
) option, but it has no effect since the read-only attribute cannot be removed.
Operands
Operands specify the names of the functions to modify. If no operands are given, the built-in prints functions (see below).
Note: The built-in operates only on existing functions. It cannot create new functions or change the body of existing functions.
Standard output
None.
Examples
See Read-only functions.
Printing functions
If both the -f
(--functions
) and -p
(--print
) options are specified, the built-in prints the attributes and definitions of the functions named by the operands, using a format that can be evaluated as shell code to recreate the functions. If there are no operands and the -f
(--functions
) option is specified, the built-in prints all functions in the same format, in alphabetical order.
Synopsis
typeset -fp [-r] [+r] [name…]
typeset -f [-r] [+r]
Options
The -f
(--functions
) and -p
(--print
) options must both be specified to print functions when operands are given. Otherwise, the built-in modifies functions. The -p
(--print
) option may be omitted if there are no operands.
The -r
(--readonly
) option can be specified to limit the output to read-only functions. If this option is negated as +r
(++readonly
), the built-in prints functions that are not read-only. If the option is not specified, all functions are printed.
Operands
Operands specify the names of the functions to print. If no operands are given, the built-in prints all functions that match the selection criteria.
Standard output
For each function, a function definition command is printed, which may be followed by a typeset
command to set the function’s attributes. The output is formatted by the shell and may differ from the original function definition.
Note: Evaluating the printed commands in the current shell environment may fail if functions are read-only, since read-only functions cannot be redefined.
Currently, yash-rs does not print the contents of here-documents. Functions containing here-documents are not correctly recreated when the output is evaluated.
Examples
See Showing function definitions.
Errors
The read-only attribute cannot be removed from a variable or function. If a variable is already read-only, you cannot assign a value to it.
It is an error to modify a non-existent function.
When printing variables or functions, it is an error if an operand names a non-existent variable or function.
Exit status
Zero if successful; non-zero if an error occurs.
Additional notes
The -g
(--global
) option has no effect if the built-in is used outside a function.
Compatibility
The typeset
built-in is not specified by POSIX, and many shells implement it differently. This implementation is based on common characteristics found in other shells, but it is not fully compatible with any of them.
Some implementations allow operating on variables and functions at the same time. This implementation does not.
This implementation requires the -g
(--global
) option to print variables defined outside the current function. Other implementations may print such variables by default.
This implementation allows hiding a read-only variable defined outside the current function by introducing a variable with the same name in the current function. Other implementations may not allow this.
Historical versions of yash performed assignments when operands of the form name=value
were given, even if the -p
option was specified. This implementation treats such usage as an error.
Historical versions of yash used the -X
(--unexport
) option to negate the -x
(--export
) option. This is now deprecated because its behavior was incompatible with other implementations. Use the +x
(++export
) option instead.
Ulimit built-in
The ulimit
built-in displays or sets system resource limits for the current shell process.
Synopsis
ulimit [-SH] [-a|-b|-c|-d|-e|-f|-i|-k|-l|-m|-n|-q|-R|-r|-s|-t|-u|-v|-w|-x] [limit]
Description
The ulimit
built-in allows you to view or change resource limits imposed by the operating system on the shell and its child processes. The limit operand specifies the new value for a resource limit. If limit is omitted, the current value is displayed.
Each resource has two limits:
- Soft limit: The value enforced by the kernel for the process. It can be increased up to the hard limit.
- Hard limit: The maximum value to which the soft limit can be set. Any process can lower its hard limit, but only privileged processes can raise it.
Options
-S
(--soft
): Set or display the soft limit.-H
(--hard
): Set or display the hard limit.
If neither -S
nor -H
is specified:
- When setting a limit, both soft and hard limits are changed.
- When displaying a limit, only the soft limit is shown.
Specifying both -S
and -H
together sets both limits. However, when displaying limits, using both options is an error.
Resource selection
Specify a resource to set or display using one of the following options. Supported resources may vary by platform, so not all options are available everywhere:
-b
(--sbsize
): maximum size of the socket buffer (bytes)-c
(--core
): maximum size of a core file created by a terminated process (512-byte blocks)-d
(--data
): maximum size of a data segment of the process (kilobytes)-e
(--nice
): maximum process priority (see below)-f
(--fsize
): maximum size of a file the process can create (512-byte blocks)-i
(--sigpending
): maximum number of signals that can be queued to the process-k
(--kqueues
): maximum number of kernel event queues-l
(--memlock
): maximum size of memory locked into RAM (kilobytes)-m
(--rss
): maximum physical memory size of the process (kilobytes)-n
(--nofile
): maximum number of open files in the process-q
(--msgqueue
): maximum total size of POSIX message queues-R
(--rttime
): maximum amount of CPU time the process can consume in real-time scheduling mode without a blocking system call (microseconds)-r
(--rtprio
): maximum real-time priority-s
(--stack
): maximum size of the process’s stack (kilobytes)-t
(--cpu
): maximum amount of CPU time the process can consume (seconds)-u
(--nproc
): maximum number of processes the user can run-v
(--as
): maximum total memory size of the process (kilobytes)-w
(--swap
): maximum size of the swap space the user can occupy (kilobytes)-x
(--locks
): maximum number of file locks the process can hold
The limit operand and output values use the units shown in parentheses above.
If no resource option is specified, ulimit
defaults to -f
(--fsize
).
For -e
(--nice
), the limit value sets the lowest nice value allowed, using the formula: nice = 20 - limit
. Lower nice values mean higher priority. For example, ulimit -e 25
allows the nice value to be lowered to -5.
To display all resource limits, use -a
(--all
). This cannot be combined with a limit operand.
Operands
The limit operand sets a new value for the selected resource. It is interpreted as follows:
- A non-negative integer sets the limit to that value.
unlimited
sets the limit to the maximum allowed.hard
sets the limit to the current hard limit.soft
sets the limit to the current soft limit.
Standard output
If limit is omitted, the built-in prints the current value for the selected resource. It is either a non-negative integer or unlimited
.
With -a
, it prints all resource limits in a table that includes the short option name, the resource name, and the current limit value for each resource.
Errors
The built-in fails if:
- The specified resource is unsupported on the current platform.
- The soft limit is set higher than the hard limit.
- The hard limit is set above the current hard limit without sufficient privileges.
- The limit operand is out of range.
- Both
-S
and-H
are specified without a limit operand. - More than one resource option is specified.
Exit status
Zero if successful; non-zero if an error occurs.
Examples
Setting resource limits:
Showing resource limits:
Showing all resource limits:
$ ulimit -a
-v: virtual address space size (KiB) unlimited
-c: core dump size (512-byte blocks) 0
-t: CPU time (seconds) unlimited
-d: data segment size (KiB) unlimited
-f: file size (512-byte blocks) unlimited
-x: number of file locks unlimited
-l: locked memory size (KiB) 65536
-q: message queue size (bytes) 819200
-e: process priority (20 - nice) 0
-n: number of open files 1024
-u: number of processes 62113
-m: resident set size (KiB) unlimited
-r: real-time priority 0
-R: real-time timeout (microseconds) unlimited
-i: number of pending signals 62113
-s: stack size (KiB) 8192
Compatibility
The ulimit
built-in is specified by POSIX.1-2024, but some behaviors are implementation-defined.
Only these options are required by POSIX: -H
, -S
, -a
, -c
, -d
, -f
, -n
, -s
, -t
, and -v
. Other options are extensions.
Some shells do not allow combining options (e.g., ulimit -fH
). For portability, specify options separately (e.g., ulimit -f -H
).
Shells differ in behavior when both -H
and -S
are given. Yash-rs sets or displays both limits; older versions of yash only honored the last one.
Specifying multiple resource options is an error in yash-rs, but some shells allow operating on multiple resources at once.
The hard
and soft
values for the limit operand are not defined by POSIX.
The output format for ulimit -a
is implementation-defined and subject to change.
Umask built-in
The umask
built-in shows or sets the file mode creation mask.
Synopsis
umask [-S] [mode]
Description
The built-in shows the current file mode creation mask if no mode is given. Otherwise, it sets the file mode creation mask to mode.
The file mode creation mask is a set of permissions that determines the default permissions for newly created files. In the numeric form, it is represented as a three-digit octal number, where each digit represents the permissions for the user, group, and others, respectively. For example, if the mask is set to 077
, all permissions for group and others are removed, and only the user can have read, write, and execute permissions.
$ umask 077
$ mkdir foo
$ echo "Hello, world!" > greet.txt
$ ls -do foo greet.txt
drwx------ 2 user 4096 Aug 11 13:07 foo
-rw------- 1 user 14 Aug 11 13:07 greet.txt
If the mask is 000
, all permissions are granted to the user, group, and others.
$ umask 000
$ mkdir foo
$ echo "Hello, world!" > greet.txt
$ ls -do foo greet.txt
drwxrwxrwx 2 user 4096 Aug 11 13:07 foo
-rw-rw-rw- 1 user 14 Aug 11 13:07 greet.txt
Note that execute permissions are not granted for the text files created by the redirections because redirection omits the execute permission when creating files. In general, the actual permissions set for files may be more restrictive than what the current mask suggests.
Options
The -S
(--symbolic
) option causes the built-in to show the current file mode creation mask in symbolic notation. Without this option, the mask is shown in octal notation.
Operands
mode is an octal integer or a symbolic notation that represents the file mode creation mask. The octal number is the bitwise OR of the file mode bits to be turned off when creating a file. The symbolic notation specifies the file mode bits to be kept on when creating a file.
Numeric notation
The numeric notation is a three-digit octal number, where each digit represents the permissions for the user, group, and others, respectively. Each digit can be a value from 0 to 7, which is a sum of:
- 4 for read permission,
- 2 for write permission,
- 1 for execute permission.
For example, the mask 027
removes write permission for the group and all permissions for others, while granting all permissions to the user.
Symbolic notation
The symbolic notation consists of one or more clauses separated by commas. Each clause consists of a (possibly empty) sequence of who symbols followed by one or more actions. The who symbols are:
u
for the user bits,g
for the group bits,o
for the other bits, anda
for all bits.
An action is an operator optionally followed by permission symbols. The operators are:
+
to add the permission,-
to remove the permission, and=
to set the permission.
The permission symbols are:
- one or more of:
r
for the read permission,w
for the write permission,x
for the execute permission,X
for the execute permission if the execute permission is already set for any who, ands
for the set-user-ID-on-execution and set-group-ID-on-execution bits.
u
for the current user permission,g
for the current group permission, ando
for the current other permission.
For example, the symbolic notation u=rwx,go+r-w
:
- sets the user bits to read, write, and execute,
- adds the read permission to the group and other bits, and
- removes the write permission from the group and other bits.
A symbolic mode that starts with -
may be confused as an option. To avoid this, use umask -- -w
or umask a-w
instead of umask -w
.
Standard output
If no mode is given, the built-in prints the current file mode creation mask in octal notation followed by a newline to the standard output. If the -S
option is effective, the mask is formatted in symbolic notation instead.
Errors
It is an error if the specified mode is not a valid file mode creation mask.
Exit status
Zero if successful; non-zero if an error occurs.
Examples
Setting and showing the file mode creation mask:
$ umask 027
$ umask
027
Using symbolic notation:
$ umask ug=rwx,g-w,o=
$ umask -S
u=rwx,g=rx,o=
Security considerations
If the file mode creation mask is too permissive, sensitive files may become accessible to unauthorized users. It is generally recommended to remove write permissions for group and other users.
Compatibility
The umask
built-in is defined in POSIX.1-2024.
POSIX does not specify the default output format used when the -S
option is not given. Yash-rs, as well as many others, uses octal notation. In any cases, the output can be reused as mode to restore the previous mask.
This implementation ignores the -S
option if mode is given. However, bash prints the new mask in symbolic notation if the -S
option and mode are both given.
An empty sequence of who symbols is equivalent to a
in this implementation as well as many others. However, this may not be strictly true to the POSIX specification.
The permission symbols other than r
, w
, and x
are not widely supported. This implementation currently ignores the s
symbol.
Unalias built-in
The unalias
built-in removes alias definitions.
Synopsis
unalias name…
unalias -a
Description
The unalias built-in removes alias definitions as specified by the operands.
Options
The -a
(--all
) option removes all alias definitions.
Operands
Each operand must be the name of an alias to remove.
Errors
It is an error if an operand names a non-existent alias.
Exit status
Zero unless an error occurs.
Examples
$ alias greet='echo Hello,'
$ greet world
Hello, world
$ unalias greet
$ greet world
error: cannot execute external utility "greet"
--> <stdin>:4:1
|
4 | greet world
| ^^^^^ utility not found
|
Compatibility
The unalias
built-in is specified in POSIX.
Some shells implement some built-in utilities as predefined aliases. Using unalias -a
may make such built-ins unavailable.
Unset built-in
The unset
built-in unsets variables or functions.
Synopsis
unset [-f|-v] name…
Description
The built-in unsets variables or functions named by the operands.
Options
Either of the following options may be used to select what to unset:
- The
-v
(--variables
) option unsets variables. This is the default behavior. - The
-f
(--functions
) option unsets functions.
Operands
Operands are the names of variables or functions to unset.
Errors
Unsetting a read-only variable or function is an error.
It is not an error to unset a variable or function that is not set. The built-in ignores such operands.
Exit status
Zero if successful; non-zero if an error occurs.
Examples
See Removing variables and Removing functions.
Compatibility
The unset
built-in is specified by POSIX.1-2024.
The behavior is not portable when both -f
and -v
are specified. Earlier versions of yash used to honor the last specified option, but this version errors out.
If neither -f
nor -v
is specified and the variable named by an operand is not set, POSIX allows the built-in to unset the same-named function if it exists. Yash does not do this.
POSIX requires that at least one operand be specified. Yash-rs currently does not detect this and allows the built-in to be called without any operands, but this may change in the future.
When a global variable is hidden by a local variable, the current implementation unsets the both. This is not portable. Old versions of yash used to unset the local variable only.
Wait built-in
The wait
built-in waits for jobs to finish.
Synopsis
wait [job_id_or_process_id…]
Description
See Job control for an overview of job control in yash-rs, though this built-in can also be used for asynchronous commands without job control. If you specify one or more operands, the built-in waits for the specified jobs to finish. Otherwise, the built-in waits for all existing jobs. If the jobs are already finished, the built-in returns without waiting.
If you attempt to wait for a suspended job, the built-in will wait indefinitely until the job is resumed and completes. Currently, there is no way to cancel a wait in progress. When job control is enabled, it is often preferable to use fg
instead of wait
, as fg
allows you to interact with the job—including suspending or interrupting it.
In the future, the shell may provide a way to cancel a wait in progress or treat a suspended job as if it were finished.
Options
None.
Operands
An operand can be a job ID or decimal process ID, specifying which job to wait for. A process ID is a non-negative decimal integer.
If there is no job matching the operand, the built-in assumes that the job has already finished with exit status 127.
Errors
The following error conditions causes the built-in to return a non-zero exit status without waiting for any job:
If the shell receives a signal that has a trap action set, the trap action is executed and the built-in returns immediately.
Exit status
If you specify one or more operands, the built-in returns the exit status of the job specified by the last operand. If there is no operand, the exit status is 0 regardless of the awaited jobs.
If the built-in was interrupted by a signal, the exit status indicates the signal.
The exit status is between 1 and 126 (inclusive) for any other error.
Examples
See The wait
utility for an example of using the wait
built-in without job control.
Using wait
with job control and examining the exit status:
$ set -m
$ sleep 10&
$ kill %
$ wait %
$ echo "Exit status $? corresponds to SIG$(kill -l $?)"
Exit status 399 corresponds to SIGTERM
Subshells cannot wait for jobs in the parent shell environment:
In the above example, wait
treats the job %
as an unknown job and returns exit status 127.
Compatibility
The wait
built-in is specified in POSIX.1-2024.
Many existing shells behave differently on various errors. POSIX requires that an unknown process ID be treated as a process that has already exited with exit status 127, but the behavior for other errors should not be considered portable.
Pattern matching
This section describes the pattern matching notation used in the shell. Patterns are used in pathname expansion, case
commands, and parameter expansion modifiers.
Literals
A literal is a character that matches itself. For example, the pattern a
matches the character a
. All characters are literals except for the special characters described below.
Quoting
Quoting makes a special character behave as a literal. Additionally, for unquoted parts of a pattern produced by parameter expansion, command substitution, or arithmetic expansion, backslashes escape the following character, but such backslashes are not subject to quote removal.
In this example, no pathname expansion occurs because the special characters are quoted:
$ echo a\*b
a*b
$ asterisk='*'
$ echo "$asterisk"
*
$ quoted='a\*b'
$ echo $quoted
a\*b
Special characters
The following characters have special meanings in patterns:
?
– Matches any single character.*
– Matches any number of characters, including none.[…]
– Matches any single character from the set of characters inside the brackets. For example,[abc]
matchesa
,b
, orc
. Ranges can be specified with a hyphen, like[a-z]
for lowercase letters.[!…]
and[^…]
– Matches any single character not in the set of characters inside the brackets. For example,[!abc]
matches any character excepta
,b
, orc
.
The [^…]
form is not supported in all shells; prefer using [!…]
for compatibility.
$ echo ?????? # prints all six-character long filenames
Videos
$ echo Do* # prints all files starting with Do
Documents Downloads
$ echo [MP]* # prints all files starting with M or P
Music Pictures
$ echo *[0-9] # prints all files ending with a digit
foo.bak.1 foo.bak.2 bar.bak.3
Special elements in brackets
Bracket expressions […]
can include special elements:
-
Character classes:
[:class:]
matches any character in the specified class. Available classes:[:alnum:]
– Alphanumeric characters (letters and digits)[:alpha:]
– Alphabetic characters (letters)[:blank:]
– Space and tab characters[:cntrl:]
– Control characters[:digit:]
– Digits (0-9)[:graph:]
– Printable characters except space[:lower:]
– Lowercase letters[:print:]
– Printable characters including space[:punct:]
– Punctuation characters[:space:]
– Space characters (space, tab, newline, etc.)[:upper:]
– Uppercase letters[:xdigit:]
– Hexadecimal digits (0-9, a-f, A-F)
$ echo [[:upper:]]* # prints all files starting with an uppercase letter Documents Downloads Music Pictures Videos $ echo *[[:digit:]~] # prints all files ending with a digit or tilde foo.bak.1 foo.bak.2 bar.bak.3 baz~
-
Collating elements:
[.char.]
matches the collating elementchar
. A collating element is a character or sequence of characters treated as a single unit in pattern matching. Collating elements depend on the current locale and are not yet implemented in yash-rs. -
Equivalence classes:
[=char=]
matches the equivalence class ofchar
. An equivalence class is a set of characters considered equivalent for matching purposes (e.g.,a
andA
in some locales). This feature is not yet implemented in yash-rs.
Locale support is not yet implemented in yash-rs. Currently, all patterns match the same characters regardless of locale. Collating elements and equivalence classes simply match the characters as they are, without any special treatment.
Special considerations for pathname expansion
See the Pathname expansion section for additional rules that apply in pathname expansion.
Arithmetic expressions
Arithmetic expressions in arithmetic expansion are similar to C expressions. They can include numbers, variables, operators, and parentheses.
Numeric constants
Numeric constants can be:
- Decimal, written as-is (e.g.,
42
) - Octal, starting with
0
(e.g.,042
) - Hexadecimal, starting with
0x
or0X
(e.g.,0x2A
)
$ echo $((42)) # decimal
42
$ echo $((042)) # octal
34
$ echo $((0x2A)) # hexadecimal
42
All integers are signed 64-bit values, ranging from -9223372036854775808
to 9223372036854775807
.
C-style integer suffixes (U
, L
, LL
, etc.) are not supported.
Floating-point constants are not supported, but may be added in the future.
Variables
Variables in arithmetic expressions appear as bare names. Variable names can include Unicode letters, Unicode digits, and ASCII underscores (_
), but cannot start with a digit. Variables must have numeric values.
$ a=5 b=10
$ echo $((a + b))
15
If a variable is unset and the nounset
shell option is off, it is treated as zero:
$ unset x; set +o nounset
$ echo $((x + 3))
3
If the nounset
option is on, an error occurs when trying to use an unset variable:
$ unset x; set -o nounset
$ echo $((x + 3))
error: cannot expand unset parameter
--> <arithmetic_expansion>:1:1
|
1 | x + 3
| ^ parameter `x` is not set
|
::: <stdin>:2:6
|
2 | echo $((x + 3))
| ---------- info: arithmetic expansion appeared here
|
= info: unset parameters are disallowed by the nounset option
If a variable has a non-numeric value, an error occurs.
$ x=foo
$ echo $((x + 3))
error: error evaluating the arithmetic expansion
--> <arithmetic_expansion>:1:1
|
1 | x + 3
| ^ invalid variable value: "foo"
|
::: <stdin>:2:6
|
2 | echo $((x + 3))
| ---------- info: arithmetic expansion appeared here
|
Currently, variables in arithmetic expressions must have a single numeric value. In the future, more complex values may be supported.
Operators
The following operators are supported, in order of precedence:
(
)
– grouping- Postfix:
++
– increment--
– decrement
- Prefix:
+
– no-op-
– numeric negation~
– bitwise negation!
– logical negation++
– increment--
– decrement
- Binary (left associative):
*
– multiplication/
– division%
– modulus
- Binary (left associative):
+
– addition-
– subtraction
- Binary (left associative):
<<
– left shift>>
– right shift
- Binary (left associative):
<
– less than<=
– less than or equal to>
– greater than>=
– greater than or equal to
- Binary:
==
– equal to!=
– not equal to
- Binary:
&
– bitwise and
- Binary:
|
– bitwise or
- Binary:
^
– bitwise xor
- Binary:
&&
– logical and
- Binary:
||
– logical or
- Ternary (right associative):
?
:
– conditional expression
- Binary (right associative):
=
– assignment+=
– addition assignment-=
– subtraction assignment*=
– multiplication assignment/=
– division assignment%=
– modulus assignment<<=
– left shift assignment>>=
– right shift assignment&=
– bitwise and assignment|=
– bitwise or assignment^=
– bitwise xor assignment
Other operators, such as sizeof
, are not supported.
Compatibility
POSIX.1-2024 defines arithmetic expressions on the basis of C.
POSIX requires support for signed long
integers. This implementation uses signed 64-bit integers, which is at least as wide as long
on all common platforms. Future versions may support wider integers. Other implementations may only support narrower integers.
POSIX does not require support for the ++
and --
operators. Dash 0.5.12 treats the ++
prefix operator as two +
operators, effectively making it a no-op.
Index
- action (trap)
alias
built-in- alias substitution
allexport
shell option- and-or list
- argument
- arithmetic expansion
- assignment
- background process group
- backslash escape
bg
built-inbreak
built-in- built-in utility
- case command
cd
built-inCDPATH
variable- character class
- child process
clobber
shell optioncmdline
shell option- collating element
- colon (
:
) built-in - command
command
built-in- command prompt
- command search
- command string mode
- command substitution
- compound command
- condition (trap)
continue
built-in- control terminal
- current job
- custom action
- declaration utilities
- default action
- delimiter (here-document)
- dollar single quote
- double quote
- elective built-in
- empty field removal
ENV
variable- environment variable
- equivalence class
errexit
shell optioneval
built-inexec
built-inexec
shell optionexit
built-in- exit status
EXIT
trapexport
built-in- external utility
false
built-infg
built-in- field
- field splitting
- file descriptor
- file mode
- file mode creation mask
- foreground process group
- function
getopts
built-inglob
shell option- globbing
- grouping
- hard limit
hashondefinition
shell option- here-document
HOME
variable- if command
IFS
variable- ignore
ignoreeof
shell option- interactive shell
interactive
shell option- job
- job control
- job ID
- job list
- job number
jobs
built-in- keyword
kill
built-in- length modifier
- line continuation
LINENO
variable- list
- literal (pattern matching)
- local variable
log
shell optionlogin
shell option- mandatory built-in
- modifier
monitor
shell optionnoallexport
shell optionnoclobber
shell optionnocmdline
shell optionnoerrexit
shell optionnoexec
shell optionnoglob
shell optionnohashondefinition
shell optionnoignoreeof
shell optionnointeractive
shell optionnolog
shell optionnologin
shell optionnomonitor
shell optionnonotify
shell optionnopipefail
shell optionnoposixlycorrect
shell optionnostdin
shell optionnotify
shell optionnounset
shell optionnoverbose
shell optionnovi
shell optionnoxtrace
shell optionOLDPWD
variable- operand
- operator
OPTARG
variableOPTIND
variable- option
- option, shell
- parameter
- parameter expansion
- parent process
PATH
variable- pathname expansion
- pattern matching notation
pipefail
shell option- pipeline
- positional parameter
- POSIX
posixlycorrect
shell optionPPID
variable- previous job
- process
- process group
- prompt
PS1
variablePS2
variablePS4
variablepwd
built-inPWD
variable- quote removal
- quoting
read
built-inreadonly
built-in- read-only variable
- redirection
- reserved word
return
built-in- session
set
built-in- shell error
- shell execution environment
- shell option
shift
built-in- signal
- simple command
- single quote
- soft limit
source
(.
) built-in- special built-in
- special parameter
- standard error
- standard input
- standard input mode
- standard output
stdin
shell option- subshell
- substitutive built-in
- switch modifier
- terminal
- tilde expansion
times
built-in- token
- trap
trap
built-in- trim modifier
true
built-intype
built-intypeset
built-inulimit
built-inumask
built-inunalias
built-inunset
built-inunset
shell option- utility
- variable
verbose
shell optionvi
shell optionwait
built-in- word
- word expansion
- working directory
xtrace
shell option
Symbols
!!
(literal!
in prompt)!
special parameter!
(history number in prompt)!
(command negation)"…"
(quoting)#
special parameter$"…"
(quoting)$((…))
(arithmetic expansion)$(…)
(command substitution)$
special parameter$
(default prompt)${…}
(parameter expansion)%
,%%
,%+
,%-
,%?
,%…
(job ID)&&
(and-or list)&
(asynchronous command)'…'
(quoting)()
(function definition)(…)
(case command pattern)(…)
(subshell command)(…)
(array definition)*
special parameter*
(pattern)+
(current job)--
(option-operand separator)-
special parameter-
(option prefix)-
(default trap action)-
(previous job).
built-in0
special parameter:
built-in;;
,;&
,;;&
,;|
(case branch separator);
(command sequence)<
,<&
,<>
,<<
,<<-
,<<<
,<(
(redirection)=(…)
(array assignment)=
(variable assignment)>
,>&
,>|
,>>
,>>|
,>(
(redirection)?
special parameter?
(pattern)@
special parameter[…]
,[!…]
,[^…]
,[:…:]
,[.….]
,[=…=]
(pattern)[…]
(job number)\
(quoting)`…`
(command substitution){ … }
(braced command group)|
(pipeline)||
(and-or list)~
(tilde expansion)