funwithlinux blog

How to Create a Shell Function to Tail Log Files for a Specific String with 20-Minute Timeout: Exit or Throw Exception

In the world of system administration, DevOps, and software development, monitoring log files for specific events (e.g., errors, success messages, or user actions) is a common task. However, manually tailing logs and waiting indefinitely for a string to appear is inefficient and error-prone. What if the event never occurs? Your script or terminal session could hang indefinitely.

To solve this, we’ll create a reusable shell function that:

  • Follows (tails) a log file in real time.
  • Searches for a specific target string.
  • Automatically exits after 20 minutes (timeout) if the string is not found.
  • Returns a clear success/failure status (or "throws an exception" via exit codes) to integrate with larger scripts.

This guide will walk you through the entire process, from understanding the requirements to testing the function. By the end, you’ll have a robust tool to streamline log monitoring workflows.

2026-01

Table of Contents#

  1. Prerequisites
  2. Understanding the Requirements
  3. Step-by-Step Implementation
  4. Testing the Function
  5. Customization Tips
  6. Conclusion
  7. 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, and timeout (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:

RequirementDetails
Input ParametersAccept two arguments: (1) path to the log file, (2) target string to search.
Log TailingFollow the log file in real time (i.e., show new lines as they are added).
String MatchingStop searching and exit successfully when the target string is found.
TimeoutTerminate after 20 minutes (1200 seconds) if the string is not found.
Error HandlingValidate 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_timeout for clarity.
  • It accepts two parameters: log_file (path to the log) and target_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, -F handles log rotation (e.g., if the log is rotated by logrotate, tail -F will reopen the new file).
  • -n 0: Start reading from the end of the file (ignore existing lines; use -n +1 to include all lines if needed).
  • grep -m 1: Stop searching after the first match (-m 1 = "max 1 match"). This ensures grep exits immediately when the target string is found, which in turn closes the pipe and stops tail.

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 pipeline

Explanation:

  • timeout 1200: Terminate the tail command after 1200 seconds (20 minutes).
  • -- in grep -- "$target_string": Ensures grep treats 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: grep found the target string, so the pipeline exits successfully.
  • 124: timeout terminated the command (string not found within 20 minutes).
  • Other codes (e.g., 1): grep found no matches (unlikely here, since timeout would trigger first), or tail failed (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:

  1. Create a dummy log file: touch test.log.
  2. Open a second terminal and run the function:
    tail_log_for_string_with_timeout test.log "ERROR: Failed"
  3. 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:

  1. Run the function with a non-existent string:
    tail_log_for_string_with_timeout test.log "RARE_ERROR: Missing"
  2. Wait 20 minutes (or simulate a timeout by pressing Ctrl+C and 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 1200 in timeout 1200 to modify the duration (e.g., 300 for 5 minutes).
  • Case-Insensitive Search: Add -i to grep (e.g., grep -i -m 1).
  • Include Existing Lines: Remove -n 0 from tail to search existing log lines (not just new ones).
  • Custom Timeout Signal: Use timeout -s SIGKILL 1200 ... to force-kill tail if 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#