Skip to content

2 - Advanced Bash Script

Linux Directory Structure

DirectoriesDescription
/binbinary or executable programs.
/etcsystem configuration files.
/homehome directory. It is the default current directory.
/optoptional or third-party software.
/tmptemporary space, typically cleared on reboot.
/usrUser related programs.
/varlog files.

File descriptors (FDs) are numeric identifiers that represent open files or streams in Unix/Linux systems. In bash, every process automatically gets three standard file descriptors:

Standard File Descriptors

0 - stdin (Standard Input)

  • Purpose: Where a program reads input from

  • Default: Connected to keyboard/terminal

  • Example: When you type commands or data

1 - stdout (Standard Output)

  • Purpose: Where a program sends normal output

  • Default: Connected to terminal screen

  • Example: Regular command output, echo results

2 - stderr (Standard Error)

  • Purpose: Where a program sends error messages

  • Default: Connected to terminal screen (same as stdout)

  • Example: Error messages, warnings

#!/bin/bash
echo "=== File Descriptor Redirection Demo ==="
# Setup: Create some test files
echo -e "banana\napple\ncherry" > fruits.txt
echo "Setting up demo files..."
echo -e "\n1. REDIRECT STDOUT TO FILE"
echo "This goes to a file" > message.txt
echo "✓ Check message.txt - you won't see the output on screen"
echo -e "\n2. REDIRECT STDERR TO FILE"
ls thisfiledoesnotexist 2> error_log.txt
echo "✓ Error message went to error_log.txt instead of screen"
echo -e "\n3. REDIRECT BOTH STDOUT AND STDERR"
# This command will produce both output and an error
{ echo "Success message"; ls fakefile; } > all_output.txt 2>&1
echo "✓ Both success and error messages went to all_output.txt"
echo -e "\n4. SHORTHAND FOR BOTH"
{ echo "Another success"; ls anotherfakefile; } &> shorthand_output.txt
echo "✓ Same result using &> shorthand"
echo -e "\n5. REDIRECT STDIN FROM FILE"
echo "Sorting fruits from fruits.txt:"
sort < fruits.txt
echo -e "\n6. DISCARD OUTPUT (send to /dev/null)"
echo "Running ls on fake file - you won't see any error:"
ls nonexistent 2> /dev/null
echo "✓ Error was discarded"
echo -e "\n7. DISCARD EVERYTHING"
{ echo "This output disappears"; ls fakefile; } &> /dev/null
echo "✓ Both output and errors were discarded"
echo -e "\n=== Demo complete! Check these files: ==="
echo "- message.txt"
echo "- error_log.txt"
echo "- all_output.txt"
echo "- shorthand_output.txt"
# Cleanup option
echo -e "\nTo clean up demo files, run:"
echo "rm -f message.txt error_log.txt all_output.txt shorthand_output.txt fruits.txt"
  • Global variable
#!/bin/bash
# Global variable
username="Alice"
function display_user() {
echo "User: $username" # Can access global variable
}
function change_user() {
username="Bob" # Modifies the global variable!
}
echo "Initial: $username" # Alice
display_user # User: Alice
change_user
echo "After change: $username" # Bob (global was modified)

OUTPUT

Terminal window
c@c:~/cos3105$ ./test.sh
Initial: Alice
User: Alice
After change: Bob
  • local variable
#!/bin/bash
username="Alice" # Global
function test_local() {
local username="Bob" # Local variable, shadows global
echo "Inside function: $username" # Bob
}
echo "Before: $username" # Alice
test_local # Inside function: Bob
echo "After: $username" # Alice (global unchanged)

OUTPUT

Terminal window
c@c:~/cos3105$ ./test.sh
Before: Alice
Inside function: Bob
After: Alice
  • echo for simple output
  • printf for formatted output
#!/bin/bash
name="John"
age=25
score=87.5
# echo - basic concatenation
echo "Name: $name, Age: $age, Score: $score"
# printf - formatted output
printf "Name: %-10s Age: %3d Score: %6.2f\n" "$name" "$age" "$score"
# Output: Name: John Age: 25 Score: 87.50
# Without formatting
printf "%s\n" "hello"
# Output: hello
# With %-10s (left-aligned, 10 characters wide)
printf "%-10s|\n" "hello"
# Output: hello |
# ^^^^^ ^ (5 spaces added to make it 10 chars total)
# Compare with right-aligned (no minus sign)
printf "%10s|\n" "hello"
# Output: hello|
# ^^^^^ ^ (5 spaces before the text)

OUTPUT

Terminal window
c@c:~/cos3105$ ./test.sh
Name: John, Age: 25, Score: 87.5
Name: John Age: 25 Score: 87.50
hello
hello |
hello|
#!/bin/bash
var1=$1
var2=$2
var3=$3
var4=$4
add(){
#Note the $1 and $2 variables here are not the same of the
#main script...
echo "The first argument to this function is $1"
echo "The second argument to this function is $2"
result=$(($1+$2))
echo $result
}
add $var1 $var2
add $var3 $var4

OUTPUT

Terminal window
c@c:~/cos3105$ ./test.sh 1 2 3 4
The first argument to this function is 1
The second argument to this function is 2
3
The first argument to this function is 3
The second argument to this function is 4
7
#!/bin/bash
var1=$1
var2=$2
var3=$3
var4=$4
add(){
result=$(($1+$2))
echo $result
}
minus(){
result=$(($1-$2))
echo $result
}
result1=$(add $var1 $var2)
result2=$(add $var3 $var4)
result3=$(minus $result2 $result1)
echo "$result2 - $result1 = $result3"

