How to Check if a String Contains a Substring in Bash
String substring checking is a fundamental operation in Bash scripting. Whether you're validating input, parsing log files, or filtering data, knowing how to efficiently check if one string contains another is essential. Bash provides several methods for substring detection, each with different capabilities and performance characteristics.
Prerequisites
You should have basic knowledge of Bash scripting and familiarity with variables and conditional statements. A Unix-like system (Linux, macOS, or WSL) is needed to test the examples.
Using Pattern Matching with Double Brackets
The most common and readable method uses Bash's built-in pattern matching with double brackets:
#!/bin/bash
check_substring() {
local main_string="$1"
local substring="$2"
if [[ "$main_string" == *"$substring"* ]]; then
echo "✓ '$substring' found in '$main_string'"
return 0
else
echo "✗ '$substring' not found in '$main_string'"
return 1
fi
}
# Test examples
text="The quick brown fox jumps over the lazy dog"
check_substring "$text" "quick" # Found
check_substring "$text" "fox" # Found
check_substring "$text" "cat" # Not found
check_substring "$text" "QUICK" # Not found (case sensitive)
The pattern *"$substring"*
uses wildcards where *
matches any characters, making this a simple and efficient substring check.
Case-Insensitive Substring Detection
For case-insensitive matching, convert both strings to lowercase or use specific bash options:
#!/bin/bash
case_insensitive_check() {
local main_string="$1"
local substring="$2"
# Method 1: Convert to lowercase
local main_lower="${main_string,,}"
local sub_lower="${substring,,}"
if [[ "$main_lower" == *"$sub_lower"* ]]; then
echo "✓ '$substring' found in '$main_string' (case-insensitive)"
return 0
else
echo "✗ '$substring' not found in '$main_string' (case-insensitive)"
return 1
fi
}
# Method 2: Using shopt nocasematch
case_insensitive_shopt() {
local main_string="$1"
local substring="$2"
# Enable case-insensitive matching
shopt -s nocasematch
if [[ "$main_string" == *"$substring"* ]]; then
echo "✓ '$substring' found in '$main_string' (nocasematch)"
result=0
else
echo "✗ '$substring' not found in '$main_string' (nocasematch)"
result=1
fi
# Restore original setting
shopt -u nocasematch
return $result
}
# Test case-insensitive matching
text="The Quick Brown Fox"
case_insensitive_check "$text" "QUICK"
case_insensitive_shopt "$text" "brown"
Both methods achieve case-insensitive matching, with the shopt
approach being useful when you need temporary case-insensitive behavior.
Using grep for Substring Detection
The grep
command provides powerful pattern matching capabilities for substring detection:
#!/bin/bash
grep_substring_check() {
local main_string="$1"
local substring="$2"
if echo "$main_string" | grep -q "$substring"; then
echo "✓ '$substring' found using grep"
return 0
else
echo "✗ '$substring' not found using grep"
return 1
fi
}
# Case-insensitive grep
grep_case_insensitive() {
local main_string="$1"
local substring="$2"
if echo "$main_string" | grep -qi "$substring"; then
echo "✓ '$substring' found using grep -i"
return 0
else
echo "✗ '$substring' not found using grep -i"
return 1
fi
}
# Using grep with regex patterns
grep_pattern_check() {
local main_string="$1"
local pattern="$2"
if echo "$main_string" | grep -qE "$pattern"; then
echo "✓ Pattern '$pattern' matched using grep -E"
return 0
else
echo "✗ Pattern '$pattern' not matched using grep -E"
return 1
fi
}
# Examples
log_entry="2024-01-15 ERROR: Database connection failed"
grep_substring_check "$log_entry" "ERROR"
grep_case_insensitive "$log_entry" "error"
grep_pattern_check "$log_entry" "[0-9]{4}-[0-9]{2}-[0-9]{2}" # Date pattern
Grep is particularly useful when you need regular expression matching or when processing external input.
Practical Log File Processing
Here's a real-world example of substring checking for log analysis:
#!/bin/bash
analyze_log_file() {
local log_file="$1"
local error_count=0
local warning_count=0
local info_count=0
if [[ ! -f "$log_file" ]]; then
echo "Error: Log file not found: $log_file"
return 1
fi
echo "Analyzing log file: $log_file"
echo "================================"
while IFS= read -r line; do
# Check for different log levels
if [[ "$line" == *"ERROR"* ]]; then
((error_count++))
echo "ERROR: $line"
elif [[ "$line" == *"WARNING"* ]] || [[ "$line" == *"WARN"* ]]; then
((warning_count++))
echo "WARNING: $line"
elif [[ "$line" == *"INFO"* ]]; then
((info_count++))
fi
# Check for specific issues
if [[ "$line" == *"connection"* ]] && [[ "$line" == *"failed"* ]]; then
echo "⚠️ Connection issue detected: $line"
fi
if [[ "$line" == *"timeout"* ]]; then
echo "⏱️ Timeout detected: $line"
fi
done < "$log_file"
echo
echo "Summary:"
echo "Errors: $error_count"
echo "Warnings: $warning_count"
echo "Info messages: $info_count"
}
# Create sample log file for testing
cat > /tmp/sample.log << 'EOF'
2024-01-15 10:30:15 INFO: Application started
2024-01-15 10:30:20 INFO: Database connection established
2024-01-15 10:35:45 WARNING: High memory usage detected
2024-01-15 10:40:12 ERROR: Database connection failed
2024-01-15 10:40:15 INFO: Retrying connection
2024-01-15 10:40:30 ERROR: Connection timeout occurred
2024-01-15 10:45:00 INFO: Connection restored
EOF
analyze_log_file "/tmp/sample.log"
This example shows how substring checking helps categorize and filter log entries for analysis.
URL and Email Validation
Use substring checks for basic input validation:
#!/bin/bash
validate_email() {
local email="$1"
# Basic email validation using substring checks
if [[ "$email" == *"@"* ]] && [[ "$email" == *"."* ]]; then
# Check for valid format
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "✓ Valid email format: $email"
return 0
else
echo "✗ Invalid email format: $email"
return 1
fi
else
echo "✗ Email missing @ or . symbols: $email"
return 1
fi
}
validate_url() {
local url="$1"
# Check for common URL patterns
if [[ "$url" == *"http://"* ]] || [[ "$url" == *"https://"* ]]; then
if [[ "$url" == *"."* ]]; then
echo "✓ Valid URL format: $url"
return 0
else
echo "✗ URL missing domain extension: $url"
return 1
fi
else
echo "✗ URL missing http/https protocol: $url"
return 1
fi
}
check_domain_type() {
local url="$1"
if [[ "$url" == *".com"* ]]; then
echo "Commercial domain detected"
elif [[ "$url" == *".org"* ]]; then
echo "Organization domain detected"
elif [[ "$url" == *".edu"* ]]; then
echo "Educational domain detected"
elif [[ "$url" == *".gov"* ]]; then
echo "Government domain detected"
else
echo "Other domain type"
fi
}
# Test validation functions
test_emails=(
"[email protected]"
"invalid.email"
"test@domain"
"[email protected]"
)
test_urls=(
"https://www.example.com"
"http://test.org"
"ftp://files.example.com"
"www.example.com"
)
echo "Email validation:"
for email in "${test_emails[@]}"; do
validate_email "$email"
done
echo
echo "URL validation:"
for url in "${test_urls[@]}"; do
validate_url "$url"
check_domain_type "$url"
echo "---"
done
This demonstrates practical substring checking for common validation tasks.
Performance Comparison of Methods
Different substring checking methods have varying performance characteristics:
#!/bin/bash
performance_test() {
local test_string="This is a very long string that we will use to test the performance of different substring checking methods in Bash scripting"
local substring="performance"
local iterations=10000
echo "Performance test with $iterations iterations"
echo "============================================"
# Method 1: Pattern matching
echo "Testing pattern matching..."
time {
for ((i=1; i<=iterations; i++)); do
[[ "$test_string" == *"$substring"* ]] >/dev/null
done
}
# Method 2: grep
echo "Testing grep..."
time {
for ((i=1; i<=iterations; i++)); do
echo "$test_string" | grep -q "$substring" >/dev/null
done
}
# Method 3: case statement (for specific strings)
echo "Testing case statement..."
time {
for ((i=1; i<=iterations; i++)); do
case "$test_string" in
*"$substring"*) true ;;
*) false ;;
esac >/dev/null
done
}
}
# Run performance test
performance_test
Pattern matching with double brackets is typically the fastest for simple substring checks, while grep excels at complex pattern matching.
Advanced String Matching Functions
Create a comprehensive string matching utility:
#!/bin/bash
# Advanced string matching functions
string_utils() {
local operation="$1"
local main_string="$2"
local pattern="$3"
local flags="$4"
case "$operation" in
"contains")
if [[ "$flags" == *"i"* ]]; then
# Case-insensitive
local main_lower="${main_string,,}"
local pattern_lower="${pattern,,}"
[[ "$main_lower" == *"$pattern_lower"* ]]
else
[[ "$main_string" == *"$pattern"* ]]
fi
;;
"starts_with")
if [[ "$flags" == *"i"* ]]; then
local main_lower="${main_string,,}"
local pattern_lower="${pattern,,}"
[[ "$main_lower" == "$pattern_lower"* ]]
else
[[ "$main_string" == "$pattern"* ]]
fi
;;
"ends_with")
if [[ "$flags" == *"i"* ]]; then
local main_lower="${main_string,,}"
local pattern_lower="${pattern,,}"
[[ "$main_lower" == *"$pattern_lower" ]]
else
[[ "$main_string" == *"$pattern" ]]
fi
;;
"regex")
if [[ "$flags" == *"i"* ]]; then
shopt -s nocasematch
[[ "$main_string" =~ $pattern ]]
local result=$?
shopt -u nocasematch
return $result
else
[[ "$main_string" =~ $pattern ]]
fi
;;
"count")
# Count occurrences of substring
local temp="${main_string//[^$pattern]}"
echo "${#temp}"
;;
*)
echo "Invalid operation: $operation" >&2
return 1
;;
esac
}
# Wrapper functions for easier use
contains() { string_utils "contains" "$1" "$2" "$3"; }
starts_with() { string_utils "starts_with" "$1" "$2" "$3"; }
ends_with() { string_utils "ends_with" "$1" "$2" "$3"; }
matches_regex() { string_utils "regex" "$1" "$2" "$3"; }
count_occurrences() { string_utils "count" "$1" "$2"; }
# Test the utility functions
test_string="Hello World! This is a TEST string."
echo "String: '$test_string'"
echo "Contains 'test' (case-sensitive): $(contains "$test_string" "test" && echo "YES" || echo "NO")"
echo "Contains 'test' (case-insensitive): $(contains "$test_string" "test" "i" && echo "YES" || echo "NO")"
echo "Starts with 'hello' (case-insensitive): $(starts_with "$test_string" "hello" "i" && echo "YES" || echo "NO")"
echo "Ends with 'string.' (case-sensitive): $(ends_with "$test_string" "string." && echo "YES" || echo "NO")"
echo "Matches email pattern: $(matches_regex "$test_string" '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' && echo "YES" || echo "NO")"
This utility library provides a comprehensive set of string matching functions for various use cases.
Configuration File Processing
Use substring checking to parse configuration files:
#!/bin/bash
parse_config_file() {
local config_file="$1"
declare -A config_values
if [[ ! -f "$config_file" ]]; then
echo "Configuration file not found: $config_file"
return 1
fi
echo "Parsing configuration file: $config_file"
while IFS= read -r line; do
# Skip comments and empty lines
if [[ "$line" == \#* ]] || [[ -z "$line" ]]; then
continue
fi
# Check for key=value format
if [[ "$line" == *"="* ]]; then
key="${line%%=*}"
value="${line#*=}"
# Remove leading/trailing whitespace
key="${key// /}"
value="${value# }"
value="${value% }"
config_values["$key"]="$value"
echo "Found setting: $key = $value"
fi
# Check for specific configuration sections
if [[ "$line" == *"[database]"* ]]; then
echo "📊 Database configuration section found"
elif [[ "$line" == *"[server]"* ]]; then
echo "🖥️ Server configuration section found"
elif [[ "$line" == *"[logging]"* ]]; then
echo "📝 Logging configuration section found"
fi
done < "$config_file"
# Print summary
echo
echo "Configuration summary:"
for key in "${!config_values[@]}"; do
echo " $key: ${config_values[$key]}"
done
}
# Create sample configuration file
cat > /tmp/app.conf << 'EOF'
# Application Configuration
[server]
port=8080
host=localhost
debug=true
[database]
host=db.example.com
port=5432
name=myapp_db
user=dbuser
[logging]
level=INFO
file=/var/log/myapp.log
rotate=daily
EOF
parse_config_file "/tmp/app.conf"
This example shows how substring checking helps parse structured configuration files.
Next Steps
You now understand multiple methods for checking substrings in Bash, from simple pattern matching to advanced string processing. Consider exploring more sophisticated text processing with awk
and sed
, implementing fuzzy string matching, or building command-line tools that parse complex input formats. You might also want to investigate using these techniques for data validation, log analysis, or configuration management systems.
Good luck with your string processing and text manipulation tasks!
Found an issue?