Wie kann ich Duplikate in meiner .bash_history entfernen, wobei die Reihenfolge erhalten bleibt?

60

Es macht mir wirklich Spaß control+r, rekursiv in meinem Befehlsverlauf zu suchen. Ich habe ein paar gute Optionen gefunden, die ich gerne damit verwende:

# ignore duplicate commands, ignore commands starting with a space
export HISTCONTROL=erasedups:ignorespace

# keep the last 5000 entries
export HISTSIZE=5000

# append to the history instead of overwriting (good for multiple connections)
shopt -s histappend

Das einzige Problem für mich ist, dass erasedupsnur sequentielle Duplikate gelöscht werden - so dass mit dieser Befehlsfolge:

ls
cd ~
ls

Der lsBefehl wird tatsächlich zweimal aufgezeichnet. Ich habe darüber nachgedacht, regelmäßig mit cron zu laufen:

cat .bash_history | sort | uniq > temp.txt
mv temp.txt .bash_history

Dies würde das Entfernen der Duplikate erreichen, aber leider würde die Reihenfolge nicht beibehalten. Wenn ich sortdie Datei nicht zuerst bekomme, uniqkann sie meiner Meinung nach nicht richtig funktionieren.

Wie kann ich Duplikate in meiner .bash_history entfernen, wobei die Reihenfolge erhalten bleibt?

Zusätzliches Guthaben:

Gibt es Probleme beim Überschreiben der .bash_historyDatei über ein Skript? Wenn Sie beispielsweise eine Apache-Protokolldatei entfernen kill, müssen Sie meines Erachtens ein Nohup / Reset-Signal mit senden, damit die Verbindung zur Datei wiederhergestellt wird. Wenn dies bei der .bash_historyDatei der Fall ist , kann ich möglicherweise irgendwie psüberprüfen, ob keine Sitzungen verbunden sind, bevor das Filterskript ausgeführt wird.

cwd
quelle
3
Versuchen Sie es ignoredupsstatt erasedupsfür eine Weile und sehen Sie, wie das bei Ihnen funktioniert.
JW013
1
Ich glaube nicht, dass bash ein offenes Dateihandle für die Verlaufsdatei enthält - es liest / schreibt es, wenn es benötigt wird, so dass es sicher sein sollte (Anmerkung - sollte - ich habe es nicht getestet), es von woanders zu überschreiben.
D_Bye
1
Ich habe gerade im ersten Satz Ihrer Frage etwas Neues gelernt. Guter Trick!
Ricardo
Ich kann die Manpage für alle Optionen des historyBefehls nicht finden. Wo soll ich suchen?
Jonathan Hartley
Die Verlaufsoptionen befinden sich in 'man bash'. Suchen Sie im Abschnitt 'Shell Builtin Commands' nach 'history'.
Jonathan Hartley

Antworten:

36

Die Geschichte sortieren

Dieser Befehl funktioniert wie sort|uniqfolgt, behält jedoch die Zeilen bei

nl|sort -k 2|uniq -f 1|sort -n|cut -f 2

Grundsätzlich wird jeder Zeile ihre Nummer vorangestellt. Danach sort|uniqwerden alle Zeilen gemäß ihrer ursprünglichen Reihenfolge (unter Verwendung des Zeilennummernfelds) zurücksortiert, und das Zeilennummernfeld wird aus den Zeilen entfernt.

Diese Lösung hat den Fehler, dass nicht definiert ist, welcher Repräsentant einer Klasse gleicher Linien es in der Ausgabe macht, und daher ist seine Position in der endgültigen Ausgabe nicht definiert. Sollte jedoch der letzte Vertreter gewählt werden, können Sie sortdie Eingabe mit einer zweiten Taste vornehmen:

nl|sort -k2 -k 1,1nr|uniq -f1|sort -n|cut -f2

Verwalten von .bash_history

Zum erneuten Lesen und Zurückschreiben des Verlaufs können Sie history -aund history -wverwenden.

