Bash Scripting: Difference between revisions

From Leo's Notes
This page was last edited on 16 September 2022, at 23:53.
string matching using new test
mNo edit summary
 
(42 intermediate revisions by the same user not shown)
Line 1: Line 1:
== String Matching ==
==Useful functions==
=== Regex ===
Here are some common functions and commands I use in my scripts.
Use the new test (ie: {{code|[[ x =~ regex ]]}}) to test regular expressions.
{| class="wikitable"
{{Highlight|lang=bash|code=
!Function
[[ zone36-ta =~ .*ta$ ]] && echo matched
!Code
|-
|Use this to ensure the script runs relative to its location.
 
This is useful if your script uses relative paths.
|{{highlight|lang=bash|code=
#!/bin/bash
cd "$(dirname "$0")"
}}
}}
|-
|Find the maximum of two numbers. You can implement min by flipping the operands.
|{{highlight|lang=bash|code=
max (){
        echo $(($1>$2 ? $1 : $2))
<nowiki>}</nowiki>
}}
|-
|Similar to the {{code|die}} command in PHP which prints out a message before stopping execution.


Or use the new test with wildcards.
'''Note that this will not work inside a subshell''' as it will just quit the subshell rather than the script. To get around this, you may need to kill the parent PID instead.
{{Highlight|lang=bash|code=
|<syntaxhighlight lang="bash">
[[ zone36-ta =~ *ta ]] && echo matched
function die {
echo $@
exit
}
</syntaxhighlight>
|-
|Indents text piped to the function.
|{{highlight|lang=terminal|code=
# Supports up to 10 tabs
function Indent (){
Tabs="\t\t\t\t\t\t\t\t\t\t"
sed "s/^/${Tabs:0:$((2*$1))}/g"
}
 
echo "test" {{!}} Indent 1
test
echo "test" {{!}} Indent 2
test
}}
|-
|Extracting the file name and file extension from a path
|{{highlight|lang=bash|code=
Path="/etc/resolv.conf"
 
# Get path parts
Directory=$(dirname $Path)  # /etc
Filename=$(basename $Path)  # resolv.conf
 
# Get filename parts
Name="${Filename%.*}"  # resolv
Ext="${Filename##*.}"  # conf
}}
|-
|Skip the first N lines from an output
|{{highlight|lang=terminal|code=
## -n +N outputs starting on line N.
## Eg. To skip the first 2 lines, we start on line 3:
# tail -n+3
}}
|-
|Get the current Unix timestamp
|{{Highlight
| code = $ date +"%s"
1596137957
| lang = terminal
}}
|-
|Unix timestamp to human readable time.
You can use either <code>awk</code> with <code>strftime</code>, or with <code>date</code>.
|{{highlight|lang=terminal|code=
$ echo 1355514777 {{!}} awk '{ print strftime("%c", $1) }'
Fri 14 Dec 2012 12:52:57 PM MST
}}{{Highlight
| code = $ date +"%c" -d @15996137957
Thu 30 Jul 2020 01:39:17 PM MDT
| lang = terminal
}}
}}
|-
|Get a file's last modified time in Unix timestamp
|<code>stat -c %Y ''filename''</code>
|-
|Convert a multi-line list into a comma delimited string
|<code>''cat list''<nowiki> | paste -s -d, -</nowiki></code>
|-
|Get all IPv4 addresses on this host
|<code><nowiki>ip a | grep -ohE 'inet [0-9.]{7,15}'</nowiki></code>
|-
|Get the URL from <a href="...">
|<code>''echo '<a href="asdf">'<nowiki/>''<nowiki> | grep -Eo 'href="[^\"]+"' | sed 's/href="\(.*\)"/\1/'</nowiki></code>
|-
|Replace substring in Bash
|
{| class="wikitable"
|-
|Use <code>${variable/search_term/replace_term}</code>. Eg:{{highlight|lang=bash|code=
Path="/etc/resolv.conf"


# This returns /etc/hostname
echo ${Path/resolv.conf/hostname<nowiki>}</nowiki>
}}
|}
|}


== Useful Functions / Utilities ==
==Useful scripting patterns==
Here are some common functions I use in my scripts.


