6

I'm interested in a general solution, but my specific example problem is writing a .bashrc function that wraps grep and appends a file path to the command if missing. Basically, any time grep would wait on stdin I instead want it to search a specific file. My problem is how to tell (in the wrapper) whether the final argument is a path to be searched versus e.g. the search pattern.

$ ls
example_file.txt
$ grep -someopts 'somestring' 'example_file.txt'

Pretend that -someopts are in fact arbitrary valid options to grep.
Is the final argument a pattern (to be searched for) or a file (to be searched)?
If somestring is a parameter to one of the -options, then example_file.txt is the pattern to search for, and grep will wait on stdin. Otherwise, somestring is the pattern to search for and example_file.txt will be searched.

In the former case, I want to append my own file to be searched on the end of the command, but I can't detect said case without false-positives. The only way seems to be for the wrapper to consider every argument that grep could take.

Here is my wrapper function (where check_has_path is what I need to implement):

function grep_wrapped() {
    if check_has_path ; then
        grep "$@"
    else
        grep "$@" '/default/filepath.txt'
    fi
}
  • What about the case where grep is working on stdin? – Jeff Schaller Oct 6 '17 at 15:07
  • @Jeff Schaller I will not be piping stdin to the wrapper. I will either be using it with a specified search path/file (in which case for the purposes of this example it would be equivalent to just calling grep), or not (in which case the default search path/file should be searched) – benxyzzy Oct 6 '17 at 15:13
3

Approach: Separate the command line options from the pattern and possible filenames on the command line, then count the command line arguments that are not options. If there's more than one, run the command as is, otherwise tag on your file.

In bash:

mygrep () {
    local -a opts

    while [ "$#" -gt 0 ]; do
        case "$1" in
            --) opts+=( "$1" ); shift; break ;;
            -*) opts+=( "$1" ) ;;
            *)  break
        esac
        shift
    done

    if [ "$#" -gt 1 ]; then
        grep "${opts[@]}" "$@"
    else
        grep "${opts[@]}" "$@" "/my/file"
    fi
}

The function separates the command line options from the rest of the command line and checks whether there are more than one non-option command line argument or not (i.e. something other than a pattern). If there isn't, your file is tagged onto the end of the command.

This does not work if you use options with option-arguments (e.g. -e PATTERN), so it's somewhat flawed. Maybe it can serve as a starting point for someone else?


From comments it is clear that the user is not interested in running grep at all, but rather in searching for files with a particular extension.

The following shell function does that:

extfind () {
    local ext="$1"

    if [ -z "$ext" ]; then
        echo 'Missing extension' >&2
        return 1
    fi

    shift
    local dir="${1:-$HOME}"

    find "$dir" -type f -name "*.$ext"
}

This function would be used as

$ extfind txt

to find all files whose names ends with .txt in or under the home directory, or

$ extfind "[hc]" /usr/src

to find all files whose names end with either .h or .c in or under the /usr/src directory.

|improve this answer|||||
  • Great, except you have an explicit path in your last grep. The question appears to be that he wants the last argument of the previous command to be the "default". I.e., add local lastfile="$_" to the top of the function, and change "/my/file" to read "$lastfile" – Rich Oct 6 '17 at 17:47
  • 1
    @Rich Sorry, but I can't see anything about using the last argument of the previous command in the question. In the OP's own code, there is an explicit and literal path too. Maybe it's "I want to append my own file to be searched on the end of the command" that you're interpreting like that? I read that as "I want to apply grep to a specific file". – Kusalananda Oct 6 '17 at 18:02
  • Right! Lack of comprehension on my part. I misinterpreted the first two lines as a single line, i.e. $ ls example_file.txt. Plus, I guess it doesn't make sense for a default filename to be hardcoded into such a general-purpose command as grep. Mind, it doesn't make sense to avoid typing Ctrl-D, cursor-up, Alt-. either! – Rich Oct 6 '17 at 19:40
  • My example is simplified; I'm trying to write a custom grep wrapper for searching a specific file extension. Often I'll want to find one such file somewhere in my home directory, in which case I'd like to omit the filepath. When I include a filepath or directory however, I want that to be searched instead of my home. All other behaviour and functionality should be as grep. Specifically, I may use any options. If a general solution exists it would greatly extend my use of wrappers – benxyzzy Oct 7 '17 at 14:25
  • @benxyzzy So, you're actually not interested in grepping the contents of a file, but rather to search for files with a particular extension? This is not what grep does, but it's trivial to write a shell function that uses find to do this. – Kusalananda Oct 7 '17 at 14:27
1

You could go with a sort of pythonesque try it and if there is an exception do something else. Something like:

function grep_wrapped(){
    grep "$@" <&-
    local rc=$?
    if [ $rc = 2 ]      # probably a read error from closed stdin
    then   grep "$@" /default/filepath.txt
    else   return $rc
    fi
}

It leaves all the work to grep. This will have the side-effect of the error message

grep: (standard input): Bad file descriptor

when you have the exit code 2 from grep because stdin was closed. You can obviously redirect stderr to capture this to file or variable, and print it if the return code was 0 or 1.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.