artistoex
quelle
6
Eine Version von decorate-sort-undecorate , die mit Shell-Tools implementiert wurde. Nett.
ire_and_curses
Mit sortdem -rumkehrt Schalter immer die Sortierreihenfolge. Dies wird jedoch nicht das gewünschte Ergebnis bringen. sortbetrachtet die beiden Vorkommen von lsals identisch mit dem Ergebnis, dass die endgültige Reihenfolge auch in umgekehrter Reihenfolge vom Sortieralgorithmus abhängt. Aber siehe mein Update für eine andere Idee.
artistoex
1
Wenn Sie .bash_history nicht ändern möchten, können Sie Folgendes in .bashrc einfügen: alias history = 'history | sort -k2 -k 1,1nr | uniq -f 1 | sort -n '
Nathan
Was steht nlam Anfang jeder Codezeile? Sollte es nicht sein history?
AL
1
@AL nl fügt Zeilennummern hinzu. Der Befehl als Ganzes löst das allgemeine Problem: Entfernen von Duplikaten unter Beibehaltung der Reihenfolge. Die Eingabe wird von stdin gelesen.
artistoex
48

Also suchte ich genau das Gleiche, nachdem ich mich über Duplikate geärgert hatte, und stellte fest, dass ich, wenn ich mein ~ / .bash_profile (Mac) bearbeite, mit:

export HISTCONTROL=ignoreboth:erasedups

Es macht genau das, was Sie wollten, es hält nur den neuesten Befehl. ignorebothist eigentlich genauso wie zu tun ignorespace:ignoredupsund das zusammen mit erasedupsbekommt den Job erledigt.

Zumindest auf meinem Mac-Terminal mit Bash funktioniert dies einwandfrei. Habe es hier auf askubuntu.com gefunden .

Sprite
quelle
10
Dies sollte die richtige Antwort sein
MitchBroadhead
Getestet unter Max OS X Yosemite und unter Ubuntu 14_04
Ricardo
1
stimme mit @MitchBroadhead überein. Dies löst das Problem innerhalb von Bash selbst, ohne externen Cron-Job. testete es auf Ubuntu 17.04 und 16.04 LTS
Georg Jung
funktioniert auch unter OpenBSD. Es werden nur Dups von Befehlen entfernt, die an die Verlaufsdatei angehängt werden, was für mich in Ordnung ist. Es hat den interessanten Effekt, die Verlaufsdatei zu verkürzen, wenn ich Befehle eingebe, die zuvor als Duplikate vorhanden waren. Jetzt kann ich meine History-Datei max kürzer machen.
WeakPointer
1
Dies ignoriert nur doppelte, aufeinanderfolgende Befehle. Wenn Sie wiederholt zwischen zwei angegebenen Befehlen wechseln, wird Ihr Bash-Verlauf mit Duplikaten
gefüllt
16

Fand diese Lösung in freier Wildbahn und getestet:

awk '!x[$0]++'

Wenn zum ersten Mal ein bestimmter Wert einer Zeile ($ 0) angezeigt wird, ist der Wert von x [$ 0] Null.
Der Wert von Null wird mit invertiert !und wird zu Eins.
Eine Anweisung, die eins ergibt, löst die Standardaktion print aus.

Daher wird ein bestimmtes Objekt beim ersten Mal $0gedruckt.

Bei jeder nächsten x[$0]Inkrementierung des Werts von (den Wiederholungen) ist der
negierte Wert Null, und eine Anweisung, die mit Null bewertet wird, wird nicht gedruckt.

Um den zuletzt wiederholten Wert beizubehalten, kehren Sie den Verlauf um und verwenden Sie dasselbe awk:

awk '!x[$0]++' ~/.bash_history                 # keep the first value repeated.

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.
Clayton Stanley
quelle
Beeindruckend! Das hat einfach funktioniert. Aber es beseitigt alles bis auf das erste Vorkommen, denke ich. Ich habe die Reihenfolge der Zeilen mit Sublime Text umgekehrt, bevor ich dies ausgeführt habe. Jetzt mache ich es noch einmal rückgängig, um einen sauberen Verlauf zu erhalten, bei dem nur das letzte Vorkommen aller Duplikate zurückbleibt. Danke.
Trss
Schau dir meine Antwort an!
Ali Shakiba
Schöne, saubere und allgemeine Antwort (nicht auf den History-Anwendungsfall beschränkt), ohne einen Bazilion-Unterprozess zu starten ;-)
JepZ
9

Erweiterung der Antwort von Clayton:

tac $HISTFILE | awk '!x[$0]++' | tac | sponge $HISTFILE

