doug
doug

Reputation: 163

How do I delete lines from my bash history matching a specific pattern?

I can get a list of the line numbers matching a specific pattern such as containing the word "function".

history | grep function | sed -e 's/^\(.\{5\}\).*/\1/' | sed 's/^ *//g'

If I do history -d on that it says bad pattern, I don't know if it's as it's a list or their strings rather than numbers?

history -d (history | grep function | sed -e 's/^\(.\{5\}\).*/\1/' | sed 's/^ *//g')

Upvotes: 6

Views: 1931

Answers (3)

kvantour
kvantour

Reputation: 26471

Quick answer:

while read n; do history -d $n; done < <(history | tac | awk '/function/{print $1}')

Explanation: The history command accepts only a single offset when using the -d flag. On top of that when you delete an entry, it also renumbers all the commands after this entry. For this reason we revert the output of history using tac and process the lines from last to first. This short awk line just replaces the grep and sed command to pick up the history offset.

We do not use a full pipeline as this creates subshells and history -d $n would not work properly. This is nicely explained in: Why can't I delete multiple entries from bash history with this loop

Note: If you want to push this to your history file ($HISTFILE), you have to use history -w

Warning: When you have multiline commands in your history the story becomes very complicated and strongly depends on various options that have been set. See [U&L] When is a multiline history entry (aka lithist) in bash possible? for the nasty bits.

Upvotes: 7

Ted Lyngmo
Ted Lyngmo

Reputation: 117228

You can delete one history entry or a range of entries, but not a list. Your matches are likely to be spread out, so the range option is out.

The multiple sed commands to extract the history offsets can be simplified into one:

sed -E 's/^ *([0-9]*).*$/\1/'

One problem with history is that it can have multiline entries, like:

741  source <(history | \
       grep function | \
       sed -E 's/^ *([0-9]*).*$/\1/' | \
       sort -rn | \
       xargs -n1 echo history -d)

If your grep matches on function above, your sed will not be able to extract the history offset number, so we need to make that possible. One way may be to remove all newlines and only add them on lines containing the history offset. This is one way that probably can be done in some easier way:

awk '/^ {0,4}[0-9]+/ {
  printf("\n%s",$0);
}
!/^ {0,4}[0-9]+/{
  printf(" %s",$0);
}
END{
  printf("\n")
}'

We can then produce a number of history -d commands with xargs. xargs can't run the build-it history directly, so I've just used it to produce input to the built-in source using Process Substitution:

source <(history | \
         awk '/^ {0,4}[0-9]+/ {
           printf("\n%s",$0);
         }
         !/^ {0,4}[0-9]+/{
           printf(" %s",$0);
         }
         END{
           printf("\n")
         }' | \
         grep function | \
         sed -E 's/^ *([0-9]*).*$/\1/' | \
         sort -rn | \
         xargs -n1 echo history -d)

@kvantour gives nice alternatives to grep + sed + sort -rn. Using those, my above blob could be simplified into:

source <(history | \
         awk '/^ {0,4}[0-9]+/ {
           printf("\n%s",$0);
         }
         !/^ {0,4}[0-9]+/{
           printf(" %s",$0);
         }
         END{
           printf("\n")
         }' | \
         awk '/function/ {print "history -d",$1}' | \
         tac)

Upvotes: 3

Roberto Hernandez
Roberto Hernandez

Reputation: 8518

You need to store the pattern in a variable and then pass it to history.

$ history | grep function | sed -e 's/^\(.\{5\}\).*/\1/' | sed 's/^ *//g'
1077
$ var=$( history | grep function | sed -e 's/^\(.\{5\}\).*/\1/' | sed 's/^ *//g')
$ history -d $var

However, as you can have a lot of ocurrences for the patter, I would use a loop

$ var=$( history | grep function | sed -e 's/^\(.\{5\}\).*/\1/' | sed 's/^ *//g')
$ for i in $var
> do
> history -d $i
> history -w 
> done

If the line you want to delete has already been written to your $HISTFILE (which typically happens when you end a session by default), you will need to write back to $HISTFILE, or the line will reappear when you open a new session.

After the deletion you need to load again the .bashrc by executing

 $ cd
 $ source .bashrc

However, there are cases that the lines won't be deleted: if you set PROMPT_COMMAND to history -a, in that case it is already written to the history file, rather than on exit under normal configuration.

Upvotes: 0

Related Questions