72

When accidentally pasting a file into the shell it puts a ton of ugly nonsense entries in the bash history. Is there a clean way to remove those entries? Obviously I could close the shell and edit the .bash_history file manually but maybe there's some kind of API available to modify the history of the current shell?

14 Answers 14

63

Just this one liner in command prompt will help.

for i in {1..N}; do history -d START_NUM; done

Where START_NUM is starting position of entry in history. N is the number of entries you may want to delete.

ex: for i in {1..50}; do history -d 1030; done

8
  • 4
    I wonder why this is not a built-in function already. 'history' is a very old tool. Commented Aug 1, 2016 at 9:23
  • 1
    serves the purpose very well, but this command can be seen in history :) Commented Nov 26, 2018 at 4:12
  • 1
    askubuntu.com/a/978276/22866 has a nice way to delete the "delete from history command" from history :-)
    – HanSooloo
    Commented Jul 4, 2019 at 6:46
  • 2
    @RajeevAkotkar If the delete command is the nth line, then using N+1 in the for loop will also help delete the command that did the deleting.
    – lightsong
    Commented Nov 29, 2019 at 2:55
  • 1
    Alternatively, you can also use for i in {1030..1080}; do history -d 1030; done instead - which might be a bit more intuitive. I wrote a small bash script, just replace 1030 with $1 and 1080 with $2.
    – Cadoiz
    Commented Jan 6, 2022 at 14:39
49

As of bash-5.0-alpha, the history command now takes a range for the delete (-d) option. See rastafile's answer.

For older versions, workaround below.


You can use history -d offset builtin to delete a specific line from the current shell's history, or history -c to clear the whole history.

It's not really practical if you want to remove a range of lines, since it only takes one offset as an argument, but you could wrap it in a function with a loop.

rmhist() {
    start=$1
    end=$2
    count=$(( end - start ))
    while [ $count -ge 0 ] ; do
        history -d $start
        ((count--))
    done
}

Call it with rmhist first_line_to_delete last_line_to_delete. (Line numbers according to the output of history.)

(Use history -w to force a write to the history file.)

2
  • 1
    Since the OP asked for deleting the N last lines, this script should be modified by doing something like: tot_lines=$(history | wc -l) and then repeat history -d $(( tot_lines - $1 )). Commented Jan 19, 2018 at 9:13
  • 2
    Instead of $(history | wc -l), there is the variable $HISTCMD that can be used. Commented Jan 19, 2018 at 9:58
24

According to the man-page for Bash 5.x, the history built-in command now also takes a range:

-d offset

    Delete the history entry at position offset.  If offset is negative, it is interpreted as relative to one greater than the last history position, so negative indices count back from the end of the history, and an index of -1 refers to the current history -d command.

-d start-end

    Delete the history entries between positions start and end, inclusive.  Positive and negative values for start and end are interpreted as described above.

But history --help does not currently (as of v5.2.21) reflect this:

 Options:
   -c        clear the history list by deleting all of the entries
   -d offset delete the history entry at position OFFSET. Negative
             offsets count back from the end of the history list
 
   -a        append history lines from this session to the history file
   ...

For example, history -d 2031-2034 deletes four lines at once.  You could use $HISTCMD to delete from the newest N lines backwards.


You can also export with history -w tmpfile, then edit that file, clear with history -c and then read back with history -r tmpfile.  No need to write to .bash_history (directly).

3
  • 3
    I get "history position out of range" when I try this. Commented Aug 24, 2020 at 1:17
  • Me too. BASH_VERSION 4.4.20(1)-release Commented Oct 8, 2020 at 16:12
  • The range parameter is available in Bash 5. It's not available in version 4 Commented Jun 7, 2022 at 3:53
7

If you delete line N from the history, then line N+1 moves in position N etc.

For this reason, I prefer identifying the oldest and newest history line between which I want to delete all history. (Note: oldest < newest).

If for instance I want to delete the history lines from oldest = 123 up to newest = 135, I'd write:

$ for i in {135..123}; do history -d $i ; done

I find it easier to read; besides: the for command can also decrement a range...

