---
The POSIX shell built-in utilities are:
All other POSIX utilities--such as 'pwd', 'read', 'test' ('[') and 'true'--are external to the shell. Most shells include these and other utilities as built-ins.
An exit status of 0 is true/success; a nonzero exit status is false/failure. Contrast with languages that use Boolean algebra (1 is true; 0 is false).
'set -C' prevents '>' from overwriting existing files. You can override it at run time with '>|'.
You can check if a file descriptor is a terminal with '-t'. By checking if FD 0 (STDIN) is a terminal, you can determine if input is coming from an interactive session or being piped from somewhere else.
if [ -t 0 ]; then echo "input is coming from a terminal" else echo "input is coming from a pipe" fi
'read' and other commands will take input from STDIN, regardless of where STDIN is coming from. To force input from a terminal, pipe directly from '/dev/tty'.
read -r input < /dev/tty
Use $PWD to get the current working directory without having to call an external utility.
echo "$PWD"
POSIX shell does not have arrays. A crude workaround is to put elements in positional arguments with 'set'.
set -- 1 0 5 2 "hello world" echo "$5" expr "$1" + "$3"
To test if a string contains a substring, use 'case'. This enables checking for several substrings at once.
string="hello world" substring="wo" case "$string" in *"$substring"*) echo "the string contains the substring" ;; *) echo "the string does not contain the substring" ;; esac
Alternatively, remove the largest part of the string that starts with the substring and compare the result to the original string. If they are equal, the attempted removal does not change the string, so the substring is not present. This is useful for one-liners.
string="hello world" sub="wo" [ "$string" = "${string%%"$sub"*}" ] && echo "substring not present"
The second syntax enables you to check if a string has multiple line without invoking 'wc'.
string="hello world" [ "$string" = "${string%%
Remove all instances of a substring from a string without using 'sed' or 'awk'. Loop continuously and use 'case' to check if the substring is present. If it is, remove the first occurrece and repeat. If it isn't, break out of the loop.
string="1f9774f4-ef87-4d32-b574-36f228414410" sub="-" while true; do case "$string" in *"$sub"*) string="${string%%"$sub"*}${string#*"$sub"}";; *) break;; esac done
Create an empty file, or truncate an existing file, by writing ':' (no-op) to it.
: > /tmp/file.txt
'until' executes when the command given to it returns a nonzero exit status. This is equivalent to 'while !' but is more readable.
i=0 until [ $i -eq 10 ]; do echo $i i=$((i+1)) done
Group commands with either braces or parentheses. Braces execute the group in the current shell environment; parentheses spawn a subshell for the group. When using braces, all commands must be delimited, e.g. with a semicolon or a newline.
empty_string=0 string="nonempty" [ -z "$string" ] && { empty_string=1; echo "string is empty"; } [ "$string" = "hello" ] || ( hello_string="no"; echo "${hello_string}" )
This can be useful when several commands need to use data from a single pipe.
cat <<EOF > /tmp/file.txt Tokyo London Paris New York EOF sort /tmp/file.txt | { read -r first_entry echo "first entry: ${first_entry}" cat }
':' behaves identically to 'true'. It saves an external call but is less readable.
while :; do read -r input echo "$input" done
When writing a here-document, '<<-' strips leading tabs from the input lines, including from the delimiter. This is useful for managing indentation in a script.
cat <<-EOF no tabs here EOF
It is often tempting to reduce 'if'/'then'/'else' statements to a pseudo-ternary.
: > /tmp/file.txt [ -f /tmp/file.txt ] && echo "file exists" || echo "file does not exist"
This can produce unexpected results. If the second command returns a nonzero exit status, the third command will execute, even if the first command returns 0.
echo "right" > /tmp/file.txt [ -f /tmp/file.txt ] && grep -q "wrong" /tmp/file.txt || echo "file does not exist"
You can emulate the intended behavior by grouping the second command and returning 0 at the end of the group. This comes at the cost of losing the actual return value of the second command.
echo "right" > /tmp/file.txt [ -f /tmp/file.txt ] && { grep -q "wrong" /tmp/file.txt; true; } || echo "file does not exist"
Replace 'xxd -p':
Create a hex dump of data using 'od', then remove spaces with 'tr'.
echo "test file" > /tmp/file.txt od -An -tx1 /tmp/file.txt | tr -d " "
The outputted hex is identical, but the formatting is not: 'od' prints 16 bytes per line, while by default 'xxd' prints 30 bytes per line. To emulate 'xxd -p -c0' (no column size limit), remove newlines as well as spaces.
od -An -tx1 /tmp/file.txt | tr -d " \n"
Replace 'xxd -r -p':
'fold' the hex dump into single bytes and loop over them. A nested 'printf' first converts the hexadecimal to octal, then prints the raw byte corresponding to the octal. Each line outputted by 'fold' must contain exactly 2 characters; otherwise an extra partial byte will appear in the result. Hexadecimal digits must be in lowercase. The dump can contain newlines (\n) if they do not split bytes; no other non-digit characters can be present. (Note: this is orders of magnitude slower than 'xxd -r', since it spawns a separate subshell for each byte of output. It is only useful for small amounts of data.)
echo "746573742066696c650a" > /tmp/file.txt fold -w2 /tmp/file.txt | while IFS= read -r byte; do [ ${#byte} -eq 2 ] && printf "%b" "\\0$(printf "%o" "0x$byte")" done
POSIX.1-2024 introduced the dollar-single-quote syntax, which allows bytes to be printed from their hexadecimals value directly. This removes the need for a subshell and is much faster.
fold -w2 /tmp/file.txt | while IFS= read -r byte; do [ ${#byte} -eq 2 ] && eval "printf \"%b\" \"