aboutsummaryrefslogtreecommitdiff
path: root/mgrep
blob: 13275b32faf5d1f1c387ec14a6b8691a550620fd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/usr/bin/env bash

#
# GNU grep(1) wrapper to search for fixed patterns in a predefined directory
# recursively and quickly, intended for interactive use. I wrote this to search
# my IRC logs in a quick and lazy way, but you could use it for any sort of
# quick-recursive-grep interactive task, hopefully.
#
# "mgrep" is short for "my grep".
#
# Author: Tom Ryder <tom@sanctum.geek.nz>
# Copyright: 2015
# License: MIT
#
self=mgrep

# You will probably want to create an ~/.mgreprc to specify at least the path
# you want to search, unless it really is $HOME. Here's mine:
#
# context=3
# exclude=$HOME/.mgrep/exclude
# path=$HOME/Documents/Logs
#
# If you want, you can specify different paths by exporting MGREP_CONFIG.
#
conf=${MGREP_CONFIG:-$HOME/.${self}rc}
if [[ -r $conf ]] ; then
    source -- "$conf"
fi

# Default to giving three lines of context in grep(1) output
context=${context:-3}

# Path to filename containing a newline-separated list of exclusion patterns
# for filenames (not full paths) to be matched by find(1); this file will be
# silently ignored if nonexistent; bash(1) should raise an error if it exists
# but isn't readable; defaults to "$HOME"/.local/etc/mgrep/exclude.
exclude=${exclude:-$HOME/.$self/exclude}

# The path through which find(1) will run to find files for grep(1) to search.
# Defaults to your $HOME.
path=${path:-$HOME}

# Start lists of arguments for find(1) and grep(1)
declare -a find_args grep_args

# Output describing correct usage
usage() {
    printf 'Usage: %s [-h|-tN] PATTERN [PATTERN...]\n' \
        "$self" "$self"
}

# Look for the -t option, which optionally specifies an -mtime argument to
# find(1); that is, limit the search to only files modified in the last n days
while getopts 'ht:' opt ; do
    case $opt in
        h)
            usage
            exit 0
            ;;
        t)
            find_args=("${find_args[@]}" '-mtime' -"$OPTARG")
            ;;
        \?)
            printf '%s: Invalid option: -%s\n' "$self" "$OPTARG" >&2
            usage
            exit 1
            ;;
    esac
done
shift "$((OPTIND-1))"

# Bail usefully if there are no arguments left; we need at least one
if ! (($#)) ; then
    printf '%s: Need at least one argument\n' "$self" >&2
    usage >&2
    exit 1
fi

# Add --regexp patterns to the grep arguments for each command-line argument;
# this is an OR search, not an AND one, so most of the time you'll probably
# be specifying just one argument
for arg in "${@:?}" ; do
    grep_args=("${grep_args[@]}" '--regexp' "$arg")
done

# Attempt to read the exclusions file; if it exists, try to read exclusion
# patterns from it. If it doesn't exist, just continue on.
if [[ -e $exclude ]] ; then
    while read -r exclusion ; do
        if [[ $exclusion ]] ; then
            find_args=("${find_args[@]}" '!' '-name' "$exclusion")
        fi
    done < "$exclude"
fi

# Try to change into the path for find(1). This is just to prevent long
# prefixes in the output of `grep -h`.
cd -- "$path" || exit

# Run the find(1) command with its determined arguments, with an -exec option
# calling grep(1) with its determined arguments.
find ./* \
    -type f \
    "${find_args[@]}" \
    -exec \
        grep \
        --color=auto \
        --context="$context" \
        --dereference-recursive \
        --ignore-case \
        --with-filename \
        "${grep_args[@]}" \
        {} +