1
  • 2
    Instead you could also just do for i in {123..135}; do history -d 123; done
    – Cadoiz
    Commented Dec 16, 2021 at 7:37
4

the history -d arg takes a range and $HISTCMD is the max number in the history.

This function works to remove the last n entries from history (just pass in the number of history commands to remove like, eg rmhist 5 :

$ rmhist()  { history -d $(($HISTCMD - $1))-$HISTCMD ;}

Or.. Go fancy with an arg like this to remove from a point in history (inclusive) or last 'n' commands:

rmhist() { 
  case $1 in 
    --from|from) local start=$2; ;; 
    --last|last) local start=$(($HISTCMD - $2)) ;; 
    *) echo "Try rmhist --from # or rmhist --last n "; return ;; 
  esac; 
  history -d ${start}-${HISTCMD}
}

The result looks something like this:

 5778  ls /etc
 5779  ls /tmp
 5780  ls /dev
 5781  ll
 5782  cd /tmp
 5783  cd
 5784  history
(base) ~ $ rmhist --last 3
(base) ~ $ history 5
 5778  ls /etc
 5779  ls /tmp
 5780  ls /dev
 5781  ll
 5782  history 5
(base) ~ $ rmhist --from 5780
(base) ~ $ history 5
 5776  history 10
 5777  ls
 5778  ls /etc
 5779  ls /tmp
 5780  history 5
1
  • I get "bash: history: N: history position out of range" errors, but this worked for me to delete the last entry hdl() { history -d $(($HISTCMD - $1))-$(($HISTCMD - 2)) ;} I'll write any error understandings in my answer here: unix.stackexchange.com/a/573258/346155
    – alchemy
    Commented Mar 18, 2022 at 20:59
1

What's different in this answer: it also displays what is being deleted.

For those using Bash 4.x (e.g. in Centos 7 and lower), I use this to delete the last N items in the history including the command to do this. (here N=5)

for i in {1..5}; do
  echo "clearing line $(($HISTCMD-2)): $(history -p \!$(($HISTCMD-2)))";
  history -d $(($HISTCMD-2));
done;
history -d $(($HISTCMD-1))

To delete the history lines between n and m (e.g. here 544 and 541), I use the following. NOTE: you must enter the bigger line number first and then the smaller one:

for i in {544..541}; do
  echo "clearing line $i: $(history -p \!$i)";
  history -d $i;
done;
history -d $(($HISTCMD-1))
1

The answer by user2982704 almost worked for me but not quite. I had to make a small variation like this.

Assuming my history is is at 1000 and I want to delete the last 50 entries

start=1000

for i in {1..50}; do count=$((start-i)); history -d $count; done
1

It works for me. To delete line 2 to 29 (includes line 2 and line 29):

history -d 2-29
history -w 
1

This should delete every history list entry in reverse order N times:

function hd { for i in $(seq 1 $1); do history -d $(($HISTCMD-1)); done; }

If you want to add this to your .bashrc, use:

echo "function hd { for i in $(seq 1 $1); do history -d \$((\$HISTCMD-1)); done; }">>~/.bashrc

..and then source ~/.bashrc to reload bash config. Use as:

hd <N>, where N is the number of lines to delete

My other answer for a different approach that is not working, but is long and complicated.

Also see How to delete history of last 10 commands in shell?


EDIT: this started giving me the error "bash: history: -N: history position out of range" as does history -d -2 for some reason. So I used another users answer, that even though it gave me the same error, I could adjust to make work. This example deletes only the last line, but can be extended.

hdl() { history -d $(($HISTCMD - $1))-$(($HISTCMD - 2)) ;}

0
0

Before I give up on this alternative approach, I want to put down what I've learned. Part of my difficulty in debugging my first answer is that its a bit unclear how many N ends up being, because it includes the current command, so I think it is really N-1 or maybe N-2.

To make it more clear, I was trying a solution that would delete from a line number going forward. And wow, what a big surprise of how difficult that is. The main problem is the history routine is running in the background so deleting say number 50 will result in number 51 now being 50 in less than a second. So I tried making a function to delete going backwards from the end. And when trying to deleting 10 in a row and it gets about 5 before running out of commands as they shift down. So I tried making a function to delete forwards and it, for some reason only gets every other one. Consistent with the history routine running about half as fast as the function. I also tried inserting sleep at certain points.

