Table of Contents#
- Prerequisites
- Understanding the Requirements
- Step-by-Step Implementation
- Testing the Function
- Customization Tips
- Conclusion
- References
Prerequisites#
Before starting, ensure you have:
- A Unix-like operating system (Linux, macOS, or WSL).
- Basic familiarity with shell scripting (Bash syntax).
- Access to standard command-line tools:
tail,grep, andtimeout(all included in coreutils, preinstalled on most systems). - A log file to test with (or create a dummy log file for testing).
Understanding the Requirements#
Let’s formalize the goals for our shell function:
| Requirement | Details |
|---|---|
| Input Parameters | Accept two arguments: (1) path to the log file, (2) target string to search. |
| Log Tailing | Follow the log file in real time (i.e., show new lines as they are added). |
| String Matching | Stop searching and exit successfully when the target string is found. |
| Timeout | Terminate after 20 minutes (1200 seconds) if the string is not found. |
| Error Handling | Validate inputs (e.g., log file exists, target string is provided). |
| "Throw Exception" | Use exit codes to signal success (0), timeout (non-zero), or errors (non-zero). |
Step-by-Step Implementation#
3.1 Define the Function and Validate Inputs#
First, we’ll define the function and ensure the user provides valid inputs. Add the following to your shell script (e.g., log_monitor.sh) or directly to your .bashrc/.zshrc for global access.
tail_log_for_string_with_timeout() {
# Define local variables for inputs
local log_file="$1"
local target_string="$2"
# Validate: Check if log file path and target string are provided
if [ $# -ne 2 ]; then
echo "Error: Invalid number of arguments." >&2
echo "Usage: $0 <log_file_path> <target_string>" >&2
return 1 # Exit with code 1 (error)
fi
# Validate: Check if log file exists and is readable
if [ ! -f "$log_file" ]; then
echo "Error: Log file '$log_file' not found or is not a file." >&2
return 1
fi
if [ ! -r "$log_file" ]; then
echo "Error: No read permissions for log file '$log_file'." >&2
return 1
fi
# Validate: Check if target string is non-empty
if [ -z "$target_string" ]; then
echo "Error: Target string cannot be empty." >&2
return 1
fi
# (Implementation continues below...)
}Explanation:
- The function is named
tail_log_for_string_with_timeoutfor clarity. - It accepts two parameters:
log_file(path to the log) andtarget_string(string to search). - Input validation ensures:
- Exactly 2 arguments are provided.
- The log file exists, is a regular file, and is readable.
- The target string is not empty.
- Errors are printed to
stderr(using>&2) for proper error handling.
3.2 Tail the Log and Search for the String#
Next, we’ll use tail to follow the log file and grep to search for the target string.
Add this code inside the function (after the validation steps):
# Tail the log file and search for the target string
# -F: Follow log file (reopen if rotated), -n 0: start from the end
# grep -m 1: Stop after first match (exit immediately when found)
tail -F -n 0 "$log_file" | grep -m 1 -- "$target_string"Explanation:
tail -F: "Follow" the log file. Unlike-f,-Fhandles log rotation (e.g., if the log is rotated bylogrotate,tail -Fwill reopen the new file).-n 0: Start reading from the end of the file (ignore existing lines; use-n +1to include all lines if needed).grep -m 1: Stop searching after the first match (-m 1= "max 1 match"). This ensuresgrepexits immediately when the target string is found, which in turn closes the pipe and stopstail.
3.3 Add the 20-Minute Timeout#
To limit the function to 20 minutes (1200 seconds), we’ll use the timeout command (part of coreutils), which terminates a command after a specified duration.
Wrap the tail | grep pipeline with timeout:
# Timeout after 20 minutes (1200 seconds). Capture exit code.
timeout 1200 tail -F -n 0 "$log_file" | grep -m 1 -- "$target_string"
local exit_code=$? # Capture the exit code of the pipelineExplanation:
timeout 1200: Terminate thetailcommand after 1200 seconds (20 minutes).--ingrep -- "$target_string": Ensuresgreptreats the target string as a literal (avoids issues if the string contains hyphens or special characters).
3.4 Handle Exit Codes and "Exceptions"#
The final step is to interpret the exit code of the timeout command to determine success, timeout, or failure.
Add this code to the end of the function:
# Interpret exit code
case $exit_code in
0)
echo "Success: Target string '$target_string' found in '$log_file'."
return 0 # Success
;;
124)
echo "Timeout: Target string '$target_string' not found in 20 minutes." >&2
return 124 # Timeout (custom non-zero code)
;;
*)
echo "Error: Failed to search log (exit code $exit_code)." >&2
return $exit_code # Other errors (e.g., grep failed to run)
;;
esac
}Exit Code Logic:
0:grepfound the target string, so the pipeline exits successfully.124:timeoutterminated the command (string not found within 20 minutes).- Other codes (e.g., 1):
grepfound no matches (unlikely here, sincetimeoutwould trigger first), ortailfailed (e.g., log file deleted mid-execution).
Full Function Code#
Combining all steps, here’s the complete function:
tail_log_for_string_with_timeout() {
local log_file="$1"
local target_string="$2"
# Validate input count
if [ $# -ne 2 ]; then
echo "Error: Invalid number of arguments." >&2
echo "Usage: $FUNCNAME <log_file_path> <target_string>" >&2
return 1
fi
# Validate log file exists and is readable
if [ ! -f "$log_file" ]; then
echo "Error: Log file '$log_file' not found or is not a file." >&2
return 1
fi
if [ ! -r "$log_file" ]; then
echo "Error: No read permissions for log file '$log_file'." >&2
return 1
fi
# Validate target string is non-empty
if [ -z "$target_string" ]; then
echo "Error: Target string cannot be empty." >&2
return 1
fi
# Run with timeout and capture exit code
timeout 1200 tail -F -n 0 "$log_file" | grep -m 1 -- "$target_string"
local exit_code=$?
# Handle results
case $exit_code in
0)
echo "Success: Target string '$target_string' found in '$log_file'."
return 0
;;
124)
echo "Timeout: Target string '$target_string' not found in 20 minutes." >&2
return 124
;;
*)
echo "Error: Search failed (exit code $exit_code)." >&2
return $exit_code
;;
esac
}Testing the Function#
Let’s test the function with three scenarios to ensure it works as expected.
Test Case 1: String Found Before Timeout#
Steps:
- Create a dummy log file:
touch test.log. - Open a second terminal and run the function:
tail_log_for_string_with_timeout test.log "ERROR: Failed" - In the first terminal, add the target string to the log:
echo "2024-05-20 10:00:00 ERROR: Failed to connect" >> test.log
Expected Outcome:
The function should immediately print:
Success: Target string 'ERROR: Failed' found in 'test.log'.
And exit with code 0 (check with echo $?).
Test Case 2: Timeout Triggered (String Not Found)#
Steps:
- Run the function with a non-existent string:
tail_log_for_string_with_timeout test.log "RARE_ERROR: Missing" - Wait 20 minutes (or simulate a timeout by pressing
Ctrl+Cand checking the exit code).
Expected Outcome:
After 20 minutes, the function prints:
Timeout: Target string 'RARE_ERROR: Missing' not found in 20 minutes. >&2
And exits with code 124.
Test Case 3: Invalid Input (e.g., Missing Log File)#
Steps:
Run the function with a log file that doesn’t exist:
tail_log_for_string_with_timeout missing.log "test"Expected Outcome:
The function errors immediately:
Error: Log file 'missing.log' not found or is not a file. >&2
And exits with code 1.
Customization Tips#
- Adjust Timeout: Change
1200intimeout 1200to modify the duration (e.g.,300for 5 minutes). - Case-Insensitive Search: Add
-itogrep(e.g.,grep -i -m 1). - Include Existing Lines: Remove
-n 0fromtailto search existing log lines (not just new ones). - Custom Timeout Signal: Use
timeout -s SIGKILL 1200 ...to force-killtailif it ignores SIGTERM.
Conclusion#
You now have a robust shell function to monitor log files for specific strings with a 20-minute timeout. This tool automates tedious manual monitoring, integrates with larger scripts via exit codes, and handles edge cases like missing files or log rotation.
By leveraging tail, grep, and timeout, we’ve created a solution that’s both simple and powerful. Customize it to fit your workflow, and never leave a terminal hanging on an unresponsive log again!
References#
tailman page: Linux man-pagesgrepman page: GNU Grep Documentationtimeoutman page: GNU Coreutils- Bash Exit Codes: Bash Hackers Wiki