bashfunctionhistory

Why does history require a numeric value for grep?


I am trying to make a custom function (hisgrep) to grep from history.

I had it working before, when the code was basically "history | grep $1", but I want the implementation to be able to grep multiple keywords. (e.g. "hisgrep docker client" would equal "history | grep docker | grep client").

My problem is that, when I try to do this I get this error: "-bash: history: |: numeric argument required."

I've tried changing how the command was called in the end from $cmd to just $cmd, but that did nothing.

Here's the code:

#!/bin/bash

function hisgrep() {
    cmd='history'
    for arg in "$@"; do
        cmd="$cmd | grep $arg"
    done
    `$cmd`
}

Solution

  • Sadly, bash doesn't have something called "foldl" or similar function.

    You can do it like this:

    histgrep() {
        local str;
        # save the history into some list
        # I already filter the first argument, so the initial list is shorter
        str=$(history | grep -e "$1");
        shift;
        # for each argument
        for i; do
           # pass the string via grep
           str=$(<<<"$str" grep "$i")
        done
        printf "%s\n" "$str"
    }
    

    Notes:

    It is possible to hack with eval (and eval is evil) with something that generates bash code with a long pipeline for each argument:

    histgrep() { eval "history $(printf '| grep -e "$%s" ' $(seq $#))"; }
    

    The eval here will see history | grep -e "$1" | grep -e "$2" | ... which I think looks actually quite safe.

    The recursive solution in the comment by @melpomene also looks amazing. This would be my code on the idea:

    _multigrep_in() {
      case $# in
      0) cat ;;
      1) grep "$1" ;;
      *) grep "$1" | _multigrep_in "${@:2}" ;;
      esac
    }
    multigrep() {
       history | _multigrep_in "$@"
    }