Comment puis-je supprimer les doublons dans mon .bash_history, en préservant l'ordre?

61

J'aime vraiment utiliser control+rpour rechercher de manière récursive mon historique de commandes. J'ai trouvé quelques bonnes options que j'aime utiliser avec:

# 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

Le seul problème pour moi est que erasedupsseulement les doublons séquentiels ne sont effacés - de sorte qu'avec cette chaîne de commandes:

ls
cd ~
ls

La lscommande sera effectivement enregistrée deux fois. J'ai pensé à exécuter périodiquement w / cron:

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

Cela permettrait de supprimer les doublons, mais malheureusement, l'ordre ne serait pas préservé. Si je ne sortcommence pas par le fichier, je ne crois pas que cela uniqpuisse fonctionner correctement.

Comment puis-je supprimer les doublons dans mon .bash_history, en préservant l'ordre?

Crédit supplémentaire:

Existe-t-il des problèmes pour écraser le .bash_historyfichier via un script? Par exemple, si vous supprimez un fichier journal Apache, je pense que vous devez envoyer un signal nohup / reset avec killpour le vider de sa connexion au fichier. Si tel est le cas avec le .bash_historyfichier, je pourrais peut-être utiliser d'une manière ou d'une autre pspour vérifier et s'assurer qu'il n'y a pas de session connectée avant l'exécution du script de filtrage?

cwd
la source
3
Essayez ignoredupsplutôt erasedupspendant un moment et voyez comment cela fonctionne pour vous.
jw013
1
Je ne pense pas que bash contienne un handle de fichier ouvert dans le fichier d'historique - il le lit / l'écrit quand il le faut, il devrait donc (remarque - devrait - je n'ai pas testé) être sûr de l'écraser ailleurs.
D_Bye
1
Je viens d'apprendre quelque chose de nouveau sur la première phrase de votre question. Bon tour!
Ricardo
Je ne parviens pas à trouver la page de manuel pour toutes les options de la historycommande. Où devrais-je chercher?
Jonathan Hartley
Les options de l'historique sont dans 'man bash', recherchez la section 'commandes intégrées au shell', puis 'historique' en dessous.
Jonathan Hartley

Réponses:

36

Tri de l'histoire

Cette commande fonctionne comme sort|uniq, mais garde les lignes en place

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

Fondamentalement, chaque ligne est préfixée par son numéro. Ensuite sort|uniq, toutes les lignes sont triées selon leur ordre d'origine (à l'aide du champ numéro de ligne) et le champ numéro de ligne est supprimé des lignes.

Cette solution a le défaut de ne pas définir quel représentant d'une classe de lignes égales le fera dans la sortie et, par conséquent, sa position dans la sortie finale n'est pas définie. Toutefois, si le dernier représentant devait être choisi, vous pouvez sortsaisir une seconde clé:

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

Gérer .bash_history

Pour relire et réécrire l'historique, vous pouvez utiliser history -aet history -wrespectivement.

artistoex
la source
6
Une version de decorate-sort-undecorate , implémentée avec des outils shell. Agréable.
ire_and_curses
Avec sort, le -rcommutateur inverse toujours l'ordre de tri. Mais cela ne donnera pas le résultat escompté. sortconsidère les deux occurrences de lscomme identiques avec le résultat que, même inversé, l'ordre éventuel dépend de l'algorithme de tri. Mais voir ma mise à jour pour une autre idée.
artistoex
1
Au cas où vous ne voudriez pas modifier .bash_history, vous pourriez mettre ce qui suit dans .bashrc: alias history = 'history | trier -k2 -k 1,1nr | uniq -f 1 | sort -n '
Nathan
Qu'y a-t-il nlau début de chaque ligne de code? Ça ne devrait pas être ça history?
AL le
1
@AL nl ajoute des numéros de ligne. La commande dans son ensemble résout le problème général: supprimer les doublons tout en préservant l'ordre. L'entrée est lue à partir de stdin.
artistoex
49

Donc, je cherchais exactement la même chose après avoir été ennuyé par les doublons, et je me suis rendu compte que si je modifiais mon ~ / .bash_profile (Mac) avec:

export HISTCONTROL=ignoreboth:erasedups

Il fait exactement ce que vous vouliez, il ne garde que la dernière de toutes les commandes. ignorebothest en fait juste comme faire ignorespace:ignoredupset cela avec erasedupsfait le travail fait.

Au moins sur mon terminal Mac avec bash, ce travail est parfait. Trouvé ici sur askubuntu.com .

lutin
la source
10
cela devrait être la réponse correcte
MitchBroadhead
testé sur Max OS X Yosemite et sur Ubuntu 14_04
Ricardo
1
d'accord avec @MitchBroadhead. cela résout le problème dans bash lui-même, sans tâche externe. testé sur ubuntu 17.04 et 16.04 LTS
Georg Jung
fonctionne également sur OpenBSD. Il supprime uniquement les doublons des commandes qu’il ajoute au fichier d’historique, ce qui me convient parfaitement. Cela a pour effet intéressant de raccourcir le fichier historique car je saisis des commandes qui existaient auparavant en double. Je peux maintenant rendre mon fichier d’historique plus court.
WeakPointer
2
Cela ignore uniquement les commandes dupliquées et consécutives. Si vous alternez de manière répétée entre deux commandes données, votre historique bash se remplira de doublons
Dylanthepiguy
16

Trouvé cette solution à l'état sauvage et testé:

awk '!x[$0]++'

La première fois qu'une valeur spécifique d'une ligne ($ 0) est vue, la valeur de x [$ 0] est zéro.
La valeur de zéro est inversée avec !et devient un.
Une instruction évaluée à un provoque l'action par défaut, à savoir imprimer.

Par conséquent, la première fois qu'un élément spécifique $0est vu, il est imprimé.

A chaque fois (les répétitions) la valeur de x[$0]a été incrémentée,
sa valeur inversée est zéro et une instruction évaluée à zéro ne s'imprime pas.

Pour conserver la dernière valeur répétée, inversez l’historique et utilisez le même awk:

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

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.
Clayton Stanley
la source
Hou la la! Cela a juste fonctionné. Mais cela supprime tout sauf le premier événement je suppose. J'avais inversé l'ordre des lignes en utilisant Sublime Text avant d'exécuter ceci. Maintenant, je vais inverser encore une fois pour obtenir un historique vierge avec seulement la dernière occurrence de tous les doublons laissés. Je vous remercie.
trss
Regarde ma réponse!
Ali Shakiba
Belle réponse nette et générale (sans se limiter au cas d'utilisation de l'historique) sans lancer de sous-processus
bazilion
9

Prolonger la réponse de Clayton:

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

tacinverser le fichier, assurez-vous que vous avez installé moreutilsafin que vous avez spongedisponible, sinon utilisez un fichier temporaire.

Ali Shakiba
la source
1
Pour ceux sur Mac, utilisez brew install coreutilset notez que tous les utilitaires GNU ont été gpréfabriqués pour éviter toute confusion avec les commandes Mac intégrées de BSD (par exemple, gsed est GNU alors que sed est BSD). Alors utilisez gtac.
Tralston
J'avais besoin de history -c et de history -r pour que l'histoire soit
utilisée
4

Ceux-ci garderaient les dernières lignes dupliquées:

ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history
Lri
la source
Pour être explicite, ai-je bien compris que vous avez présenté deux (splendides) solutions ici, et qu'un utilisateur n'a besoin que de l'une d'entre elles? Le rubis ou le Bash?
Jonathan Hartley
3

Ceci est un vieux message, mais un problème perpétuel pour les utilisateurs qui souhaitent avoir plusieurs terminaux ouverts et dont l'historique doit être synchronisé entre les fenêtres, mais non dupliqué.

Ma solution dans .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
  • L'option histappend ajoute l'historique du tampon à la fin du fichier historique ($ HISTFILE)
  • ignoreboth et Erasedups empêche l’enregistrement des entrées en double dans $ HISTFILE
  • La commande prompt met à jour le cache de l'historique
    • history -n lit toutes les lignes de $ HISTFILE susceptibles de s’être produites dans un terminal différent depuis le dernier retour chariot
    • history -w écrit le tampon mis à jour dans $ HISTFILE
    • history -c efface le tampon pour éviter toute duplication
    • history -r relit le $ HISTFILE, en ajoutant au tampon maintenant vide
  • le script awk stocke la première occurrence de chaque ligne rencontrée. tacl'inverse, puis l'inverse afin qu'il puisse être enregistré avec les commandes les plus récentes encore les plus récentes de l'historique
  • rm le fichier / tmp

Chaque fois que vous ouvrez un nouveau shell, l'historique a toutes les dupes effacées, et chaque fois que vous appuyez sur la Entertouche dans une fenêtre shell / terminal différente, il met à jour cet historique à partir du fichier.

sourire
la source
Voici une excellente explication à cela dans les commentaires
smilingfrog
Si "ignorer les deux et effacer empêche les dupes d'être enregistrées", alors pourquoi avez-vous également besoin de faire la commande "awk" pour supprimer les dupes du fichier? Est-ce parce que "ignorer les deux et effacer" empêche uniquement la sauvegarde des dupes consécutives ? Désolé d'être pédant, j'essaie juste de comprendre.
Jonathan Hartley
1
effacement ne supprime que les doublons consécutifs. Et vous avez raison de dire que la commande awk duplique la commande effacée en la rendant superflue.
Smilingfrog
Merci, cela me fait comprendre ce qui se passe.
Jonathan Hartley
0

Enregistrer uniformément chaque nouvelle commande est délicat. Tout d'abord, vous devez ajouter ~/.profileou similaire:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Ensuite, vous devez ajouter à ~/.bash_logout:

history -a
history -w
Steven Penny
la source
Pouvez-vous m'aider à comprendre pourquoi, lors de la déconnexion, vous devez ajouter un historique non écrit au fichier historique avant de réécrire tout le fichier historique? Ne pouvez-vous pas simplement écrire le fichier entier sans 'append'?
Jonathan Hartley