Writing Bash Scripts
1. Creating Script File
| Step | Description | Action |
|---|---|---|
| 1 | Choose a location to save the script | Create a new file in your preferred directory (e.g., ~/scripts/) |
| 2 | Add the shebang line | Start with #!/bin/bash to indicate the interpreter |
| 3 | Write the script content | Add commands and logic as needed |
| 4 | Save the file | Ensure it has a meaningful name (e.g., hello.sh) |
| 5 | Make it executable | Run chmod +x hello.sh |
Note: The shebang (
#!) tells the system which interpreter to use when running the script.
2. Making Scripts Executable
| Step | Description | Action |
|---|---|---|
| 1 | Write the script content | Create a file with your bash commands, e.g., script.sh |
| 2 | Add shebang | Start with #!/bin/bash as the first line |
| 3 | Set execute permissions | Run chmod +x script.sh |
| 4 | Run the script | Execute with ./script.sh |
Note: The shebang line tells the system which interpreter to use to run the script.
3. Using Variables
| Variable | Type | Description | Example |
|---|---|---|---|
| VAR_NAME | Local | Standard variable declaration | name="John" |
| export VAR_NAME | Global | Makes variable available to child processes | export PATH="/usr/bin" |
| $VAR_NAME | Reference | Accesses variable value | echo "Hello $name" |
| ${VAR_NAME} | Brace Expansion | Used to avoid ambiguity in variable names | echo "User: ${USER}" |
| VAR_NAME=value | Assignment | Assigns value to variable | count=5 |
Example: Variable Assignment and Usage
name="Alice"
age=30
echo "Hello, $name! You are $age years old."
Note: Variables in Bash are case-sensitive and must not contain spaces around the equals sign.
Warning: Using
VAR_NAME=value without export limits the variable scope to the current shell session.
4. Reading User Input
| Command | Description | Example |
|---|---|---|
| read | Reads a line of input from the user | |
| read -p | Displays a prompt before reading input | |
| read -s | Suppresses echoing input (useful for passwords) | |
| read -t | Times out after specified seconds | |
Note: The
read command reads from standard input (stdin) by default.Warning: Be cautious with
-s option as it may not work consistently across all terminal environments.5. Using Command-Line Arguments
| Variable | Description | Example |
|---|---|---|
| $0 | Name of the script | ./my_script.sh |
| $1 | First argument | ./script.sh arg1 |
| $2 | Second argument | ./script.sh arg1 arg2 |
| $_ | Last argument passed to the script | ./script.sh arg1 arg2 |
| $# | Number of arguments | ./script.sh arg1 arg2 → 2 |
| $* | All arguments as a single string | ./script.sh "arg1 arg2" |
| $@ | All arguments as separate strings | ./script.sh arg1 arg2 |
| $? | Exit status of the last command | echo $? |
Example: Accessing Arguments
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Total arguments: $#"
Example: Handling Optional Arguments
#!/bin/bash
if [ $# -eq 0 ]; then
echo "No arguments provided"
else
echo "First argument is: $1"
fi
6. Implementing Conditional Statements
| Statement | Syntax | Description |
|---|---|---|
| if | |
Basic conditional execution |
| if-else | |
Execute commands based on condition |
| if-elif-else | |
Multiple condition checks |
Example: Basic if-else
#!/bin/bash
if [ "$USER" = "root" ]; then
echo "You are root"
else
echo "You are not root"
fi
Example: Nested if-else
#!/bin/bash
if [ "$USER" = "root" ]; then
if [ -d "/etc" ]; then
echo "Root user with access to /etc"
fi
else
echo "Not root user"
fi
Note: Use
[ ] for tests, not [[ ]] unless you need extended features like pattern matching or string operators.Example: Using test command
#!/bin/bash
if test -f "file.txt"; then
echo "file.txt exists"
else
echo "file.txt does not exist"
fi
7. Using Test Conditions
| Condition | Description | Examples |
|---|---|---|
| [ string ] | True if string is non-empty | [ "hello" ] |
| [ -z string ] | True if string is empty | [ -z "$var" ] |
| [ -n string ] | True if string is non-empty | [ -n "$var" ] |
| [ string1 = string2 ] | True if strings are equal | [ "$a" = "$b" ] |
| [ string1 != string2 ] | True if strings are not equal | [ "$a" != "$b" ] |
| [ integer1 -eq integer2 ] | True if integers are equal | [ "$a" -eq 5 ] |
| [ integer1 -ne integer2 ] | True if integers are not equal | [ "$a" -ne 5 ] |
| [ integer1 -lt integer2 ] | True if integer1 is less than integer2 | [ "$a" -lt 10 ] |
| [ integer1 -le integer2 ] | True if integer1 is less than or equal to integer2 | [ "$a" -le 10 ] |
| [ integer1 -gt integer2 ] | True if integer1 is greater than integer2 | [ "$a" -gt 5 ] |
| [ integer1 -ge integer2 ] | True if integer1 is greater than or equal to integer2 | [ "$a" -ge 5 ] |
| [ -f filename ] | True if file exists and is a regular file | [ -f "/etc/passwd" ] |
| [ -d directory ] | True if directory exists | [ -d "/tmp" ] |
| [ -r filename ] | True if file is readable | [ -r "/etc/passwd" ] |
| [ -w filename ] | True if file is writable | [ -w "/tmp/script.sh" ] |
| [ -x filename ] | True if file is executable | [ -x "/bin/ls" ] |
| [ -e filename ] | True if file exists (regardless of type) | [ -e "/etc/passwd" ] |
| [ -s filename ] | True if file exists and has size greater than zero | [ -s "/tmp/file" ] |
| [ -L filename ] | True if file is a symbolic link | [ -L "/tmp/link" ] |
| [ -O filename ] | True if file is owned by the effective user ID | [ -O "/tmp/file" ] |
| [ -G filename ] | True if file is owned by the effective group ID | [ -G "/tmp/file" ] |
| [ -N filename ] | True if file has been modified since last read | [ -N "/tmp/file" ] |
| [ -B filename ] | True if file is a block device | [ -B "/dev/sda" ] |
| [ -c filename ] | True if file is a character device | [ -c "/dev/tty" ] |
| [ -p filename ] | True if file is a named pipe (FIFO) | [ -p "/tmp/fifo" ] |
| [ -S filename ] | True if file is a socket | [ -S "/tmp/socket" ] |
| [ -u filename ] | True if file has the set-user-ID bit set | [ -u "/usr/bin/passwd" ] |
| [ -g filename ] | True if file has the set-group-ID bit set | [ -g "/usr/bin/sudo" ] |
| [ -k filename ] | True if file has the sticky bit set | [ -k "/tmp/dir" ] |
| [ -t fd ] | True if file descriptor is a terminal | [ -t 0 ] |
| [ -o option ] | True if shell option is set | [ -o errexit ] |
| [ -a file ] | True if file exists (alias for -e) | [ -a "/etc/passwd" ] |
| [ -b file ] | True if file is a block special device | [ -b "/dev/sda" ] |
| [ -h file ] | True if file is a symbolic link (alias for -L) | [ -h "/tmp/link" ] |
| [ -w file ] | True if file is writable (alias for -w) | [ -w "/tmp/file" ] |
| [ -x file ] | True if file is executable (alias for -x) | [ -x "/bin/ls" ] |
| [ -z string ] | True if string is empty (alias for -z) | [ -z "$var" ] |
| [ -n string ] | True if string is non-empty (alias for -n) | [ -n "$var" ] |
Example: Using test conditions in an if statement
#!/bin/bash
if [ -f "/etc/passwd" ]; then
echo "The file /etc/passwd exists."
else
echo "The file /etc/passwd does not exist."
fi
if [ "$USER" = "root" ]; then
echo "You are root."
else
echo "You are not root."
fi
Example: Combining test conditions with logical operators
#!/bin/bash
if [ "$USER" = "root" ] && [ -d "/root" ]; then
echo "You are root and /root directory exists."
fi
if [ "$USER" = "root" ] || [ "$USER" = "admin" ]; then
echo "You are either root or admin."
fi
Note: The
[ command is a synonym for the test command. Both forms are equivalent.
Warning: Always quote variables in test conditions to prevent word splitting and pathname expansion. For example, use
[ "$var" = "value" ] instead of [ $var = value ].
8. Implementing Loops
| Loop Type | Description | Example |
|---|---|---|
| for | Iterates over a list of items or a range of numbers | |
| while | Continues executing as long as a condition is true | |
| until | Executes until a condition becomes true | |
Note: The
for loop in Bash can iterate over arrays, command output, or a list of items. The while loop continues as long as the condition evaluates to true.
Warning: Infinite loops can occur if the loop condition never becomes false. Always ensure that the loop variable is updated inside the loop body.
9. Using Case Statements
| Structure | Description | Example |
|---|---|---|
| case $variable in | Starts the case statement with a variable to test | case $1 in |
| pattern) | Defines a pattern to match against the variable | |
| *) | Default case if no patterns match | |
Example: Basic Case Statement
#!/bin/bash
case $1 in
start)
echo "Starting service"
;;
stop)
echo "Stopping service"
;;
restart)
echo "Restarting service"
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
Note: The
;; is required to end each case block, and the *) pattern matches any value not matched by previous patterns.10. Defining Functions
| Aspect | Description | Notes |
|---|---|---|
| Function Definition Syntax | function_name() { ... } |
Alternative syntax: function function_name { ... } |
| Function with Parameters | greet() { echo "Hello, $1"; } |
Use $1, $2, etc. for arguments |
| Return Values | return 0 |
Only exit status (0-255) can be returned. Use echo for output. |
| Local Variables | local var_name=value |
Prevents variable leakage to global scope |
Example: Function with Parameters
calculate() {
local result=$(( $1 + $2 ))
echo "Sum is: $result"
}
calculate 5 3
Example: Function with Local Variables
process_data() {
local temp_file="/tmp/data_$"
echo "Processing..." > "$temp_file"
cat "$temp_file"
rm "$temp_file"
}
process_data
Note: Functions must be defined before they are called. Bash does not support forward declarations.
11. Handling Exit Codes
| Exit Code | Description | Usage |
|---|---|---|
| 0 | Successful execution | Default return value for successful commands |
| 1 | General error | Most common non-zero exit code for failures |
| 2 | Usage error | Incorrect usage of a command (e.g., missing arguments) |
| 126 | Command cannot execute | Permission denied or command not found |
| 127 | Command not found | Shell cannot find the command in PATH |
| 128 | Invalid argument to exit | Exit with invalid argument (e.g., exit 256) |
| 128+n | Terminated by signal n | Exit due to a signal (e.g., 128+9 = killed by SIGKILL) |
Example: Using exit codes in a script
#!/bin/bash
if ls /nonexistent > /dev/null 2&&1; then
echo "Directory exists"
exit 0
else
echo "Directory does not exist"
exit 1
fi
Example: Checking exit code of a command
ls /etc
if [ $? -eq 0 ]; then
echo "Command succeeded"
else
echo "Command failed with exit code: $?"
fi
Note: The special variable
$? holds the exit status of the last executed command.
Warning: Exit codes are integers from 0 to 255. Values outside this range wrap around (e.g., exit 256 is equivalent to exit 0).
12. Implementing Error Handling
| Handler | Description | Usage |
|---|---|---|
| set -e | Exit immediately if a command exits with a non-zero status | set -e |
| set -u | Exit on use of undefined variables | set -u |
| set -o pipefail | Make pipelines fail if any component fails | set -o pipefail |
Example: Using set options
set -e
set -u
set -o pipefail
'command that might fail' | 'another command that might fail'
Note: These options should be set at the beginning of your script to ensure consistent error behavior.
| Method | Description | Example |
|---|---|---|
| trap | Catch signals and clean up before exit | trap 'rm -f /tmp/myfile' EXIT |
| check exit status | Use $? to check command success |
if [ $? -ne 0 ]; then echo "Failed"; exit 1; fi |
| function error handling | Wrap commands in functions with error checks | myfunc() { command || return 1; } |