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`
}
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:
cmd="$cmd | grep $arg"
and then doing `$cmd` looks unsafe.$()
command substitution.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 "$@"
}