The Bash shell, short for “Bourne Again SHell,” is a command-line interface widely used in Unix-based operating systems. Whether you’re a seasoned developer or a tech novice, understanding Bash can greatly enhance your productivity and streamline your workflow. In this article, we’ll explore the essentials of Bash, its features, and some practical tips to help you master this powerful tool.
What is Bash?
Bash is both a command interpreter and a scripting language. It allows users to interact with the operating system by executing commands, running scripts, and automating tasks. Originally developed by Brian Fox for the GNU Project in 1987, Bash has since become the default shell for most Linux distributions and macOS.
Key Features of Bash
- Command Line Interface (CLI)
Bash provides a text-based interface for executing commands. This allows for quick navigation and management of files and processes. Learning the CLI can significantly speed up tasks that might take longer through a graphical user interface (GUI). - Scripting Capabilities
Bash scripting allows users to write sequences of commands in a text file, which can be executed as a single program. This is particularly useful for automating repetitive tasks, such as backups, system updates, or batch processing of files. - Job Control
Bash supports job control, enabling users to run multiple processes simultaneously. You can start a process in the background, suspend it, and bring it back to the foreground, making multitasking easier. - Command History
Bash maintains a history of commands executed during the session. Users can easily recall previous commands, which boosts efficiency. The history command displays a list of executed commands, while the arrow keys allow for quick navigation through past commands. - Variables and Parameters
Bash allows the use of variables to store data and parameters, making scripts more dynamic and reusable. You can create environment variables, which can be accessed globally throughout your session, or local variables, which are limited to the script’s scope.
Essential Bash Commands
Here are some fundamental Bash commands that every user should know:
- ls: Lists files and directories in the current directory.
- cd: Changes the current directory.
- cp: Copies files or directories.
- mv: Moves or renames files or directories.
- rm: Removes files or directories (use with caution).
- mkdir: Creates a new directory.
- touch: Creates an empty file or updates the timestamp of an existing file.
- echo: Displays a message or variable value to the terminal.
- man: Displays the manual for a command, providing detailed information on its usage.
Bash Shell Scripting basic syntax
Bash shell scripting is a powerful way to automate tasks and manage system operations in Unix-based environments. In this article, we will delve into the essentials of Bash scripting, focusing on variables, input parameter parsing, and the various types of loops you can use to control the flow of your scripts.
Echo command
The echo command in Bash is used to display text or variables to the terminal.
#!/bin/bash
# Example 1: Simple text
echo "Hello, World!"
# Example 2: Displaying a variable
name="Alice"
echo "My name is $name."
# Example 3: Using escape sequences
echo -e "Line 1\nLine 2\nLine 3" # -e enables interpretation of backslash escapes
# Example 4: Displaying the current date
echo "Today's date is: $(date)"
# Example 5: Redirecting output to a file
echo "This will be saved to a file." > output.txt
# taking input from user and print it
echo -n "Enter some text > "
read text
echo "You entered: $text"
Variables in Bash
Variables in Bash are used to store data that can be referenced later in the script. Here’s how to work with variables:
Declaring and Accessing Variables
You can declare a variable simply by assigning a value to it, without spaces around the equals sign and To access the value of a variable, prefix it with a dollar sign $
my_var="Hello, World!"
echo $my_var
Read-Only Variables
In Bash, there are no built-in constants like you might find in some other programming languages (e.g., const in C or Java) . By convention, you can define variables in uppercase to indicate that they should be treated as constants. While this doesn’t prevent reassignment, it serves as a signal to other developers (and yourself) that these variables should not change. You can make a variable read-only using the readonly which prevents it from being modified after its initial assignment. Use the readonly command for this
#!/bin/bash
# Define a read-only variable
readonly CONSTANT_VALUE=42
# Attempt to change the constant (this will cause an error)
# CONSTANT_VALUE=100 # Uncommenting this line will result in an error
echo "The constant value is: $CONSTANT_VALUE"
Functions as Constants
You can also create functions that act like constants by always returning a fixed value.
#!/bin/bash
# Function to return a constant value
constant_value() {
echo 42
}
echo "The constant value is: $(constant_value)"
Environment Variables
You can export a variable to make it available to child processes:
export my_env_var="I am an environment variable"
# check if variable is set or not
if [ -z "$my_env_var" ]; then
echo "my_env_var is not set."
else
echo "my_env_var is set to: $my_env_var"
fi
Arithmetic operation on variable
This is the most common method for arithmetic operations. You can use standard operators inside (( )).
Operators:
- +: Addition
- -: Subtraction
- *: Multiplication
- / : Division
- % : Modulus (remainder)
- ** : Exponentiation (power)
#!/bin/bash
a=10.5
b=2.5
result=$(echo "$a + $b" | bc)
echo "Addition: $result"
result=$(echo "$a - $b" | bc)
echo "Subtraction: $result"
result=$(echo "$a * $b" | bc)
echo "Multiplication: $result"
result=$(echo "$a / $b" | bc)
echo "Division: $result"
Incrementing /decrementing variable
count = 45
# incrementing variable
((count = count + 14))
((count += 14))
let count=count+14
let count+=14
count=$(expr $count + 14)
Input Parameters
You can pass parameters to your bash script which can be accessed using $<number> syntax
Here’s a simple script that echoes the parameters passed to it:
#!/bin/bash
echo "Script name: $0"
echo "First parameter: $1"
echo "Second parameter: $2"
echo "Total parameters: $#"
# print all the passed parameters
for i in "$@"; do
echo $i
done
You can run this script as follows:
./script.sh Hello World
Detecting Command Line Arguments:
#!/bin/bash
if [ "$1" != "" ]; then
echo "Positional parameter 1 contains something"
else
echo "Positional parameter 1 is empty"
fi
#!/bin/bash
if [ $# -gt 0 ]; then
echo "Your command line contains $# arguments"
else
echo "Your command line contains no arguments"
fi
shift command
After each iteration of the loop, shift is used to move to the next command-line argument, effectively discarding the current one that has been processed.
#!/bin/bash
# Default values
operation=""
num1=0
num2=0
# Function to display usage information
usage() {
echo "Usage: $0 -o | --operation <add|subtract|multiply|divide> -n1 <number1> -n2 <number2>"
echo "Options:"
echo " -o, --operation Specify the operation to perform (add, subtract, multiply, divide)"
echo " -n1 First number"
echo " -n2 Second number"
echo " -h, --help Show this help message"
}
# Parse command-line arguments
while [ "$1" != "" ]; do
case $1 in
-o | --operation ) shift
operation=$1
;;
-n1 ) shift
num1=$1
;;
-n2 ) shift
num2=$1
;;
-h | --help ) usage
exit
;;
* ) usage
exit 1
esac
shift
done
# Check if required arguments are provided
if [ -z "$operation" ] || [ -z "$num1" ] || [ -z "$num2" ]; then
usage
exit 1
fi
# Perform the operation
case $operation in
add )
result=$((num1 + num2))
echo "Result: $num1 + $num2 = $result"
;;
subtract )
result=$((num1 - num2))
echo "Result: $num1 - $num2 = $result"
;;
multiply )
result=$((num1 * num2))
echo "Result: $num1 * $num2 = $result"
;;
divide )
if [ "$num2" -eq 0 ]; then
echo "Error: Division by zero!"
exit 1
fi
result=$((num1 / num2))
echo "Result: $num1 / $num2 = $result"
;;
* )
echo "Error: Invalid operation '$operation'"
usage
exit 1
;;
esac
To test this you can pass the following parameters:
./simple_calculator.sh -o add -n1 5 -n2 3
./simple_calculator.sh -o subtract -n1 5 -n2 3
./simple_calculator.sh -o multiply -n1 5 -n2 3
./simple_calculator.sh -o divide -n1 6 -n2 2
./simple_calculator.sh -h
Setting the default value to variable if no parameter is passed
#!/bin/bash
name=${1:-"User"} # Default to "User" if no parameter is provided
echo "Hello, $name!"
Flow Control
Flow control statements allow you to control the execution flow of your scripts based on conditions. The primary flow control constructs in Bash are if, case, break, and continue.
If Statement
The if statement allows you to execute a block of code conditionally:
#!/bin/bash
number=10
if [ $number -gt 5 ]; then
echo "$number is greater than 5"
elif [ $number -eq 5 ]; then
echo "$number is equal to 5"
else
echo "$number is less than 5"
fi
Case Statement
The case statement is a convenient way to handle multiple conditions:
#!/bin/bash
color="red"
case $color in
red)
echo "Color is red"
;;
green)
echo "Color is green"
;;
blue)
echo "Color is blue"
;;
*)
echo "Unknown color"
;;
esac
# case also suuport patterns
character = 12
case $character in
# Check for letters
[[:lower:]] | [[:upper:]] ) echo "You typed the letter $character"
;;
# Check for digits
[0-9] ) echo "You typed the digit $character"
;;
# Check for anything else
* ) echo "You did not type a letter or a digit"
esac
Loops
Loops allow you to execute a block of code multiple times. Bash supports several types of loops: for, while, and until.
For Loop
The for loop iterates over a list of items. Here’s an example that prints numbers 1 to 5:
for i in {1..5}; do
echo "Number: $i"
done
You can also iterate over a list of strings:
for fruit in apple banana cherry; do
echo "Fruit: $fruit"
done
While Loop
The while loop continues executing as long as a specified condition is true. Here’s a simple countdown example:
count=1
until [ $count -gt 5 ]; do
echo "Number: $count"
((count++)) # Increment count
done
# infinite loop
while true; do
: # Do nothing
done
operators supported by while loop :
- lt : less than
- le : less than or equal to
- gt : greater than
- ge : greater than or equal to
- eq : equal to
- ne : not equal to
alternative syntax :
You can also use (( )) for arithmetic comparisons, which is often cleaner syntax/style:
#!/bin/bash
count=1
while (( count < 5 )); do
echo "Count is: $count"
((count++)) # Increment count
done
Nested Loops
You can also nest loops for more complex iterations. For instance, a loop within another loop can be used to create a multiplication table:
for i in {1..5}; do
for j in {1..5}; do
echo "$i * $j = $((i * j))"
done
done
Break and Continue
- Break: Use break to exit a loop prematurely
- Continue: Use continue to skip the current iteration and proceed to the next one:
for i in {1..10}; do
if [ $i -eq 5 ]; then
break # Exit loop when i is 5
fi
echo "Number: $i"
done
for i in {1..5}; do
if [ $i -eq 3 ]; then
continue # Skip when i is 3
fi
echo "Number: $i"
done
Command Substitution
Command substitution in Bash allows you to capture the output of a command and use it as an argument in another command or assign it to a variable. This is typically done using backticks (`) or the more modern syntax $(…). The latter is preferred for readability and nesting.
# Using backticks
my_var=`command`
# alternatively you can use $() systax [preffered way]
my_var=$(command)
# Examples
right_now="$(date +"%x %r %Z")"
home_dir=$(echo "My home directory is: $(echo $HOME)")
file_count=$(ls | wc -l)
echo "There are $file_count files in the current directory."
echo "Number of logged-in users: $(who | wc -l)"
Exit status
Commands, including scripts and shell functions, return a value to the system upon termination, known as the exit status. This value, which is an integer between 0 and 255, signifies whether the command was executed successfully or encountered an error. By convention, an exit status of zero indicates success, while any other value indicates failure. The shell offers a parameter that allows us to check the exit status, as demonstrated below:
#!/bin/bash
# Run a command
mkdir my_directory
# Check the exit status of the command
if [ $? -eq 0 ]; then
echo "Directory created successfully."
else
echo "Failed to create directory."
fi
# Run another command
rm my_directory
# Check the exit status of the second command
if [ $? -eq 0 ]; then
echo "Directory removed successfully."
else
echo "Failed to remove directory."
fi
Test command
In Bash, the test command (or its shorthand [ ]) allows you to check various conditions using specific options
#!/bin/bash
# Define variables
file="sample.txt"
# Test if a file exists
if test -e "$file"; then
echo "$file exists."
else
echo "$file does not exist."
fi
alternative syntax
#!/bin/bash
if [ -f .bashrc ]; then
echo "You have a .bash_rc. Things are fine."
else
echo "Yikes! You have no .bash_rc!"
fi
Partial list of file operators supported by test command
- -a FILE True if file exists.
- -b FILE True if file is block special.
- -c FILE True if file is character special.
- -d FILE True if file is a directory.
- -e FILE True if file exists.
- -f FILE True if file exists and is a regular file.
- -h FILE True if file is a symbolic link.
- -L FILE True if file is a symbolic link.
- -k FILE True if file has its `sticky’ bit set.
- -r FILE True if file is readable by you.
- -s FILE True if file exists and is not empty.
- -w FILE True if the file is writable by you.
- -x FILE True if the file is executable by you.
- -N FILE True if the file has been modified since it was last read.
- FILE1 -nt FILE2 True if file1 is newer than file2 (according to (modification date).
- FILE1 -ot FILE2 True if file1 is older than file2.
- FILE1 -ef FILE2 True if file1 is a hard link to file2.
#!/bin/bash
# Create some test files for demonstration
touch file1.txt # Regular file
touch file2.txt # Another regular file
echo "Hello, World!" > file3.txt # Non-empty file
touch -d "1 day ago" file4.txt # Older file
ln file1.txt file5.txt # Create a hard link
mkdir test_directory # Create a directory
mkdir empty_directory # Create an empty directory
# Check if file1.txt exists
if [ -e "file1.txt" ]; then
echo "file1.txt exists."
else
echo "file1.txt does not exist."
fi
# Check if file2.txt is a regular file
if [ -f "file2.txt" ]; then
echo "file2.txt is a regular file."
fi
# Check if file3.txt is not empty
if [ -s "file3.txt" ]; then
echo "file3.txt is not empty."
fi
# Check if file4.txt is older than file1.txt
if [ "file4.txt" -ot "file1.txt" ]; then
echo "file4.txt is older than file1.txt."
fi
# Check if file1.txt is newer than file4.txt
if [ "file1.txt" -nt "file4.txt" ]; then
echo "file1.txt is newer than file4.txt."
fi
# Check if file1.txt is a symbolic link
if [ -h "file1.txt" ]; then
echo "file1.txt is a symbolic link."
else
echo "file1.txt is not a symbolic link."
fi
# Check if file5.txt is a hard link to file1.txt
if [ "file5.txt" -ef "file1.txt" ]; then
echo "file5.txt is a hard link to file1.txt."
fi
# Check if test_directory is a directory
if [ -d "test_directory" ]; then
echo "test_directory is a directory."
else
echo "test_directory is not a directory."
fi
# Check if empty_directory is a directory
if [ -d "empty_directory" ]; then
echo "empty_directory is a directory."
fi
# Check if file1.txt is a directory
if [ -d "file1.txt" ]; then
echo "file1.txt is a directory."
else
echo "file1.txt is not a directory."
fi
Partial list of string operators supported by test command
- -z STRING ,True if string is empty
- -n STRING , STRING True if string is not empty.
- STRING1 = STRING2 , True if the strings are equal
- STRING1 != STRING2 , True if the strings are not equal.
#!/bin/bash
# Sample strings
string1=""
string2="Hello"
string3="Hello"
# Check if string1 is empty
if [ -z "$string1" ]; then
echo "string1 is empty."
else
echo "string1 is not empty."
fi
# Check if string2 is not empty
if [ -n "$string2" ]; then
echo "string2 is not empty."
else
echo "string2 is empty."
fi
# Check if string2 and string3 are equal
if [ "$string2" = "$string3" ]; then
echo "string2 and string3 are equal."
else
echo "string2 and string3 are not equal."
fi
# Check if string2 and string1 are not equal
if [ "$string2" != "$string1" ]; then
echo "string2 and string1 are not equal."
else
echo "string2 and string1 are equal."
fi
Other operators:
- -v VAR True if the shell variable VAR is set.
- EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.
- EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.
- eq, -ne,-lt, -le, -gt, -ge
#!/bin/bash
# Declare some variables
var1="Hello"
var2=""
var3="World"
# Check if var1 is set
if [ -v var1 ]; then
echo "var1 is set."
else
echo "var1 is not set."
fi
# Check if var2 is set
if [ -v var2 ]; then
echo "var2 is set."
else
echo "var2 is not set."
fi
# Example using -a (AND)
if [ -v var1 -a -n "$var3" ]; then
echo "Both var1 is set and var3 is not empty."
else
echo "Either var1 is not set or var3 is empty."
fi
# Example using -o (OR)
if [ -v var2 -o -v var3 ]; then
echo "Either var2 is set or var3 is set."
else
echo "Neither var2 nor var3 is set."
fi
Taking input from the user
#!/bin/bash
# using if else statement
read -p "Enter a number> " character
if [ "$character" = "1" ]; then
echo "You entered one."
elif [ "$character" = "2" ]; then
echo "You entered two."
elif [ "$character" = "3" ]; then
echo "You entered three."
else
echo "You did not enter a number between 1 and 3."
fi
# using case statement
read -p "Enter a number> " character
case $character in
1 ) echo "You entered one."
;;
2 ) echo "You entered two."
;;
3 ) echo "You entered three."
;;
* ) echo "You did not enter a number between 1 and 3."
esac
# with timeout
echo -n "Type something within 3 seconds > "
if read -t 3 response; then
echo "Great"
else
echo "you are too slow!"
fi
# withtimeout && hidden user input
echo -n "Type something within 3 seconds > "
if read -ts 3 response; then
echo "Great"
else
echo "you are too slow!"
fi
explanation
- read: This command is used to read a line of input from the user.
- -p: This option allows you to specify a prompt that will be displayed to the user. In this case, the prompt is “Enter a number between 1 and 3 inclusive > “.
- character: This is the variable name where the input will be stored. After the user enters a number and hits Enter, the value they entered will be assigned to this variable.
- -t: creates a timeout
- -s: hides the user input
Heredoc in bash
The cat <<- EOF construct in Bash is used to create a here document (often abbreviated as heredoc). This allows you to define a block of text that can be treated as standard input to a command, in this case, cat
cat <<EOF syntax
Syntax Breakdown :
- cat: This command concatenates and displays the content passed to it. In this context, it’s used to output the heredoc content.
- <<-: The << operator tells Bash to start a here document. The – allows for leading tabs in the text, which can help with indentation.
- EOF: This is a delimiter that indicates the start and end of the here document. You can use any string as the delimiter, but it’s common to use EOF, EOF, or similar.
# The use of <<- allows for indentation with tabs. If you used just <<, you would need to match the indentation exactly when closing the heredoc.
cat <<- EOF
This is a heredoc example.
You can include variables like USER: $USER
And it will print their values.
EOF
# Assign multi-line string to a shell variable
sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)
# Pass multi-line string to a file in Bash
cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF
# Pass multi-line string to a pipe in Bash
cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF
String concatenation
Using += Operator :
# Initial variable
my_var="Hello"
# Append a string
my_var+=" World!"
# Display the result
echo "$my_var" # Output: Hello World!
Using printf :
# Initial variable
my_var="Hello"
# Append a string using printf
printf -v my_var "%s World!" "$my_var"
# Display the result
echo "$my_var" # Output: Hello World!
Using echo and Command Substitution :
# Initial variable
my_var="Hello"
# Append a string
my_var="$(echo "$my_var World!")"
# Display the result
echo "$my_var" # Output: Hello World!
Trap command
The trap command allows us to execute a command when our script receives a signal. It works like this:
trap arg signals
#!/bin/bash
trap " echo 'i am going'; exit" SIGHUP SIGINT SIGTERM
read -p "Print file? [y/n]: "
# other examples
# it is most used for clean up functions like this
#!/bin/bash
clean_up() {
# Perform program exit housekeeping
exit
}
trap clean_up SIGHUP SIGINT SIGTERM
read -p "Print file? [y/n]: "
# doing something
clean_up
Functions
Bash also have support for functions .
#!/bin/bash
#Exmaple 1 with single parametre
greet() {
echo "Hello, $1!"
}
greet "Alice"
greet "Bob"
#Exmaple 2 with 2 input parameter
add() {
local sum=$(( $1 + $2 ))
echo "$sum" # Print the sum (can be captured)
}
result=$(add 5 3)
echo "The sum is: $result"
# Example 3 with defaut parameter
greet() {
local name=${1:-"World"} # Default to "World" if no argument
echo "Hello, $name!"
}
greet "Alice"
greet # Will use the default
# Example 4 with return statement
is_even() {
if [ $(( $1 % 2 )) -eq 0 ]; then
return 0 # Success (true)
else
return 1 # Failure (false)
fi
}
if is_even 4; then
echo "4 is even."
else
echo "4 is odd."
fi
if is_even 5; then
echo "5 is even."
else
echo "5 is odd."
fi
# Example 5 with Array arguments
print_array() {
local arr=("$@") # Capture all arguments as an array
for element in "${arr[@]}"; do
echo "$element"
done
}
# Function call with array
my_array=("apple" "banana" "cherry")
print_array "${my_array[@]}"
Conclusion
Bash shell scripting is a powerful skill that allows you to automate tasks and streamline workflows. By mastering variables, input parameter parsing, and loops, you can create flexible and efficient scripts. Whether you’re managing system processes or automating repetitive tasks, these scripting fundamentals will serve you well in your tech journey.
So, start experimenting with your own scripts, and take your Bash skills to the next level! Happy scripting!