Program execution
#!/bin/sh or #!/usr/bin/sh
# Single line comment
: ' Multi-line comment
(:) is a null command
single quotes for literal characters
'
command arg1 arg2 arg3 ...# basic command command arg; command arg; command# multiple commands in one line command arg | command arg | command# pipeline
(:) is a null command
single quotes for literal characters
'
command arg1 arg2 arg3 ...# basic command command arg; command arg; command# multiple commands in one line command arg | command arg | command# pipeline
# I/O Redirection
# - stdin = fd 0 stdout = fd 1 stderr = fd 2
python hello.py > output.txt# redirect stdout to file
python hello.py >> output.txt# append stdout to file
python hello.py 2> error.log# redirect stderr to file
python hello.py 2>&1# stderr to stdout (dup 1 to 2)
python hello.py 2>/dev/null# stderr to (null)
python hello.py >output.txt 2>&1# stdout and stderr to (file)
python hello.py &>/dev/null# stdout and stderr to (null)
python hello.py >&-# close stdout
python hello.py <&-# close stdin
python hello.py 3>&- or 3<&-# close fd 3
echo "Warning: too many users" >&2# redirect stdout to stderr
cat foo.txt | grep test# pipe stdout to stdin
find . -print0 | xargs -0 rm# pipe to args
# Process substitution
# - redirects stdout to a temp file at /dev/fd/<n>
# - the temp filename is substituted in its place
cat <(echo 'hello')# hello
echo 'hello' > >(cat)# hello
diff <(ls -r) <(ls)# diff between file lists
Note: requires no spaces between <(
# Shell execution
source config.sh# current shell execution
. config.sh# same as source
\. config.sh# same as source, ignoring aliases for .
echo "I'm in $(pwd)"# command substitution
{cd /tmp && ls}# command list execution in current shell
(cd /tmp && ls)# command list execution as subprocess
bash -c 'env | grep CLASSPATH'# new shell execution as subprocess
./config.sh# run script in new shell as subprocess
pwd# run command as subprocess
exec pwd# replace current shell with command
exec > output.txt# apply redirections to current shell
# Conditional execution
git commit && git push# shortcut AND
git commit || echo "Commit failed"# shortcut OR
# BG / FG / multiprocess execution
pwd &# run in background
nohup pwd &# run in background will not exit on logout
nohup pwd > foo.out 2> foo.err < /dev/null &# redirect stdin stdout stderr to prevent hanging
nice -n +19 sleep 10# run with lowest priority (highest niceness +19)
nice -n -20 sleep 10# run with highest priority (lowest niceness -20)
nice -n -20 sleep 10# run with highest priority (lowest niceness -20)
jobs# show jobs and job IDs
jobs -l# show jobs (process groups) with PIDs
fg# move previous job to foreground
bg# move previous job to background
fg %<n># move job ID <n> to foreground
bg %<n># move job ID <n> to background
disown %<n># prevent job ID %<n> from exit on logout
wait PID# wait on PID and return exit status
wait %<n># wait on job ID <n> and return exit status
kill SIG PID# send signal SIG to PID
kill -l# list signal names
# 15 SIGTERM graceful shutdown (default)
# 9 SIGKILL shutdown
# 3 SIGQUIT graceful shutdown with core dump
# 2 SIGINT interrupt and return control to user
# 1 SIGHUP hang up aka log out
# 19 SIGSTOP 18 SIGCONT stop continue aka pause resume
# Trap errors
trap 'echo Error at about $LINENO' ERR
traperr() {
echo "ERROR: ${BASH_SOURCE[1]} at about ${BASH_LINENO[0]}"
}
set -o errtrace
trap traperr ERR
echo "ERROR: ${BASH_SOURCE[1]} at about ${BASH_LINENO[0]}"
}
set -o errtrace
trap traperr ERR
# Brace expansion
ls {A,B}.sh# same as ls A.sh B.sh
ls A/{B,C/{1..3}}.sh# A/B.sh A/C/1.sh A/C/2.sh A/C/3.sh
# Filename expansion (aka globbing)
ls# .a.sh a.sh b.sh c.sh cc.sh
ls *.sh# a.sh b.sh c.sh cc.sh
ls a*# a.sh
ls [c]*.sh# c.sh cc.sh
ls c?.sh# cc.sh
ls [^a].sh# a.sh
Note: Strings containing wildcards will not match filenames that start with a dot (.)
# Glob options
shopt -s nullglob# Non-matching globs are removed ('*.foo' => '')
shopt -s failglob# Non-matching globs throw errors
shopt -s nocaseglob# Case insensitive globs
shopt -s dotglob# Wildcards match dotfiles ("*.sh" => ".foo.sh")
shopt -s globstar# Allow ** for recursive matches ('lib/**/*.rb' => 'lib/a/b/c.rb')
Note: Set GLOBIGNORE as a colon-separated list of patterns to be removed from glob matches.
Variables
text="*.txt"#
cat $text# all .txt file contents
cat "$text"# *.sh
cat '$text\n'# $text\n
cat $'$text\n'# $text<newline>
unset text
Note: generally double-quote variables unless they
contain wildcards to expand or command fragments
contain wildcards to expand or command fragments
# Default values
${foo:-val}# $foo, or val if unset (or null)
${foo:=val}# set $foo to val if unset (or null)
${foo:+val}# val if $foo is set (and not null)
${foo:?message}# show error message and exit if $foo is unset (or null)
Note: (:) includes null tests
Arguments
$## Number of arguments
$*# All positional arguments (as a single word)
$@# All positional arguments (as separate strings)
$1# First argument
$_# Last argument of the previous command
Note: $@ and $* must be quoted in order to perform as described.
Otherwise, they do exactly the same thing (arguments as separate strings).
Otherwise, they do exactly the same thing (arguments as separate strings).
Special variables
$?# Exit status of last task
$!# PID of last background task
$$# PID of shell
$0# Filename of the shell script
Note: $0 is not a positional argument.
${PIPESTATUS[n]}# Exit status command n in a pipeline (array)
# Copy the array first before inspection because
# the first access will write new values into the array RC=( "${PIPESTATUS[@]}" )
# the first access will write new values into the array RC=( "${PIPESTATUS[@]}" )
# Indirection
name=joe
pointer=name
echo ${!pointer}# joe
# Prefix name expansion
prefix_a=one
prefix_b=two
# all variables names starting with prefix_
echo ${!prefix_*}# prefix_a prefix_b
# Substitution
${foo%suffix}# Remove suffix
${foo#prefix}# Remove prefix
${foo%%suffix}# Remove long suffix
${foo/%suffix}# Remove long suffix
${foo##prefix}# Remove long prefix
${foo/#prefix}# Remove long prefix
${foo/from/to}# Replace first match
${foo//from/to}# Replace all
${foo/%from/to}# Replace suffix
${foo/#from/to}# Replace prefix
# String Manipulation
foo='foobar'
echo "${#foo}"# 6 Length of $foo
echo "${foo:1:3}"# oob Substring (position, length)
echo "${foo:1}"# oobar Substring to the end
echo "${foo:(-3):3}"# bar Substring from the end
echo "${foo::-1}"# fooba Substring until length-1
str="HELLO WORLD!"
echo "${str,}"# "hELLO WORLD!" (lowercase 1st letter)
echo "${str,,}"# "hello world!" (all lowercase)
str="hello world!"
echo "${str^}"# "Hello world!" (uppercase 1st letter)
echo "${str^^}"# "HELLO WORLD!" (all uppercase)
# Numeric calculations
$((a + 200))# POSIX integer arithmetic, returns string
$(($RANDOM%200))# Random number 0..199
declare -i count# Declare as type integer
count+=1# Increment
Control Flow
# if / elif / else statement
if [[ -z "$string" ]]; then
echo 'empty'
elif [[ -n "$string" ]]; then
echo 'not empty'
else
echo
fi
[ -z $string ]# POSIX test
[[ -z $string ]]# bash test w/ extended features
((3 - 3))# bash arithmetic test w/ extended feature
returns zero when non-zero Note: [ and ] and [[ and ]] require whitespace around them
returns zero when non-zero Note: [ and ] and [[ and ]] require whitespace around them
# Case / switch statement
case "$1" in
(start | up)
vagrant up ;;
(*)
echo "Usage: $0 {start|stop|ssh}"
;;
esac
Note: opening parentheses are optional and commonly omitted
# Loops
# Basic for loop
for i in /etc/rc.*; do
echo "$i"
done
# C-like for loop
for ((i = 0 ; i < 100 ; i++)); do
echo "$i"
done
# Range for loop
for i in {1..5}; do
echo "$i"
done
# Range for loop with step size
for i in {5..50..5}; do
echo "$i"
done
# Infinite loop
while true; do
...
done
# Functions
hello() {
echo "hello $1"
}
function hello {
echo "hello $1"
}
hello "world"# hello world
Conditions
# Test conditions
[[ -z STRING ]]# Empty string
[[ -n STRING ]]# Not empty string
[[ STRING == STRING ]]# Equal
[[ STRING != STRING ]]# Not Equal
[[ NUM -eq NUM ]]# Equal
[[ NUM -ne NUM ]]# Not equal
[[ NUM -lt NUM ]]# Less than
[[ NUM -le NUM ]]# Less than or equal
[[ NUM -gt NUM ]]# Greater than
[[ NUM -ge NUM ]]# Greater than or equal
[[ STRING =~ STRING ]]# Regexp
(( NUM < NUM ))# Numeric conditions
# More conditions
[[ -o OPTIONNAME ]]# OPTIONNAME enabled
[[ ! EXPR ]]# Not
[[ X && Y ]]# And
[[ X || Y ]]# Or
# File conditions
[[ -e FILE ]]# Exists
[[ -r FILE ]]# Readable
[[ -h FILE ]]# Symlink
[[ -d FILE ]]# Directory
[[ -w FILE ]]# Writable
[[ -s FILE ]]# Size is > 0 bytes
[[ -f FILE ]]# File
[[ -x FILE ]]# Executable
[[ FILE1 -nt FILE2 ]]# 1 is more recent than 2
[[ FILE1 -ot FILE2 ]]# 2 is more recent than 1
[[ FILE1 -ef FILE2 ]]# Same files
Note: [[ is a command/program that returns
either 0 (true) or 1 (false). Any program that obeys
the same logic can be used as a condition.
either 0 (true) or 1 (false). Any program that obeys
the same logic can be used as a condition.
Functions
# Returning values
myfunc() {
local myresult='some value'
echo "$myresult"
}
result=$(myfunc)
# Raising errors
myfunc() {
return 1
}
if myfunc; then
echo "success"
else
echo "failure"
fi
Reading input
# Reading input
echo -n "Proceed? [y/n]: "
read -r ans
echo "$ans"
Note: read's -r option disables a
peculiar legacy behavior with backslashes. read -n 1 ans # Read one character
peculiar legacy behavior with backslashes. read -n 1 ans # Read one character
# Read lines loop
while read -r line; do
echo "$line"
done < file.txt
# Parsing options
while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do
case $1 in
-V | --version )
echo "$version"
exit
;;
-s | --string )
shift; string=$1
;;
-f | --flag )
flag=1
;;
esac
shift
done
if [[ "$1" == '--' ]]; then shift; fi
# Here-doc
cat << END
hello world
END# hello world
hello world
END# hello world
# Here-string
cat <<< abc.txt# abc.txt
Arrays
# Array definition
Fruits=('Apple' 'Banana' 'Orange' )
Fruits[0]="Apple"
Fruits[1]="Banana"
Fruits[2]="Orange"
# Array access
echo "${Fruits[0]}"# Element #0
echo "${Fruits[-1]}"# Last element
echo "${Fruits[@]}"# All elements, space-separated
echo "${#Fruits[@]}"# Number of elements
echo "${#Fruits}"# String length of the 1st element
echo "${#Fruits[3]}"# String length of the Nth element
echo "${Fruits[@]:3:2}"# Range (from position 3, length 2)
echo "${!Fruits[@]}"# Keys of all elements, space-separated
# Array operations
Fruits=("${Fruits[@]}" "Watermelon" )# Push
Fruits+=('Watermelon')# Also Push
Fruits=( "${Fruits[@]/Ap*/}" )# Remove by regex match
unset Fruits[2]# Remove one item
Fruits=("${Fruits[@]}")# Duplicate
Fruits=("${Fruits[@]}" "${Veggies[@]}" )# Concatenate
lines=($(cat "logfile"))# Read from file
# Array iteration
for i in "${arrayName[@]}"; do
echo "$i"
done
echo "$i"
done
Dictionaries / Associative Arrays
# Dictionary definition
declare -A sounds# declares as associative array
sounds[dog]="bark"
sounds[cow]="moo"
sounds[bird]="tweet"
sounds[wolf]="howl"
# Dictionary access
echo "${sounds[dog]}"# Dog's sound
echo "${sounds[@]}"# All values
echo "${!sounds[@]}"# All keys
echo "${#sounds[@]}"# Number of elements
unset sounds[dog]# Delete dog
# Iterate over values
for val in "${sounds[@]}" ; do
echo "$val"
done # Iterate over keys for key in "${!sounds[@]}" ; do
echo "$key"
done
echo "$val"
done # Iterate over keys for key in "${!sounds[@]}" ; do
echo "$key"
done
History
# History
history# Show history
shopt -s histverify# Don’t execute expanded result immediately
!!# Execute last command again
!n# Expand nth command in history
!-n# Expand nth most recent command
!<command># Expand most recent invocation of command
!!:s/<FROM>/<TO>/# Replace first occurrence of <FROM> to <TO> in most recent command
!!:gs/<FROM>/<TO>/# Replace all occurrences of <FROM> to <TO> in most recent command
!$:t# Expand only basename from last parameter of most recent command
!$:h# Expand only directory from last parameter of most recent command
!! and !$ can be replaced with any valid expansion.
# History slices
!!:n# Expand only nth token from most recent command (command is 0; first argument is 1)
!^# Expand first argument from most recent command, short for !!:^
!$# Expand last token from most recent command, short for !!:$
!*# Expand all parameters of most recent command
!!:n-m# Expand range of tokens from most recent command
!!:n-$# Expand nth token to last from most recent command
!! can be replaced with any valid expansion i.e. !cat, !-2, !42, etc.
Miscellaneous
# Transform strings
-c# Operations apply to characters not in the given set
-d# Delete characters
-s# Replaces repeated characters with single occurrence
-t# Truncates
[:upper:]# All upper case letters
[:lower:]# All lower case letters
[:digit:]# All digits
[:space:]# All whitespace
[:alpha:]# All letters
[:alnum:]# All letters and digits
# Example
echo "Hello World" | tr '[:lower:]' '[:upper:]'
HELLO WORLD
# Strict mode
set -euo pipefail
IFS=$'\n\t'
# Options
set -o noclobber# Avoid overlay files (echo "hi" > foo)
set -o errexit# Used to exit upon error, avoiding cascading errors
set -o pipefail# Unveils hidden failures
set -o nounset# Exposes unset variables
printf 'IFS is: %q' "$IFS"# print IFS
source "${0%/*}/../share/foo.sh"# source relative
dir=${0%/*}# command location
find . -iname text.txt# find file location
locate text.txt# find file location
realpath $1# absolute path
basepath $1# file name
basepath $1 .sh# file name without suffix
# Inspect commands
which cd# find command location
type cd# cd is a function/alias/shell builtin/...
command -V cd# cd is a function/alias/shell builtin/...
man cd# manual/man pages for cd
Go to previous directory
pwd# /home/user/foo
cd bar/
pwd# /home/user/foo/bar
cd - pwd # /home/user/foo
Check for command’s result
if ping -c 1 google.com; then
echo "It appears you have a working internet connection"
fi
Grep check
if grep -q 'foo' ~/.bash_history; then
echo "You appear to have typed 'foo' in the past"
fi
# printf
printf " 1 + 1=%d" 2# "1 + 1 = 2"
printf "This is how you print a float: %f" 2# "This is how you print a float: 2.000000"
printf '%s\n' '#!/bin/bash' 'echo hello' < file
# format string is applied to each group of arguments
printf '%i+%i=%i\n' 1 2 3 4 5 9