OUTPUT

Terminal window
c@c:~/cos3105$ ./test.sh 1 2 3 4
7 - 3 = 4
  • read string into array
#!/bin/bash
read first second third <<< "a b c"
echo $first # a
echo $second # b
echo $third # c
# With -a (reads into array)
read -a array <<< "a b c"
echo ${array[0]} # a
echo ${array[1]} # b
echo ${array[2]} # c

OUTPUT

Terminal window
c@c:~/cos3105$ ./test.sh
a
b
c
a
b
c
  • return string of array and read array
#!/bin/bash
get_user_info() {
echo "john" "doe" "30"
}
read -a user_info <<< "$(get_user_info)"
printf "Name = %s, Last name = %s, Age = %s\n" "${user_info[0]}" "${user_info[1]}" "${user_info[2]}"

OUTPUT

Terminal window
c@c:~/cos3105$ ./test.sh
Name = john, Last name = doe, Age = 30
  • return string of array with comma separate and read array
  • IFS=", " for tells read to split string with ", "
#!/bin/bash
get_user_info() {
echo "john, doe, 30"
}
IFS=", " read -a user_info <<< "$(get_user_info)"
printf "Name = %s, Last name = %s, Age = %s\n" "${user_info[0]}" "${user_info[1]}" "${user_info[2]}"

OUTPUT

Terminal window
c@c:~/cos3105$ ./test.sh
Name = john, Last name = doe, Age = 30
  • dialog: Original tool, developed first (1990s)
  • whiptail: Fork/reimplementation of dialog, designed to be more lightweight
  • dialog vs whiptail
    • dialog more widgets and features
    • whiptail less widgets
Terminal window
message="Whiptail: Tool Basics"
whiptail --msgbox --title "Intro to Whiptail" "$message" 0 0

OUTPUT

Whiptail Message Box

#!/bin/bash
if whiptail --title "Example Dialog" --yesno "This is an example of a yes/no box." 0 0; then
echo "User selected Yes, exit status was $?."
else
echo "User selected No, exit status was $?."
fi

Whiptail Yes/No Dialog

#!/bin/bash
username="$(whiptail --inputbox "What is your username?" 10 30 "$USER" 3>&1 1>&2 2>&3)"
printf "Your username is : %s\n" "${username}"

OUTPUT

Whiptail Input dialog

#!/bin/bash
password="$(whiptail --passwordbox "Please enter your password:" 0 0 3>&1 1>&2 2>&3)"
printf "Your password is : %s\n" "$password"

OUTPUT

Whiptail Password dialog

#!/bin/bash
result=$(whiptail \
--menu "What is your favorite shell?" 0 0 2 \
"Bash" "(Bourne Again shell)" \
"Zsh" "(Z-Shell)" \
3>&1 1>&2 2>&3)
echo "You selected: $result"

OUTPUT

Whiptail Menu dialog

#!/bin/bash
result=$(whiptail \
--checklist "What is your favorite shell?" 0 0 3 \
"Bash" "(Bourne Again shell)" 1 \
"Zsh" "(Z-Shell)" 0 \
"Dash" "(Dash shell)" 0 \
3>&1 1>&2 2>&3)
echo "You selected: $result"

OUTPUT

Whiptail multiple selected dialog

#!/bin/bash
cat > temp.txt << 'EOF'
This is a very long text that will demonstrate
the scrollable text box feature in whiptail.
You can scroll up and down through this content
using the arrow keys.
This is a very long text that will demonstrate
the scrollable text box feature in whiptail.
You can scroll up and down through this content
using the arrow keys.
This is a very long text that will demonstrate
the scrollable text box feature in whiptail.
You can scroll up and down through this content
using the arrow keys.
EOF
whiptail --textbox temp.txt 0 0 --scrolltext
rm temp.txt

OUTPUT

Whiptail Textbox dialog

#!/bin/bash
result=$(whiptail \
--radiolist "What is your favorite shell?" 0 0 3 \
"Bash" "(Bourne Again shell)" 1 \
"Zsh" "(Z-Shell)" 0 \
"Dash" "(Dash shell)" 0 \
3>&1 1>&2 2>&3)
echo "You selected: $result"

OUTPUT

Whiptail radio lists dialog

#!/bin/bash
# Simple file processing progress bar
{
for i in {0..100..20}; do
echo "XXX"
echo $i
echo "Processing file $((i/20 + 1)) of 6..."
echo "XXX"
sleep 1
done
} | whiptail --gauge "File Processing" 6 50 0
echo "Done!"

OUTPUT

Whiptail progress bar

  • Example of C program
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <command>\n", argv[0]);
return 1; // Error code
}
// Your logic here
if (argc == 2) {
fprintf(stderr, "Error: Something went wrong\n");
return 2; // Specific error code
}
if (argc > 2) {
fprintf(stderr, "Error: File not found\n");
return 3; // Different error code
}
// Success case
printf("Operation successful\n");
return 0; // Success
}
  • Example of bash script
#!/bin/bash
# Capture stdout and stderr separately
result=$(./contacts2 mode email email_body 2>&1)
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "Success: $result"
else
echo "Error (exit code $exit_code): $result"
fi

OUTPUT

Terminal window
c@c:~/cos3105$ ./contacts2.sh
Error (exit code 3): Error: File not found