Come posso rimuovere i duplicati nel mio .bash_history, preservando l'ordine?


61

Mi piace molto usare la control+rricerca ricorsiva nella cronologia dei miei comandi. Ho trovato alcune buone opzioni che mi piace usare con esso:

# 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

L'unico problema per me è che erasedupscancella solo i duplicati sequenziali, quindi con questa stringa di comandi:

ls
cd ~
ls

Il lscomando verrà effettivamente registrato due volte. Ho pensato di eseguire periodicamente w / cron:

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

Ciò consentirebbe di rimuovere i duplicati, ma purtroppo l'ordine non verrebbe conservato. Se non sortvisualizzo prima il file, non credo che uniqpossa funzionare correttamente.

Come posso rimuovere i duplicati nel mio .bash_history, preservando l'ordine?

Credito extra:

Ci sono problemi con la sovrascrittura del .bash_historyfile tramite uno script? Ad esempio, se si rimuove un file di registro di Apache penso che sia necessario inviare un segnale nohup / reset killper farlo svuotare la connessione al file. Se questo è il caso del .bash_historyfile, forse potrei in qualche modo usare psper verificare e assicurarsi che non ci siano sessioni connesse prima che venga eseguito lo script di filtro?


3
Prova ignoredupsinvece erasedupsper un po 'e vedi come funziona per te.
jw013,

1
Non credo che bash abbia un handle di file aperto nel file di cronologia: lo legge / scrive quando è necessario, quindi dovrebbe (nota - dovrebbe - non ho ancora testato) essere sicuro di sovrascriverlo da altrove.
D_Bye,

1
Ho appena imparato qualcosa di nuovo sulla prima frase della tua domanda. Buon trucco!
Ricardo,

Non riesco a trovare la pagina man per tutte le opzioni del historycomando. Dove dovrei cercare?
Jonathan Hartley,

Le opzioni della cronologia sono in "man bash", cerca la sezione "comandi incorporati nella shell", quindi "cronologia" sotto quella.
Jonathan Hartley,

Risposte:


36

Ordinamento della cronologia

Questo comando funziona come sort|uniq, ma mantiene le linee in posizione

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

Fondamentalmente, antepone a ogni riga il suo numero. Dopo sort|uniq, tutte le righe vengono riordinate in base al loro ordine originale (utilizzando il campo del numero di riga) e il campo del numero di riga viene rimosso dalle righe.

Questa soluzione ha il difetto di non essere definita quale rappresentante di una classe di linee uguali la renderà nell'output e quindi la sua posizione nell'output finale non è definita. Tuttavia, se si deve scegliere l'ultimo rappresentante, è possibile sortinserire un secondo tasto:

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

Gestione di .bash_history

Per rileggere e riscrivere la cronologia, è possibile utilizzare history -ae history -wrispettivamente.


6
Una versione di decorate-ordina-undecorate , implementata con strumenti shell. Bello.
ire_and_curses,

Con sort, l' -rinterruttore inverte sempre l'ordinamento. Ma questo non produrrà il risultato che hai in mente. sortconsidera le due occorrenze lsidentiche al risultato che, anche se invertito, l'eventuale ordine dipende dall'algoritmo di ordinamento. Ma vedi il mio aggiornamento per un'altra idea.
artistoex,

1
Nel caso in cui non si desideri modificare .bash_history, è possibile inserire quanto segue in .bashrc: alias history = 'history | ordina -k2 -k 1,1nr | uniq -f 1 | sort -n '
Nathan,

Cosa c'è nlall'inizio di ogni riga di codice? Non dovrebbe essere history?
AL

1
@AL nl aggiunge i numeri di riga. Il comando nel suo insieme risolve il problema generale: rimuovere i duplicati mantenendo l'ordine. L'input viene letto dallo stdin.
artistoex,

49

Quindi stavo cercando la stessa cosa esatta dopo essere stato infastidito dai duplicati e ho scoperto che se modifico il mio ~ / .bash_profile (Mac) con:

export HISTCONTROL=ignoreboth:erasedups

Fa esattamente quello che volevi, mantiene solo l'ultimo di qualsiasi comando. ignorebothè in realtà proprio come fare ignorespace:ignoredupse che insieme a erasedupsfare il lavoro.

Almeno sul mio terminale Mac con bash questo lavoro è perfetto. L'ho trovato qui su askubuntu.com .


10
questa dovrebbe essere la risposta corretta
MitchBroadhead

testato su Max OS X Yosemite e su Ubuntu 14_04
Ricardo

1
d'accordo con @MitchBroadhead. questo risolve il problema all'interno di bash stesso, senza cron-job esterno. testato su Ubuntu 17.04 e 16.04 LTS
Georg Jung il

funziona anche su OpenBSD. Rimuove solo i duplicati di qualsiasi comando che viene aggiunto al file della cronologia, il che va bene per me. Ha l'effetto interessante di accorciare il file della cronologia mentre inserisco i comandi che erano esistiti come duplicati prima. Ora posso ridurre al massimo il mio file di cronologia.
WeakPointer

2
Ciò ignora solo comandi doppi consecutivi. Se si alternano ripetutamente tra due comandi, la cronologia di bash si riempie di duplicati
Dylanthepiguy,

16

Ho trovato questa soluzione in natura e testata:

awk '!x[$0]++'

La prima volta che viene visualizzato un valore specifico di una riga ($ 0), il valore di x [$ 0] è zero.
Il valore di zero viene invertito con !e diventa uno.
Un'istruzione che valuta una causa l'azione predefinita, che è la stampa.

Pertanto, la prima volta che $0viene visualizzato uno specifico , viene stampato.

Ogni volta (le ripetizioni) il valore di x[$0]è stato incrostato, il
suo valore negato è zero e un'istruzione che valuta zero non viene stampata.

Per mantenere l'ultimo valore ripetuto, invertire la cronologia e utilizzare lo stesso awk:

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

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.

Wow! Questo ha funzionato. Ma rimuove tutto tranne la prima occorrenza, immagino. Avevo invertito l'ordinamento delle righe usando Sublime Text prima di eseguire questo. Ora lo invertirò di nuovo per ottenere una cronologia pulita con solo l'ultima occorrenza di tutti i duplicati lasciati indietro. Grazie.
trss,

Guarda la mia risposta!
Ali Shakiba,

Bella risposta chiara e generale (non limitata al caso d'uso della cronologia) senza avviare un sotto-processo
bazilion

9

Estensione della risposta di Clayton:

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

tacinverti il ​​file, assicurati di averlo installato in moreutilsmodo da renderlo spongedisponibile, altrimenti usa un file temporaneo.


1
Per quelli su Mac, utilizzare brew install coreutilse notare che tutti i programmi di utilità GNU sono gpredisposti per evitare confusione con i comandi Mac incorporati di BSD (ad esempio gsed è GNU mentre sed è BSD). Quindi usa gtac.
tralston,

Avevo bisogno della storia -c e della storia -r per farlo usare la storia
drescherjm

4

Questi manterrebbero le ultime righe duplicate:

ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history

Per essere esplicito, sto capendo bene che hai mostrato due (splendide) soluzioni qui e un utente deve solo eseguirne una? O quello rubino o quello Bash?
Jonathan Hartley,

3

Questo è un vecchio post, ma un problema perpetuo per gli utenti che vogliono avere più terminali aperti e la cronologia è sincronizzata tra le finestre, ma non duplicata.

La mia soluzione 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
  • L'opzione histappend aggiunge la cronologia del buffer alla fine del file della cronologia ($ HISTFILE)
  • ignoreboth e cancelledups impediscono il salvataggio di voci duplicate nel $ HISTFILE
  • Il comando prompt aggiorna la cache della cronologia
    • history -n legge tutte le righe da $ HISTFILE che potrebbero essersi verificate in un terminale diverso dall'ultimo ritorno a capo
    • history -w scrive il buffer aggiornato in $ HISTFILE
    • history -c pulisce il buffer in modo che non si verifichino duplicazioni
    • history -r rilegge $ HISTFILE, aggiungendo al buffer ora vuoto
  • lo script awk memorizza la prima occorrenza di ogni riga che incontra. taclo inverte e quindi lo inverte di nuovo in modo che possa essere salvato con i comandi più recenti ancora più recenti nella cronologia
  • rm il file / tmp

Ogni volta che si apre una nuova shell, la cronologia ha cancellato tutti i duplicati e ogni volta che si Enterpreme il tasto in una finestra shell / terminale diversa, aggiorna questa cronologia dal file.



Se "ignoreboth and erasedups impedisce il salvataggio dei duplicati", perché è necessario eseguire anche il comando "awk" per rimuovere i duplicati dal file? È perché "ignoreboth and erasedups" impedisce solo il salvataggio di duplicati consecutivi ? Mi dispiace essere pedante, sto solo cercando di capire.
Jonathan Hartley,

1
erasedups cancella solo i duplicati consecutivi. E hai ragione che il comando awk duplica il comando cancellato rendendolo superfluo.
smilingfrog,

Grazie, questo mi rende chiaro cosa sta succedendo.
Jonathan Hartley,

0

Registrare in modo univoco ogni nuovo comando è complicato. Per prima cosa devi aggiungere ~/.profileo simili:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Quindi devi aggiungere a ~/.bash_logout:

history -a
history -w

Potete aiutarmi a capire perché, al logout, è necessario aggiungere la cronologia non scritta al file della cronologia prima di riscrivere l'intero file della cronologia? Non puoi semplicemente scrivere l'intero file senza "append"?
Jonathan Hartley,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.