tacMachen Sie die Datei rückgängig, stellen Sie sicher, dass Sie installiert haben, moreutilsdamit Sie spongeverfügbar sind, andernfalls verwenden Sie eine temporäre Datei.

Ali Shakiba
quelle
1
Verwenden Sie brew install coreutilsund beachten Sie, dass bei allen GNU-Utils ein gvorangestellt ist, um Verwechslungen mit den in BSD integrierten Mac-Befehlen zu vermeiden (z. B. gsed ist GNU, sed ist BSD). Also benutze gtac.
Tralston
Ich brauchte history -c und history -r, um die history zu nutzen
drescherjm
4

Diese würden die letzten duplizierten Zeilen behalten:

ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history
Lri
quelle
Um genau zu sein, habe ich recht, dass Sie hier zwei (großartige) Lösungen gezeigt haben und ein Benutzer nur eine von ihnen ausführen muss? Entweder der Rubin oder der Bash?
Jonathan Hartley
3

Dies ist ein alter Beitrag, aber ein fortwährendes Problem für Benutzer, die mehrere Terminals geöffnet und den Verlauf zwischen Fenstern synchronisiert, aber nicht dupliziert haben möchten.

Meine Lösung in .bashrc:

shopt -s histappend
export HISTCONTROL=ignoreboth:erasedups
export PROMPT_COMMAND="history -n; history -w; history -c; history -r"
tac "$HISTFILE" | awk '!x[$0]++' > /tmp/tmpfile  &&
                tac /tmp/tmpfile > "$HISTFILE"
rm /tmp/tmpfile
  • Die Option histappend fügt den Verlauf des Puffers am Ende der Verlaufsdatei hinzu ($ HISTFILE).
  • Unkenntnis und Löschung verhindern, dass doppelte Einträge in der $ HISTFILE gespeichert werden
  • Der Eingabeaufforderungsbefehl aktualisiert den Verlaufscache
    • history -n Liest alle Zeilen aus $ HISTFILE, die seit dem letzten Wagenrücklauf in einem anderen Terminal aufgetreten sind
    • history -w schreibt den aktualisierten Puffer in $ HISTFILE
    • history -c löscht den Puffer, damit keine Duplizierung auftritt
    • history -r liest die $ HISTFILE erneut und hängt sie an den jetzt leeren Puffer an
  • Das awk-Skript speichert das erste Vorkommen jeder Zeile, auf die es stößt. tacKehrt es um und kehrt es dann zurück, damit es mit den aktuellsten Befehlen gespeichert werden kann, die noch aktuell im Verlauf sind
  • rm die / tmp-Datei

Jedes Mal, wenn Sie eine neue Shell öffnen, werden im Verlauf alle Dupes gelöscht. Jedes Mal, wenn Sie die EnterTaste in einem anderen Shell- / Terminalfenster drücken, wird der Verlauf aus der Datei aktualisiert.

lächelnder Frosch
quelle
Hier ist eine hervorragende Erklärung dafür in den Kommentaren
smilingfrog
Wenn "Unwissenheit und Löschung verhindern, dass Duplikate gespeichert werden", warum müssen Sie dann auch den Befehl "awk" ausführen, um Duplikate aus der Datei zu entfernen? Liegt es daran, dass "Ignorieren und Löschen" nur die Rettung aufeinanderfolgender Dupes verhindert ? Es tut mir leid, pedantisch zu sein, ich versuche nur zu verstehen.
Jonathan Hartley
1
Beim Löschen werden nur aufeinanderfolgende Duplikate gelöscht. Und Sie haben Recht, dass der Befehl awk den Befehl erasedupes dupliziert, wodurch er überflüssig wird.
Lachender Frosch
Danke, das macht mir klar, was los ist.
Jonathan Hartley
0

Es ist schwierig, jeden neuen Befehl eindeutig aufzuzeichnen. Zuerst müssen Sie hinzufügen ~/.profileoder ähnlich:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Dann müssen Sie hinzufügen ~/.bash_logout:

history -a
history -w
Steven Penny
quelle
Können Sie mir helfen, zu verstehen, warum Sie beim Abmelden ungeschriebenen Verlauf an die Verlaufsdatei anhängen müssen, bevor Sie die gesamte Verlaufsdatei neu schreiben können? Können Sie nicht einfach die gesamte Datei ohne das Anhängen schreiben?
Jonathan Hartley