I tried turning off history temporarily, but then $HISTCMD show the total from $HISTFILE not the history list. So it looks nary a possible. My goal was to clean the history of unnecessary commands so that I could memorize the bang number !# and use the same ones going forward for frequent and difficult commands.

Here's some of the functions I tried. Maybe someone can improve or diagnose:

Going from the Number to the end:
function hd2 { a=$HISTCMD; echo $a; echo $1; echo $(($a%$1)); for i in $(seq $1 $a); do echo $i; history -d $i; done; }

Deleting from the end repeatedly with sleep:
function hd3 { a=$HISTCMD; echo $a; echo $1; echo $(($a%$1)); for i in $(seq 1 $a); do history -d $HISTCMD; sleep 1; done; }

Deleting the Number repeatedly with sleep:
function hd4 { a=$HISTCMD; echo $a; echo $1; echo $(($a%$1)); for i in $(seq 1 $a); do history -d $1; sleep 1; done; }

The beginning echo variable are for debugging. % is remainder or mod. $HISTCMD does not carry through command substitution and need to be assigned as a variable.

new idea: since the function below works with repeated use:

echo "function hdn { history -d \$1; }">>~/.bashrc

echo "function hdn2 { for i in $(seq 1 \$2); history -d \$1; sleep 1; done; }">>~/.bashrc
0

This thread is old by has a reply from last year, but I came here looking for the same answer. Basically the simplest solution I found in the end was to simply delete one line at a time, which sounds counter intuitive but bare with me, a duplicate history -d 123 does not show up, if you type it 10 times (up arrow each time or paste, paste, paste), line 123 is deleted each time, but when you type "history" again, the line history -d 123 only shows once.

Eg:

$ history
122 ls
123 history
124 ls
125 sudo
126 su
127 host
128 history
129 history -d 123
130 ls
131 history

$ history -d 123
$ history -d 123
$ history -d 123
$ history -d 123
$ history -d 123
$ history -d 123
$ history -d 123
$ history -d 123
$ history -d 123

The output becomes:

$ history
122 ls
123 history -d 123
124 history

As you can see the history -d 123 is not duplicated for each additional time it is typed, it only shows once, so that is the easiest way to get rid of multiple lines without having to use scripts or other tools.

0

If the copy paste garbage is just a single password you can simply:

history -d -2

to delete just that one last line in your history.

It's -2 rather than -1 because -1 is the current command you are typing (history -d -1). So with -1 you'd end up with an unchanged buffer and .bash_history file.

The popular community answer above led me to this solution. And I was desperate for a command I could easily remember, without having to duckup this answer every time I uckup my history (on servers where I haven't aliased one of the other answers here).

0

Let's assume you want to delete the last 5 entries of your history as an example:

for i in {1..5}; do history -d $(($HISTCMD-1)); done

If you want to include the same deletion command to removal it is simpler:

for i in {0..5}; do history -d $HISTCMD; done

Note that ranges in for loops are only there to repeat N times the desired action.

2
  • This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review Commented Aug 29, 2022 at 9:29
  • (1) The question talks about N.  I guess you are using “5” as an example value for N.  If so, you should say so.  (2) for i in {0..5}; do something ; done does something six times.  But, if the something doesn’t reference $i, then for i in {1..6}; do would produce the same result.  So how is it possible that for i in {0..5} would delete itself from history but for i in {1..5} wouldn’t? … … … … … … … … … … … … … … … … … … … … … … … … Please do not respond in comments; edit your answer to make it clearer and more complete. Commented Sep 3, 2022 at 6:18
-2

Tried with below command and it worked fine

 end=`history| awk 'END{print $1}'`
    start=`history| awk 'END{print $1-10}'`

    awk -v end="$end" -v start="$start"  '{for(i=start;i<=end;i++){print "history -d" " " i};exit}’|sh
1
  • 1
    Will this remove entries from bash's history if you pipe it through sh??
    – Jeff Schaller
    Commented Mar 17, 2020 at 11:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .