Pedro Souza
Pedro Souza

Reputation: 15

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`
}

Upvotes: 1

Views: 1613

Answers (2)

KamilCuk
KamilCuk

Reputation: 141688

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:

  • Doing cmd="$cmd | grep $arg" and then doing `$cmd` looks unsafe.
  • Remember to quote your variables.
  • Use https://www.shellcheck.net/ to check your scripts.
  • Backticks ` are deprecated. Use $() command substitution.
  • using both function and parenthesis function func() is not portable. Just do func().

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 "$@"
}

Upvotes: 3

Mateusz Piotrowski
Mateusz Piotrowski

Reputation: 9147

It does not work because | is interpreted as an argument to the history command.

Upvotes: 2

Related Questions