Unix POSIX Shell

See sh(1).

The features of a Bourne or POSIX shell (/bin/sh) are limited compared to ksh, bash, or zsh, but /bin/sh scripts have the advantage of being portable to any unix-like system.

Standard preamble: it’s prevents headaches to do set -euf at the start of a shell script. -e exits immediately on an error. -u “nounset” writes an error instead of executing a command with an unset parameter. -f “noglob” turns off expansion of file name patterns.

Use printf rather than echo. It’s more secure and more portable.


set -euf
printf "Hello, world.\n"

Hello, prompting for name:

# Usage: hello [NAME]
set -euf
read -p "What's your name? " name
printf "Hello, ${name}\n"

Print the first line of all text files in a directory:

# Usage: [DIRECTORY]
set -euf
dir=${1%/}    # Remove trailing slash, if any
for file in ${dir}/*; do
	if file $file | grep -q "ASCII text$"; then
		head -1 $file

sh lacks arrays, but we can emulate them with eval:

set -euf
for i in $(seq 1 10); do
	eval a${i}=$i
for ((i=1; i<=10; i++)); do
	eval echo "The value of a${i} is \${a${i}}."

We need the escape in the echo because eval scans the command twice. The final variable first evaluates to \${a4}, and then to 4 (or whatever) on the second eval pass.

For command substitution, $() is preferred over the older backtick syntax:

set -euf
for f in ./*.txt; do
	echo mv "$f" "./$(basename ${f} .txt).md"

Also, always quote variables (like “$f” above). (This helps, for example, if the variable points to a file name with spaces!)

Using find in place of a loop:

set -euf
find $HOME/tmp/ -iname '*.html' -execdir sh -c '
	foo() {
		echo "$@"
	foo "$@"
' sh {} \;


Note that it is not possible in pure POSIX shell to export a function outside the current shell.

set -euf
backwards () {
		echo "$1" | /usr/bin/rev
backwards "foo bar"
# rab oof

Builtin Commands

true Returns a 0 (true) exit value. . file Shell reads “file” and executes its contents. alias [foo[=blah]] Define alias (if specified), or print the value of alias “foo”, or all aliases if none are specified. bg [job] Background the job. command [-p] [-v] [-V] command [arg …] Run command but ignore shell functions with the same name. cd - Change to previous directory. cd [-LP] [directory] Change to home or the specified directory. Directories not beginning wiht a slash must be in the current directory or in CDPATH. echo [-n] args Print args to STDOUT, spearated by spaces. A newline follows output unless “-n” is used. eval string … Concat arguments with spaces, then re-parse and execute. I.e. execute “string” as if we typed it as a command. exec [command arg …] Replace shell process with command. exit [exitstatus] Exit the shell. export name … export -p Export names to the environment of subsequent commands. Without an argument, list all exported variables. Variables can be declared an exported in one go, like: export name=value fc [-e editor] [first [last]] fc -l [-nr] [first [last]] fc -s [old=new] [first] Edit and re-execute, or list, previous commands. Not implemented in Debian’s dash? fg [job] Move job to foreground. getopts [abc:def:gh flag] getopts helps parse command line options/flags. Takes one argument: a series of option/flag letters or numbers (only single characters). A colon optionally follows each letter to indicate the flag takes an argument. OPTIND is initialized to 1. getopts puts the value of the next flag from the option string into the shell variable specified by flag, and its index into $OPTIND. (It doesn’t literally have to be “flag” the variable can be named as “var” or “option” or whatever.) $OPTIND lets getopts remember it place in the options string between invocations (but it’s up to us to manually update $OPTIND after each call). For options that require an argument, getopts puts the argument value in OPTARG. getopts returns a non-zero value when it hits the final option. When getopts runs out of flags, it sets the flag variable value to “–”, otherwise (i.e. for an invalid option), getopts sets it to “?”.

	echo "Testing getopts..."
	while getopts a:bc flag
		case $flag in
			a) echo "a"; echo $OPTARG ;;
			b) echo "b";;
			c) echo "c";;
			\?) echo "wtf?";;
	shift $(expr $OPTIND - 1)

hash [-rv command …] The shell keeps a hash table that remembers the locations of commands. With zero arguments, hash prints the contents of that table. With “command” arguments, hash removes the specified commands from the table and relocates them. The ‘-v’ option prints the location of commands as hash finds them. The ‘-r’ option deletes all non-function entries from the hash table. pwd [-LP] Remembers the present directory. read [-p prompt] [-r] variable […] Read a line from STDIN, and preface it with a prompt if -p is specified. Trailing newline is chomped. Input is split, and assigned to variables in order. If there are more input pieces than variables, the extra pieces are all assigned to the final variable. A backslash in the input escapes the following character as a literal, unless the -r flag is thrown. #!/bin/sh read -p “> ” a b c echo $a echo $b echo $c readonly variable … readonly [-p] List read-only variables if invoked without an argument. Set a read-only variable like readonly name or simultaneously set the value like readonly name=value.

Environment Variables

HOME Set by login(1) to directory specified in the passwd file. PATH Search path for executables. CDPATH Path searched by “cd” builtin command. Note that if we set CDPATH, we probably want to include “.”, like: $ export CDPATH=.:$HOME:/var MAIL Name of the mail file. Overridden by MAILPATH. MAILCHECK Check for mail every N seconds. If zero, check every prompt. MAILPATH Colon-separated list of files to check for incoming mail. Overrides MAIL. PS1 Prompt string. Defaults to “$ ” (or “# ” for superuser). PS2 Defaults to “> “. IFS Input Field Separators. Defaults to Space, Tab, and Newline. TERM Terminal type. HISTSIZE Size of history buffer in lines. PWD Logical value of current working directory. Set by “cd”. OLDPWD Previous value of PWD. Set by “cd”. PPID Process ID of the shell’s parent process.