Skip to content

Instantly share code, notes, and snippets.

@SWORDIntel
Last active April 13, 2025 15:40
Show Gist options
  • Save SWORDIntel/6372ba36768a7aa1c454854b0dcd10b2 to your computer and use it in GitHub Desktop.
Save SWORDIntel/6372ba36768a7aa1c454854b0dcd10b2 to your computer and use it in GitHub Desktop.
extract
#!/bin/bash
# ----------------------------------------------------------------------------
# Script: file_extract.sh
#
# Description:
# This script allows you to interactively select a file from the current
# directory using fzf (a terminal-based fuzzy finder), then prompts you to
# input a search string (which can be a regex or plain text). The script
# uses grep (case-insensitive) to extract matching lines from the selected
# file, saving the output to:
# INPUTFILE_SEARCHPARAM.txt
#
# Comprehensive logging is done to both the terminal and the log file.
#
# Requirements:
# - fzf (for interactive file selection)
#
# Author: John
# Date: $(date '+%Y-%m-%d %H:%M:%S')
# ----------------------------------------------------------------------------
# Exit immediately if any command fails, or if any variable is undefined.
set -euo pipefail
# Define log file with timestamp to avoid overwriting previous logs
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
LOGFILE="file_extract_${TIMESTAMP}.log"
# Number of lines to display in preview
PREVIEW_LINES=10
# Make output directory if it doesn't exist
OUTDIR="extracted_results"
mkdir -p "$OUTDIR"
# ----------------------------------------------------------------------------
# log: Logs messages with timestamps to both the console and LOGFILE.
# ----------------------------------------------------------------------------
log() {
local level="$1"
shift
local message="$*"
echo "$(date '+%Y-%m-%d %H:%M:%S') - [${level}] $message" | tee -a "$LOGFILE"
}
# ----------------------------------------------------------------------------
# spinner: Displays a simple spinner to indicate background progress.
# Parameters:
# $1 - The PID of the background process.
# ----------------------------------------------------------------------------
spinner() {
local pid=$1
local delay=0.1
local spinstr='|/-\'
# Check if process is still running before starting spinner
if ! ps -p "$pid" > /dev/null; then
return 0
fi
while ps -p "$pid" > /dev/null; do
for (( i=0; i<${#spinstr}; i++ )); do
printf "\r [%c] Processing..." "${spinstr:$i:1}"
sleep "$delay"
# Break inner loop if process ends
if ! ps -p "$pid" > /dev/null; then
break
fi
done
# Break outer loop if process ends
if ! ps -p "$pid" > /dev/null; then
break
fi
done
printf "\r Done! \n"
}
# ----------------------------------------------------------------------------
# sanitize_filename: Sanitizes a string for safe use in filenames
# Parameters:
# $1 - The string to sanitize
# ----------------------------------------------------------------------------
sanitize_filename() {
local input="$1"
# Replace problematic characters with underscores
echo "$input" | sed 's/[\/\\\:\*\?\"\<\>\|]/_/g'
}
# ----------------------------------------------------------------------------
# Check for required commands
# ----------------------------------------------------------------------------
check_requirements() {
log "INFO" "Checking requirements..."
if ! command -v fzf >/dev/null 2>&1; then
log "ERROR" "fzf is not installed. Please install fzf and re-run the script."
echo "You can install fzf with: sudo apt install fzf # For Debian/Ubuntu"
echo " or: brew install fzf # For macOS"
exit 1
fi
log "INFO" "All requirements satisfied."
}
# ----------------------------------------------------------------------------
# Main script execution
# ----------------------------------------------------------------------------
main() {
log "INFO" "Script started"
# Check requirements
check_requirements
# ----------------------------------------------------------------------------
# Step 1: Select a file using fzf from the current directory
# ----------------------------------------------------------------------------
log "INFO" "Listing files in the current directory for selection."
# Use fzf with preview functionality
selected_file=$(find . -maxdepth 1 -type f -not -path "*/\.*" | sed 's|^\./||' | \
fzf --prompt="Select a file: " \
--preview="head -n $PREVIEW_LINES {}" \
--preview-window=down:3:wrap)
if [ -z "$selected_file" ]; then
log "WARN" "No file was selected. Exiting."
exit 1
fi
log "INFO" "File selected: $selected_file"
# Get file size for informational purposes
file_size=$(du -h "$selected_file" | cut -f1)
log "INFO" "Selected file size: $file_size"
# ----------------------------------------------------------------------------
# Step 2: Get search parameters from the user
# ----------------------------------------------------------------------------
echo -n "Enter search parameters (regex or text): "
read -r search_param
if [ -z "$search_param" ]; then
log "WARN" "No search parameters provided. Exiting."
exit 1
fi
log "INFO" "Search parameters provided: $search_param"
# Ask user if they want case-sensitive search
echo -n "Case sensitive search? (y/N): "
read -r case_sensitive
grep_options="-i" # Default to case-insensitive
if [[ "${case_sensitive,,}" == "y" ]]; then
grep_options="" # Remove -i for case-sensitive search
log "INFO" "Using case-sensitive search"
else
log "INFO" "Using case-insensitive search"
fi
# ----------------------------------------------------------------------------
# Step 3: Construct the output file name
# ----------------------------------------------------------------------------
# Sanitize search parameter for use in filename
sanitized_param=$(sanitize_filename "$search_param")
# Truncate if too long (max 30 chars)
if [ ${#sanitized_param} -gt 30 ]; then
sanitized_param="${sanitized_param:0:30}"
fi
# Create a safe output filename
output_file="$OUTDIR/$(basename "$selected_file")_${sanitized_param}.txt"
log "INFO" "Output file set as: $output_file"
# ----------------------------------------------------------------------------
# Step 4: Perform extraction using grep
# ----------------------------------------------------------------------------
log "INFO" "Starting extraction using command: grep $grep_options \"$search_param\" \"$selected_file\" > \"$output_file\""
# Start grep in background and capture any errors
grep $grep_options "$search_param" "$selected_file" > "$output_file" 2>"${output_file}.err" &
grep_pid=$!
# Show spinner while grep is running
spinner "$grep_pid"
# Wait for grep to finish and check exit status
if wait "$grep_pid"; then
# Success - grep found matches or completed successfully
grep_status=0
else
# grep returned non-zero (could be no matches found, which is exit code 1)
grep_status=$?
fi
# ----------------------------------------------------------------------------
# Step 5: Check and report extraction results
# ----------------------------------------------------------------------------
# Check if there were errors
if [ -s "${output_file}.err" ]; then
log "ERROR" "Grep encountered errors: $(cat "${output_file}.err")"
rm -f "${output_file}.err"
exit 1
else
rm -f "${output_file}.err"
fi
# Count matches
if [ -s "$output_file" ]; then
match_count=$(wc -l < "$output_file")
log "INFO" "Extraction successful. Found $match_count matching lines."
echo "==============================================="
echo " Extraction Results"
echo "==============================================="
echo " Source file: $selected_file ($file_size)"
echo " Search term: $search_param"
echo " Matches found: $match_count"
echo " Results saved to: $output_file"
echo "==============================================="
# Ask if user wants to preview results
echo -n "Preview results? (y/N): "
read -r preview
if [[ "${preview,,}" == "y" ]]; then
if command -v less >/dev/null 2>&1; then
less "$output_file"
else
head -n 20 "$output_file"
echo "..."
echo "[Showing first 20 lines only]"
fi
fi
else
if [ $grep_status -eq 1 ]; then
log "INFO" "No matching lines were found."
echo "No matching lines were found for '$search_param' in '$selected_file'."
else
log "ERROR" "Grep failed with status: $grep_status"
fi
fi
log "INFO" "Script finished successfully."
echo "All done! Check $output_file for your results."
}
# Run the main function
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment