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>

Print

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

Bookmarks