Bash Script - 101
Introduction
Bourne-Again SHell (bash) is a Unix shell first released in 1989 and version 4.0 was released in 2009. Command that can be run in bash shell can also be saved as file and run as bash script.
Getting Help
Bash has very compact help system bundled with it and can be accessed via:
$ help
or if running another shell (zsh, fish):
$ bash -c help
For finding a command type bash builtin command type
is very helpful. For finding all instance of a command:
$ type -a <command-name>
$ type -a cd # Output: cd is a shell builtin
If its a shell builtin then detailed information of builtin commands can be found using
$ help <command-name>
echo
builtin command is used for printing arguments to standard output. Each arguments is printed with space in
between. If any argument is variable (starts with $) prints value of the variable.
$ echo Hello World # Output: Hello World
$ echo Hello World # Output: Hello World
# $BASH_VERSION is a builtin variable, prints the running bash version
$ echo Hello $BASH_VERSION # Output: Hello 4.4.19(1)-release
By default echo
appends a newline in the end, -n option omits newline.
# Multiple statements can be written in a line using ;
$ echo -n Hello ; echo World # Output: HelloWorld
Arguments can be surrounded with double (“) or single (‘) quotes. Variable don’t substitutes if single quotes (‘) is used.
$ echo "Hello World" # Output: Hello World
$ echo "Bash $BASH_VERSION" # Output: Bash 4.4.19(1)-release
$ echo 'Bash $BASH_VERSION' # Output: Bash $BASH_VERSION
By default echo
doesn’t interprets backslash characters. -e options enables interpretation. See help echo
for
complete list of backslash characters.
$ echo "Hello\tWorld\n" # Output: Hello\tWorld\n
$ echo -e "Hello\tWorld\n" # Output: Hello World
Creating a Bash Script
For ease of usage and avoiding repeatability command can be written in file and that file can be run as command. File
name can be any valid unix filename, usually .sh
extension is appended although not requited. In bash #
is used for
line comment, but when first line starts with #!
followed by a command path, that command is used when the file is run
as executable.
#/bin/bash
echo Hello World
Run Script
Easiest with to run a bash script is using bash
executable. This will fork another shell and will run inside that
shell.
$ bash <file-name>
For running in current bash session
$ source <file-name> # or . <file-name>
If the file is executable and #!/bin/bash
is defined, then the file can be run directly.
# Make a file executable
$ chomd +x <file-name>
# As first line of file is '#!/bin/bash' will be invoked like
# /bin/bash <file-name>
$ ./<file-name>
IO Redirection
In bash, output of a command can be used as input for another command using |
. If standard error is also needed |&
is used.
$ cat file-not-exists | wc -l
# Output:
# cat: file-not-exits: No such file or directory
# 0
$ cat file-not-exists |& cat -n # Output: 1
In unix-like operating system three predefined unique open file or file desiccator is stdin (0), stdout (1) and stderr
(3). stdout of a command can be send to a file using >
and using >>
to append in file.
$ cat -n /etc/password 1> <file-name>
$ cat -n /etc/password > <file-name> # can omit as 1 is default for output
$ cat -n /etc/password >> <file-name> # appends to <file-name>
# For sending only stderr
$ cat -n /etc/password 2> <file-name>
$ cat -n /etc/password 2>> <file-name> # appends only stderr to <file-name>
# For sending both stdin and stderr
$ cat -n /etc/password > <file-name> 2>&1
$ cat -n /etc/password &> <file-name> # same as above
For reading from a file <
is used.
$ cat -n 0< /etc/passwd
$ cat -n < /etc/passwd # can omit as 0 is default for input
Variable Declaration
Variable is declared using syntax <VARIABLE-NAME>=<VALUE>. There must be no space around =
. Bash variable
names are case sensitive and can be named using uppercase and lowercase characters (not recommended), numbers,
underscore. Number can’t be first character. It’s good practice to always surround variable values with quotes. When
using the value of variable $ is prefixed with the name.
YESTERDAY=Friday
TODAY="Saturday"
echo $TODAY # Output: Saturday
echo ${TODAY} # same as above
MESSAGE="Load"
echo "Please wait ${MESSAGE}ing data." # Output: Please wait Loading data.
# Variable can also be expression value
DATE=$(date +%A)
# Arithmetic expression
FIVE=$(( 2 + 3 ))
Input
Input can be taken using shell builtin read
command. -p option is used for prompting text. When user input is
added its stored in given variable.
$ read -p "Enter Your Name: " INPUT_NAME
Enter Your Name: John Doe
$ echo $INPUT_NAME # Output: John Doe
Another options is passing as shell command argument, which can be accessed using positional arguments $1 - $9. $#
is a total argument count. $0
is running script name.
For the following script,
#!/bin/bash
# Filename: pos-arg.sh
echo "$# arguments; first: $1, second: $2"
Output will depends on how the script is invoked.
$ ./pos-arg.sh one two # Output: 2 arguments; first: one, second: two
$ ./pos-arg.sh one two three # Output: 3 arguments; first: one, second: two
Control Statements
Command can also be executed based on condition using if/else. [[ ... ]]
is conditional command that returns 0 or 1
based on expression.
DATE="Sun"
# Output: Yay! Weekend
if [[ "$DATE" = "Sun" ]]; then
echo "Yay! Weekend"
elif [[ "$DATE" = "Tue" ]]; then
echo "Work Hard"
else
echo "Boring"
fi
For executing command on every member of list, loop is used. Bash has for
, while
, until
loop. Bash has two
flavour of for loop
# Output 12345
for (( NUM=1; NUM <= 5; NUM++)); do
echo -n $NUM
done
# Output 12345
for NUM in 1 2 3 4 5; do
echo -n $NUM
done
# Same as above, using 'seq'
# Output 12345
for NUM in $(seq 1 5); do
echo -n $NUM
done
# Prints all files in /etc
for FILE in /etc/*; do
echo $FILE
done
# If called like ./for-test.sh one two three
# Output: one, two, three,
for ARG in "$@"; do
echo $ARG
done
# If called like ./for-test.sh one two three
# Output: one two three,
for ARG in "$*"; do
echo $ARG
done
Bash while loop can be used effectively when how many times the loop needs to executes is undefined. It will execute while certain condition is met.
# Output: 54321
NUM=5
while [[ "$NUM" -gt 0 ]]; do
echo -n $NUM
NUM=$(( NUM - 1 ))
done
# If called with ./while-test.sh one two three
# Output: one, two, three,
while [[ "$#" -ne 0 ]]; do
echo -n "$1, "
shift # after shift $1 is removed and $2 becames $1
done
# If INPUT value is not gives, continues to ask
INPUT=""
while [[ "$INPUT" = "" ]]; do
read -p "Enter Name: " INPUT
done
Case statements executes command based on patten matching.
DAY="sun"
# Output: Yay! Weekend
case "$DAY" in
[Ss]un) echo "Yay! Weekend" ;;
[Tt]ue) echo "Work Hard" ;;
*) echo "Boring" ;;
esac
Function
For code maintenance and readability function is a very important feature of any language. Bash function can be defined
with optional function
keyword.
# Defined with 'function' keyword
function func_one() {
echo "Func One"
}
# Defined without 'function' keyword, recommend style
func_two() {
echo "Func Two"
}
# Arguments can be accessed $1 - $9
# But $0 prints the script name
func_with_argument() {
echo "Func with Arguments"
echo "Count: $#, values: $1, $2"
}
# Function can return exit status
# if no return, then exit status of last command is used
func_with_return() {
echo "Before return"
if [[ true ]]; then
return
fi
echo "After return"
}
func_one # Output: Func One
func_two # Output: Func Two
func_with_argument One Two
# Output:
# Func with Arguments
# Count: 2, value: One, Two
func_with_return # Output: Before return
Trap
Bash has a builtin, named trap
which can be used to run command when certain event occurs. One of these events is
EXIT which can be used to run some command when script ends. This can be used to clean or free up resources. Lets
consider a script.
TEMP_FILE_NAME="/tmp/app"
cleanup () {
echo "Removing $TEMP_FILE_NAME"
rm "$TEMP_FILE_NAME"
}
trap cleanup EXIT
echo "Create $TEMP_FILE_NAME"
touch "$TEMP_FILE_NAME"
for i in 1 2 3; do
echo "Add $i in $TEMP_FILE_NAME"
echo "$i" >> "$TEMP_FILE_NAME"
sleep 10
done
# Output
# Create /tmp/app
# Add 1 in /tmp/app
# Add 2 in /tmp/app
# Add 3 in /tmp/app
# Removing /tmp/app
cleanup will also be called if an unfinished running script is killed or ended using CTRL-C.
Tools Version
- bash 4.4.19(1)-release