=== Prompt ===
===Get a specific column===
You can prompt the user for an yes or no answer using this function. An optional default values are accepted as the second argument.
You can use either <code>awk</code> with delimiters specified with <code>-F</code>.
{{Highlight
| code = $ echo one two three four five {{!}} awk '{print $3}'
three


Example Usage:
$ echo one,two,three,four,five {{!}} awk -F, '{print $3}'
{{Highlight|lang=bash|code=
three
if prompt "Is this okay?" Y ; then
| lang = terminal
    echo "It's Okay!"
fi
if ! prompt "Is this okay?" N ; then
    echo "It's not okay!"
fi
}}
}}


Source:
Or cut by a specific delimiter and select a specific column. This method cannot handle columns separated by more than one space. If you have neat columns, you'll need to pipe through <code>tr -s ' '</code> first which collapses (or squeezes) the given repeating characters down.
<phorkie>https://phorkie.leo.home.steamr.com/13/embed</phorkie>
{{Highlight
| code = $ echo one two three four five {{!}} cut -d ' ' -f3
three
$ echo "one    two    three    four    five" {{!}} tr -s ' ' {{!}} cut -d ' ' -f3
three


=== die ===
$ echo one,two,three,four,five {{!}} cut -d ',' -f3
Similar to the <code>die</code> command in PHP which prints out a message before stopping execution.
three
| lang = terminal
}}Or use the bash built-in function <code>read</code> which can take in a list of variables. Variable assignment by piping to <code>read</code> works in zsh but will only work if used within a subshell in bash. Keep this behavior in mind if you choose to use this method of separating out words in a string. For example:{{Highlight
| code = ## Basic variable assignment with read
$ echo one two three four five {{!}} while read _ second _; do echo $second ; done
two


Function:
## zsh variable assignment works without a subshell/loop
<syntaxhighlight lang="bash">
% echo one two three four five {{!}} read _ second _
function die {
% echo $second
echo $@
two
exit
| lang = terminal
}}The last variable contains all remaining columns. Unwanted columns can be named repeatedly with <code>_</code> (like in golang) and ignored.{{Highlight
| code = ## Remember, to get only the 3rd column, we need to read the 4th column out.
% echo one two three four five {{!}} read _ _ third; echo $third
three four five
% echo one two three four five {{!}} read _ _ third _ ; echo $third
three
 
## Delimiters specified with IFS
% echo one,two,three,four,five {{!}} IFS=',' read _ _ third _ ; echo $third
three
| lang = terminal
}}In terms of speed, be aware that using <code>awk</code> or <code>tr+cut</code> are actual commands that spawns a sub-process whereas read is a bash built-in function. As a result, it's always faster to use read to parse out data, especially if in a loop.  If you are looping and using <code>awk</code> or <code>cut</code>, perhaps consider replacing it with the bash <code>read</code> equivalent. For example:
{{Highlight
| code = ## Super slow! Invokes awk 2 times per line
$ w {{!}} tail -n +3 {{!}} while read i ; do
  Username=$(echo "$i" {{!}} awk '{print $1}')
  Source=$(echo "$i" {{!}} awk '{print $3}')
  echo $Username $Source
done
 
## Instead do something like this!
$ w {{!}} tail -n +3 {{!}} while read Username _ Source _ ; do
  echo $Username $Source
done
| lang = terminal
}}
 
===Preserve header while sorting without using a temporary file===
You can sort an output on a specific column while preserving the header and without needing a temporary file by using a shim function that reads and prints the first line before passing the rest of the output down the pipe. Define this function:
{{Highlight
| code = # print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps {{!}} body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}
}
</syntaxhighlight>
| lang = bash
}}
Then use this shim body function with the desired sort function. For example:
{{Highlight
| code = $ top -b -n 1 {{!}} head -n 15 {{!}} tail -n +7 \
  {{!}} body sort -k 5 -rn
| lang = terminal
}}
Credit to https://unix.stackexchange.com/questions/11856/sort-but-keep-header-line-at-the-top for this neat trick.
==Testing things in Bash==
{{code|test}} is a bash built-in function to add conditionals in your script. {{code|[}} is an alias of the {{code|test}} command and are analogous.
 
Use the test operators listed below with {{code|test}} or a single bracket test condition like the following example.
{{highlight|lang=terminal|code=
$ test -e /etc/hosts && echo "File exists"
File exists
 
## Note that the white space around [ and ] and also the operator are required.
$ [ -e /etc/hosts ] && echo "File exists"
File exists
 
$ if [ -e /etc/hosts ] ; then echo "File exists" ; fi
File exists
}}
 
{| class="wikitable"
! width="125px" |Operator
!Description
|-
!{{code|-e}}
|file exists
|
|-
!{{code|-a}}
|file exists (deprecated)
 
This is identical in effect to {{code|-e}}
|-
!{{code|-f}}
|file is a regular file (not a directory or device file)
|-
!{{code|-s}}
|file is not zero size
|-
!{{code|-d}}
|file is a directory
|-
!{{code|-b}}
|file is a block device. Eg. {{code|/dev/sda}}.
|-
!{{code|-c}}
|file is a character device. Eg. {{code|/dev/tty0}}.
|-
!{{code|-p}}
|file is a pipe. Eg. {{code|/dev/fd/0}}.
|-
!{{code|-h}}
|file is a symbolic link
|-
!{{code|-L}}
|file is a symbolic link
|-
!{{code|-S}}
|file is a socket
|-
!{{code|-t}}
|file (descriptor) is associated with a terminal device
 
This test option may be used to check whether the stdin [ -t 0 ] or stdout [ -t 1 ] in a given script is a terminal.
|-
!{{code|-r}}
|file has read permission (for the user running the test)
|-
!{{code|-w}}
|file has write permission (for the user running the test)
|-
!{{code|-x}}
|file has execute permission (for the user running the test)
|-
!{{code|-g}}
|file or directory has set-group-id (sgid) flag set
 
If a directory has the sgid flag set, then a file created within that directory belongs to the group that owns the directory.
|-
!{{code|-u}}
|file has set-user-id (suid) flag set
 
|-
!{{code|-k}}
|file or directory has sticky bit set
 
*If set on a file, that file will be kept in cache memory, for quicker access.
*If set on a directory, it restricts write permission. Setting the sticky bit adds a t to the permissions on the file or directory listing. This restricts altering or deleting specific files in that directory to the owner of those files.
|-
!{{code|-O}}
|you are owner of file
|-
!{{code|-G}}
|group-id of file same as yours
|-
!{{code|-N}}
|file modified since it was last read
|-
!{{code|f1 -nt f2}}
|file f1 is newer than f2
|-
!{{code|f1 -ot f2}}
|file f1 is older than f2
|-
!{{code|f1 -ef f2}}
|files f1 and f2 are hard links to the same file
|-
!{{code|!}}
|"not" -- reverses the sense of the tests above (returns true if condition absent).
|}
 
===Integers===
 
Integer tests, with {{code|test}} or inside single brackets.
 
Eg.
{{highlight|lang=terminal|code=
$ test 2 -eq 2 && echo equal || echo not equal
equal
 
$ [ 2 -eq 2 ] && echo equal || echo not equal
equal
}}
 
 
{| class="wikitable"
! width="125px" |Operator
!Description
|-
!{{code|-eq}}
|is equal to
|-
|-
!{{code|-ne}}
|is not equal to
|-
!{{code|-gt}}
|is greater than
|-
!{{code|-ge}}
|is greater than or equal to
|-
!{{code|-lt}}
|is less than
|-
!{{code|-le}}
|is less than or equal to
|}
 
 
 
Integer tests can also be done inside double parentheses. Eg.
{{highlight|lang=terminal|code=
$ ((2==2)) && echo equal || echo not equal
equal
}}
 
 
{| class="wikitable"
! width="125px" |Operator
!Description
|-
!{{code|1===}}
|is equal
|-
!{{code|<}}
|is less than
|-
!{{code|1=<=}}
|is less than or equal to
|-
!{{code|>}}
|is greater than
|-
!{{code|1=>=}}
|is greater than or equal to
|}


{{Info|Caveat when using in a subshell|This will not work inside a subshell as it will just quit the subshell rather than the script. To get around this, you may need to kill the parent PID instead.}}


=== Indent ===
===Strings===
Indents text piped to the function.


Source:
{| class="wikitable"
<phorkie>https://phorkie.leo.home.steamr.com/14/embed</phorkie>
! width="125px" |Operator
!Description
|-
!{{code|-z}}
|string is null, that is, has zero length
|-
!{{code|-n}}
|string is not null.
|-
!{{code|1==}}
|is equal to


Usage:
{{highlight|lang=terminal|code=
{{Highlight|lang=bash|code=
## Does not work in zsh. Works only in sh and bash.
echo "test" {{!}} Indent
$ if [ "$a" = "$b" ] ; then
test
  echo "Equal"
echo "test" {{!}} Indent 2
fi
test
}}
|-
!{{code|1===}}
|is equal to
 
{{highlight|lang=terminal|code=
## Does not work in zsh. Works only in sh and bash.
$ if [ "$a" == "$b" ] ; then
  echo "Equal"
fi
}}
}}


=== Extracting / Parsing Filenames ===
This operator inside a double bracket {{code|[[ ]]}} construct does pattern matching.
Use the <code>basename</code> to extract filenames from a path and and <code>dirname</code> to extract the path of a filename.
|-
!{{code|1=!=}}
|is not equal to


Eg:
{{highlight|lang=terminal|code=
{{Highlight|lang=bash|code=
$ if [ "$a" != "$b" ] ; then
$ dirname /etc/hosts
  echo "Not equal"
/etc
fi
}}
}}


{{Highlight|lang=bash|code=
This operator inside a double bracket {{code|[[ ]]}} construct does pattern matching.
$ basename /etc/hosts
|-
hosts
!{{code|1==~}}
|Match using regex
 
{{highlight|lang=terminal|code=
$ if [ zone36-ta =~ .*ta$ ] ; then
  echo "matches"
fi
 
$ if [ zone36-ta =~ *ta ] ; then
  echo "matches"
fi
}}
}}
|}<br />


To extract only the extension or the filename without the extension, use bash's string matching:
==Command Substitution==
{{Highlight|lang=bash|code=
Command substitution allows output from a command to be substituted in place of the command itself in a script. Commands can be enclosed within backticks {{code|`command`}} or the POSIX compatible form {{code|$(command)}}.
Filename=$(basename /etc/resolv.conf)
{{highlight|lang=terminal|code=
Ext="${Filename##*.}"
## Eg.
Name="${Filename%.*}"
echo "Today is " $(date)
echo "Today is " `date`
}}
}}


== Arithmetic Operations ==
Do not confuse {{code|$(command)}} with {{code|$((expression))}} which is used for arithmetic expansion.
 
===Nested Substitution===
If you are trying to do something similar to:
{{highlight|lang=bash|code=
Something=`basename `ls /home/*.txt``
}}
 
You will get an error since the expansion is ambiguous. Instead, use the  {{code|$(command)}} substitution instead:
{{highlight|lang=bash|code=
Something=$(basename $(ls /home/*.txt))
}}
 
 
==Arithmetic Operations==
Do integer arithmetic operations on variables within double brackets. For example:
Do integer arithmetic operations on variables within double brackets. For example:
{{Highlight|lang=bash|code=
{{highlight|lang=terminal|code=
Value=1
Value=1
ValuePlusOne=$((Value + 1))
ValuePlusOne=$((Value + 1))
Line 91: Line 455:


Bitwise operators also work:
Bitwise operators also work:
  ^ (XOR)  | (OR)  & (AND)
  ^ (XOR)  | (OR)  & (AND)  << (Shift left)  >> (Shift right)


== Control Blocks ==
==Control Blocks==
=== Switch ===
===Switch===
{{Highlight|lang=bash|code=
{{highlight|lang=terminal|code=
read -p "What do you want to do?: " Option
read -p "What do you want to do?: " Option


Line 112: Line 476:
}}
}}


== Snippets ==
==See Also==
=== Get First IP Address ===
 
ip a | /bin/grep inet | grep -v inet6 | tail -n+2 | head -n 1 | awk '{print $2}' | cut -d'/' -f1
*https://www.tldp.org/LDP/abs/html/comparison-ops.html
*http://sipb.mit.edu/doc/safe-shell/
*http://mywiki.wooledge.org/BashFAQ
*https://github.com/dylanaraps/pure-sh-bible


{{Navbox Linux}}
[[Category:Linux]]
[[Category:Linux]]
{{Navbox Linux}}

Latest revision as of 23:53, 16 September 2022

Useful functions

Here are some common functions and commands I use in my scripts.

Function Code
Use this to ensure the script runs relative to its location.

This is useful if your script uses relative paths.

#!/bin/bash
cd "$(dirname "$0")"
Find the maximum of two numbers. You can implement min by flipping the operands.
max (){
        echo $(($1>$2 ? $1 : $2))
}
Similar to the die command in PHP which prints out a message before stopping execution.

Note that this will not work inside a subshell as it will just quit the subshell rather than the script. To get around this, you may need to kill the parent PID instead.

function die {
	echo $@
	exit
}
Indents text piped to the function.
# Supports up to 10 tabs
function Indent (){
	Tabs="\t\t\t\t\t\t\t\t\t\t"
	sed "s/^/${Tabs:0:$((2*$1))}/g"
}

echo "test" | Indent 1
	test
echo "test" | Indent 2
		test
Extracting the file name and file extension from a path
Path="/etc/resolv.conf"

# Get path parts
Directory=$(dirname $Path)  # /etc
Filename=$(basename $Path)  # resolv.conf

# Get filename parts
Name="${Filename%.*}"  # resolv
Ext="${Filename##*.}"  # conf
Skip the first N lines from an output
## -n +N outputs starting on line N.
## Eg. To skip the first 2 lines, we start on line 3:
# tail -n+3
Get the current Unix timestamp
$ date +"%s"
1596137957
Unix timestamp to human readable time.

You can use either awk with strftime, or with date.

$ echo 1355514777 | awk '{ print strftime("%c", $1) }'
Fri 14 Dec 2012 12:52:57 PM MST
$ date +"%c" -d @15996137957
Thu 30 Jul 2020 01:39:17 PM MDT
Get a file's last modified time in Unix timestamp stat -c %Y filename
Convert a multi-line list into a comma delimited string cat list | paste -s -d, -
Get all IPv4 addresses on this host ip a | grep -ohE 'inet [0-9.]{7,15}'
Get the URL from <a href="..."> echo '<a href="asdf">' | grep -Eo 'href="[^\"]+"' | sed 's/href="\(.*\)"/\1/'
Replace substring in Bash
Use ${variable/search_term/replace_term}. Eg:
Path="/etc/resolv.conf"

# This returns /etc/hostname
echo ${Path/resolv.conf/hostname}

Useful scripting patterns

Get a specific column

You can use either awk with delimiters specified with -F.

$ echo one two three four five | awk '{print $3}'
three

$ echo one,two,three,four,five | awk -F, '{print $3}'
three

Or cut by a specific delimiter and select a specific column. This method cannot handle columns separated by more than one space. If you have neat columns, you'll need to pipe through tr -s ' ' first which collapses (or squeezes) the given repeating characters down.

$ echo one two three four five | cut -d ' ' -f3
three
$ echo "one    two    three    four     five" | tr -s ' ' | cut -d ' ' -f3
three

$ echo one,two,three,four,five | cut -d ',' -f3
three

Or use the bash built-in function read which can take in a list of variables. Variable assignment by piping to read works in zsh but will only work if used within a subshell in bash. Keep this behavior in mind if you choose to use this method of separating out words in a string. For example:

## Basic variable assignment with read
$ echo one two three four five | while read _ second _; do echo $second ; done
two

## zsh variable assignment works without a subshell/loop
% echo one two three four five | read _ second _
% echo $second 
two

The last variable contains all remaining columns. Unwanted columns can be named repeatedly with _ (like in golang) and ignored.

## Remember, to get only the 3rd column, we need to read the 4th column out.
% echo one two three four five | read _ _ third; echo $third
three four five
% echo one two three four five | read _ _ third _ ; echo $third
three

## Delimiters specified with IFS
% echo one,two,three,four,five | IFS=',' read _ _ third _ ; echo $third
three

In terms of speed, be aware that using awk or tr+cut are actual commands that spawns a sub-process whereas read is a bash built-in function. As a result, it's always faster to use read to parse out data, especially if in a loop. If you are looping and using awk or cut, perhaps consider replacing it with the bash read equivalent. For example:

## Super slow! Invokes awk 2 times per line
$ w | tail -n +3 | while read i ; do
  Username=$(echo "$i" | awk '{print $1}')
  Source=$(echo "$i" | awk '{print $3}')
  echo $Username $Source
done

## Instead do something like this!
$ w | tail -n +3 | while read Username _ Source _ ; do
  echo $Username $Source
done

Preserve header while sorting without using a temporary file

You can sort an output on a specific column while preserving the header and without needing a temporary file by using a shim function that reads and prints the first line before passing the rest of the output down the pipe. Define this function:

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

Then use this shim body function with the desired sort function. For example:

$ top -b -n 1 | head -n 15 | tail -n +7 \
  | body sort -k 5 -rn

Credit to https://unix.stackexchange.com/questions/11856/sort-but-keep-header-line-at-the-top for this neat trick.

Testing things in Bash

test is a bash built-in function to add conditionals in your script. [ is an alias of the test command and are analogous.

Use the test operators listed below with test or a single bracket test condition like the following example.

$ test -e /etc/hosts && echo "File exists"
File exists

## Note that the white space around [ and ] and also the operator are required.
$ [ -e /etc/hosts ] && echo "File exists"
File exists

$ if [ -e /etc/hosts ] ; then echo "File exists" ; fi
File exists
Operator Description
-e file exists
-a file exists (deprecated)

This is identical in effect to -e

-f file is a regular file (not a directory or device file)
-s file is not zero size
-d file is a directory
-b file is a block device. Eg. /dev/sda.
-c file is a character device. Eg. /dev/tty0.
-p file is a pipe. Eg. /dev/fd/0.
-h file is a symbolic link
-L file is a symbolic link
-S file is a socket
-t file (descriptor) is associated with a terminal device

This test option may be used to check whether the stdin [ -t 0 ] or stdout [ -t 1 ] in a given script is a terminal.

-r file has read permission (for the user running the test)
-w file has write permission (for the user running the test)
-x file has execute permission (for the user running the test)
-g file or directory has set-group-id (sgid) flag set

If a directory has the sgid flag set, then a file created within that directory belongs to the group that owns the directory.

-u file has set-user-id (suid) flag set
-k file or directory has sticky bit set
  • If set on a file, that file will be kept in cache memory, for quicker access.
  • If set on a directory, it restricts write permission. Setting the sticky bit adds a t to the permissions on the file or directory listing. This restricts altering or deleting specific files in that directory to the owner of those files.
-O you are owner of file
-G group-id of file same as yours
-N file modified since it was last read
f1 -nt f2 file f1 is newer than f2
f1 -ot f2 file f1 is older than f2
f1 -ef f2 files f1 and f2 are hard links to the same file
! "not" -- reverses the sense of the tests above (returns true if condition absent).

Integers

Integer tests, with test or inside single brackets.

Eg.

$ test 2 -eq 2 && echo equal


Operator Description
-eq is equal to
-ne is not equal to
-gt is greater than
-ge is greater than or equal to
-lt is less than
-le is less than or equal to


Integer tests can also be done inside double parentheses. Eg.

$ ((2==2)) && echo equal


Operator Description
== is equal
< is less than
<= is less than or equal to
> is greater than
>= is greater than or equal to


Strings

Operator Description
-z string is null, that is, has zero length
-n string is not null.
= is equal to
## Does not work in zsh. Works only in sh and bash.
$ if [ "$a" = "$b" ] ; then
  echo "Equal"
fi
== is equal to
## Does not work in zsh. Works only in sh and bash.
$ if [ "$a" == "$b" ] ; then
  echo "Equal"
fi

This operator inside a double bracket [[ ]] construct does pattern matching.

!= is not equal to
$ if [ "$a" != "$b" ] ; then
  echo "Not equal"
fi

This operator inside a double bracket [[ ]] construct does pattern matching.

=~ Match using regex
$ if [ zone36-ta =~ .*ta$ ] ; then
  echo "matches"
fi

$ if [ zone36-ta =~ *ta ] ; then
  echo "matches"
fi


Command Substitution

Command substitution allows output from a command to be substituted in place of the command itself in a script. Commands can be enclosed within backticks `command` or the POSIX compatible form $(command).

## Eg.
echo "Today is " $(date)
echo "Today is " `date`

Do not confuse $(command) with $((expression)) which is used for arithmetic expansion.

Nested Substitution

If you are trying to do something similar to:

Something=`basename `ls /home/*.txt``

You will get an error since the expansion is ambiguous. Instead, use the $(command) substitution instead:

Something=$(basename $(ls /home/*.txt))


Arithmetic Operations

Do integer arithmetic operations on variables within double brackets. For example:

Value=1
ValuePlusOne=$((Value + 1))
ValueTimes12=$((Value * 12))

The operators that are supported are:

+ - / * % 

Bitwise operators also work:

^ (XOR)   | (OR)   & (AND)   << (Shift left)   >> (Shift right)

Control Blocks

Switch

read -p "What do you want to do?: " Option

case "$Option" in
	"K")
		install_new_kernel
		die "Complete!"
		;;
	"H")
		usage
		;;
	*)
		die "Stopping installation."
		;;
esac

See Also