This feature will cause bash(1)
to throw an error message if any attempt is made to expand an unset variable.
set -u
Using this (very wise) option will require a change to null parameter tests:
[ -z "${_dunno}" ] # This will cause script to error out
[ "x" == "${_dunno:-x}" ] # True means parameter was null (unset)
This feature will cause bash(1)
to assign a non-zero exit code to $?
if any command within the pipeline fails. If this feature isn't turned on (it isn't by default), then $?
contains the exit code from the last command in the pipeline.
set -o pipefail
# If $_name is null/unset, a message is printed to stderr (and the shell exits)
${_name:?}
# If $_age is set, the value of $_age is printed;
# If $_age is unset, 100 is printed
echo ${_age:-100}
# This will prevent $_flavor from being reassigned later
# with a different value.
readonly _flavor='banana-grape'
# This declaration works within functions, and makes the
# variable scoped to just within that function
myfun() {
local _foo='biz'
}
# Prints the length in bytes of the value in _city
echo ${#_city}
Variable | Description |
---|---|
$0 |
Script name |
$# |
Number of arguments |
"$@" |
All argument values, each quoted |
$? |
Command exit status (or function return status) |
$$ |
PID of executing script |
$UID |
UID of user executing script ($EUID is used to determine "effective UID") |
$LINENO |
Line of script currently executing |
$HOSTNAME |
Current hostname |
$RANDOM |
Random integer between 0 and 32767 |
The following characters, when used non-quoted and non-escaped (\
to escape) expand to special patterns.
Glob | Description |
---|---|
* |
Match any string of any length |
? |
Match any single character |
[abc] |
Match any one of a, b, or c |
Usage | Description |
---|---|
${variable^^} |
All upper-case of string in variable |
${variable,,} |
All lower-case of string in variable |
_brand='Gamestop'
echo "In all uppercase, now: ${_brand^^}"
There are a couple forms of this expansion, and positive or negative numbers can be used, depending on desired behavior.
Usage | Description |
---|---|
${variable: offset} |
Extract substring of variable, starting from offset |
${variable: offset: length} |
Extract substring of variable, length chars, starting from offset |
The string offset
is zero-based. (So the first character is 0
.)
_boop="Hello there, lady"
# This prints 'ere, lady'
echo ${_boop: 8}
# This prints 'Hello t'
echo ${_boop: 0: 7}
# This (negative offset) prints 'lady'
echo ${_boop: -4}
bash(1)
has features for trimming the beginning of a string or the end of a string, based on pattern matches. Additionally, non-greedy or greedy matching can be specified.
Usage | Description |
---|---|
${variable#pattern} |
Remove pattern from beginning of string (non-greedy) |
${variable##pattern} |
Remove pattern from beginning of string (greedy) |
${variable%pattern} |
Remove pattern from end of string (non-greedy) |
${variable%%pattern} |
Remove pattern from end of string (greedy) |
Example of greedy vs. non-greedy matching:
_quote='No fate but what we make for ourselves'
# Will print "e but what we make for ourselves"
# (i.e. the match stops at the first 't')
echo ${_quote#*t}
# Will print "we make for ourselves"
# (i.e. the match goes for the last possible 't'
# in the string)
echo ${_quote##*t}
There are a variety of invocations for matching a pattern (using characters and shell globs) and replacing. In each case, the value of variable
is expanded and then operated on based on the pattern
and any modifiers.
Usage | Description |
---|---|
${variable/pattern/newstring} |
Replace first instance of pattern with newstring |
${variable//pattern/newstring} |
// modifier means replace all instances |
${variable/#pattern/newstring} |
# modifier means only match start of string in variable |
${variable/%pattern/newstring} |
% modifier means only match end of string in variable |
${variable/pattern/} |
Delete first instance of pattern |
${variable//pattern/} |
Delete all instances of pattern |
Example that also incorporates a glob:
_wisdom='Some cause happiness wherever they go; others whenever they go'
# Prints "Some cause ; others react"
echo ${_wisdom/%hap*/; others react}
bash(1)
can logically evaluate variable values (within braces, or using the test
builtin) for various qualities.
Test | Description |
---|---|
string1 == string2 |
True if strings are equal (POSIX calls for use of = as the test operator in this case.) |
string1 != string2 |
True if strings are not equal |
-z string |
True if length of string is zero |
-n string |
True if length of string is greater than zero |
Test | Description |
---|---|
num1 -eq [-ne] num2 |
True if numbers are equal [or not equal] |
num1 -gt [-ge] num2 |
True if num1 is greater than [or greater than/equal to] num2 |
num1 -lt [-le] num2 |
True if num1 is less than [or less than/equal to] num2 |
bash(1)
can test files (within braces, or using the test
builtin) for various qualities. The following is a small subset of test options available.
All file tests are conducted using the permissions of the effective user. This means test results may differ, depending on who is running the script or interactive shell.
Test | Description |
---|---|
-e file |
True if file exists |
-r file |
True if file exists, and is readable |
-w file |
True if file exists, and is writeable |
-x file |
True if file exists, and is executable |
-s file |
True if file exists, and has a size greater than zero |
-f file |
True if file exists, and is a regular file |
-d file |
True if file exists, and is a directory |
The bash(1)
environment includes a couple interesting features to allow more natural processing of both long lines of input data and command output.
The here-document
feature provides to the stdin of a program a delimited, multi-line input. It uses the <<
operator to specify delimiters; everything between those is input text.
For instance:
cat <<EOF
Hello there, guv.
Quite nice to see you today!
EOF
Upon execution, that will print the three lines of input, but not the 'EOF' delimeter. (And 'EOF' is arbitrary. Most any word can be used as a delimeter.)
Write to a file:
The tee(1)
command (though noisy) provides a concise way to build an output file.
tee /etc/cron.d/my-cronjob <<MYEOF
# This is not especially useful
45 3 * * * someguy /bin/true
MYEOF
Prettier code formatting:
For better code alignment, using the <<-
operator will tell a here-document
to ignore leading tabs prior to the input text. (Note that these have to be hard tabs, not spaces, or it won't work.)
cat <<-MYEOF
Hello there, guv.
Quite nice to see you today!
MYEOF
As for the here-string
feature, it allows a simple way to assign command output (as one example) to the stdin of a program. It uses the <<<
operator to do so.
For instance:
cat <<<"$(mount -l)"
Upon execution, the mount(8)
command will be run, and its quoted (to preserve newline characters) output will be fed as stdin to cat(1)
.
Variable expansion example:
cat <<<"${_first_name}"
Source: Bash Pitfalls
What follows is the correct way to deal with looping over files in a directory. Notice the existence test, and the importance of quoting.
for _i in *q* ; do
# If no files are found, $_i (quoted) will actually contain
# the string '*q*', so we test here to get out.
if [ ! -e "${_i}" ] ; then
break
fi
file "${_i}"
done
Correctly reading lines from a file is as follows - documented on this StackOverflow thread.
while IFS='' read -r _line || [ -n "${_line}" ] ; do
# IFS='' prevents truncation of leading or trailing space.
# -r prevents backslash interpretation
# the [ -n $file ] test avoids missing the last line if no '\n'
echo "Line data: ${_line}"
done < input.txt
For any scripts using mktemp(1)
(or other temporary file creation), it's a good idea to handle cleanup using the bash builtin "trap".
trap 'cleanup' EXIT
trap 'exit 2' HUP INT QUIT TERM
function cleanup() {
rm -f /tmp/some-tmp-file
}
sleep 60
In this example, the cleanup()
function will always run when the script exits (whether it catches a signal or not). The exit 2
code will only run when the script is killed by one of the named signals.
References: