Jak mogę usunąć duplikaty z mojej historii .bash, zachowując porządek?

60

Bardzo lubię korzystać control+rz rekurencyjnego przeszukiwania historii poleceń. Znalazłem kilka dobrych opcji, z którymi lubię się z tym korzystać:

# 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

Jedynym problemem dla mnie jest to, że erasedupsusuwa tylko sekwencyjne duplikaty - dzięki czemu za pomocą tego ciągu poleceń:

ls
cd ~
ls

lsKomenda faktycznie będą rejestrowane dwukrotnie. Myślałem o okresowym uruchamianiu w / cron:

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

Pozwoliłoby to na usunięcie duplikatów, ale niestety kolejność nie zostałaby zachowana. Jeśli nie sortnajpierw plik, nie sądzę, że uniqmoże działać poprawnie.

Jak mogę usunąć duplikaty z mojej historii .bash, zachowując porządek?

Dodatkowy kredyt:

Czy są jakieś problemy z nadpisaniem .bash_historypliku za pomocą skryptu? Na przykład, jeśli usuniesz plik dziennika Apache, myślę, że musisz wysłać sygnał nohup / reset, killaby umożliwić mu opróżnienie połączenia z plikiem. Jeśli tak jest w przypadku .bash_historypliku, być może mógłbym w jakiś sposób pssprawdzić i upewnić się, że nie ma połączonych sesji przed uruchomieniem skryptu filtrującego?

cwd
źródło
3
Spróbuj ignoredupszamiast tego erasedupsi zobacz, jak to działa dla Ciebie.
jw013,
1
Nie sądzę, aby bash trzymał otwarty uchwyt pliku do pliku historii - czyta / zapisuje go, kiedy jest to konieczne, więc powinien (uwaga - powinien - nie testowałem) bezpiecznie go zastąpić z innego miejsca.
D_Bye
1
Właśnie nauczyłem się czegoś nowego w pierwszym zdaniu twojego pytania. Dobry trik!
Ricardo
Nie mogę znaleźć strony podręcznika dla wszystkich opcji historypolecenia. Gdzie powinienem szukać?
Jonathan Hartley
Opcje historii znajdują się w „man bash”, wyszukaj sekcję „wbudowane polecenia powłoki”, a następnie „historię” poniżej.
Jonathan Hartley

Odpowiedzi:

36

Sortowanie historii

To polecenie działa podobnie sort|uniq, ale utrzymuje linie na miejscu

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

Zasadniczo wstawia do każdej linii swój numer. Po zakończeniu sort|uniqwszystkie linie są sortowane z powrotem zgodnie z ich pierwotną kolejnością (przy użyciu pola numeru linii), a pole numeru linii jest usuwane z linii.

Takie rozwiązanie ma tę wadę, że nie jest zdefiniowane, który reprezentant klasy równych linii sprawi, że będzie on na wyjściu, a zatem jego pozycja na końcowym wyjściu jest niezdefiniowana. Jeśli jednak należy wybrać najnowszego przedstawiciela, możesz sortwprowadzić dane drugim przyciskiem:

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

Zarządzanie .bash_history

Do ponownego czytania i zapisywania historii można użyć odpowiednio history -ai history -w.

artistoex
źródło
6
Wersja decorate-sort-undecorate , implementowana za pomocą narzędzi powłoki. Miły.
ire_and_curses
Przy sortpomocy -rprzełącznik zawsze odwraca kolejność sortowania. Ale to nie da rezultatu, który masz na myśli. sortuważa dwa wystąpienia lsza identyczne z wynikiem, że nawet po odwróceniu ostateczna kolejność zależy od algorytmu sortowania. Ale zobacz moją aktualizację dla innego pomysłu.
artistoex,
1
W przypadku, gdy nie chcesz modyfikować .bash_history, możesz umieścić w .bashrc: alias history = 'history | sortuj -k2 -k 1,1nr | uniq -f 1 | sort -n '
Nathan
Co znajduje się nlna początku każdej linii kodu? Nie powinno tak być history?
AL
1
@AL nl dodaje numery linii. Komenda jako całość rozwiązuje ogólny problem: usuwanie duplikatów przy zachowaniu porządku. Dane wejściowe są odczytywane ze standardowego wejścia.
artistoex,
48

Tak więc szukałem dokładnie tego samego po denerwowaniu przez duplikaty i stwierdziłem, że jeśli edytuję mój ~ / .bash_profile (Mac) za pomocą:

export HISTCONTROL=ignoreboth:erasedups

Robi dokładnie to, co chciałeś, zachowuje tylko najnowsze polecenia. ignorebothjest tak naprawdę jak robienie ignorespace:ignoredupsi to wraz z erasedupswykonaniem pracy.

Przynajmniej na moim terminalu Mac z bash to działa idealnie. Znalazłem go tutaj na askubuntu.com .

krasnoludek
źródło
10
to powinna być poprawna odpowiedź
MitchBroadhead
testowany na Max OS X Yosemite i Ubuntu 14_04
Ricardo
1
zgadzam się z @MitchBroadhead. rozwiązuje to problem w samym bashu, bez zewnętrznego zadania cron. przetestowałem na Ubuntu 17.04 i 16.04 LTS
Georg Jung
działa również na OpenBSD. Usuwa tylko duplikaty każdego polecenia, które dołącza do pliku historii, co jest dla mnie w porządku. Ma interesujący efekt skracania pliku historii, gdy wprowadzam polecenia, które istniały wcześniej jako duplikaty. Teraz mogę maksymalnie skrócić mój plik historii.
WeakPointer
1
To ignoruje tylko powtarzające się kolejne polecenia. Jeśli kilkakrotnie naprzemiennie użyjesz dwóch podanych poleceń, twoja historia basha zapełni się duplikatami
Dylanthepiguy
16

Znalazłem to rozwiązanie na wolności i przetestowałem:

awk '!x[$0]++'

Przy pierwszym wyświetleniu określonej wartości linii (0 USD) wartość x [0 USD] wynosi zero.
Wartość zero jest odwracana !i staje się jednością.
Instrukcja, której wynikiem jest jeden, powoduje domyślne działanie, którym jest print.

Dlatego przy pierwszym wyświetleniu określonego $0jest on drukowany.

Za każdym razem (powtórzenia) wartość x[$0]jest zapisywana,
jej wartość zanegowana wynosi zero, a instrukcja, która zwraca zero, nie jest drukowana.

Aby zachować ostatnią powtarzaną wartość, odwróć historię i użyj tego samego awk:

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

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.
Clayton Stanley
źródło
Łał! To po prostu działało. Ale wydaje mi się, że usuwa wszystko oprócz pierwszego. Przed uruchomieniem odwróciłem kolejność wierszy za pomocą Sublime Text. Teraz odwrócę to, aby uzyskać czystą historię z ostatnim wystąpieniem wszystkich duplikatów, które pozostały. Dziękuję Ci.
trss
Sprawdź moją odpowiedź!
Ali Shakiba,
Ładna, czysta i ogólna odpowiedź (nie ograniczająca się do przypadku użycia historii) bez uruchamiania podprocesów bazilion ;-)
JepZ
9

Rozszerzanie odpowiedzi Clayton:

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

tacodwróć plik, upewnij się, że masz zainstalowany, moreutilsaby był spongedostępny, w przeciwnym razie użyj pliku tymczasowego.

Ali Shakiba
źródło
1
W przypadku komputerów Mac, użyj brew install coreutilsi zwróć uwagę, że wszystkie narzędzia GNU są przygotowane, gaby uniknąć pomyłek z wbudowanymi komendami MacD BSD (np. Gsed to GNU, a sed to BSD). Więc użyj gtac.
tralston,
Potrzebowałem historii -c i historii -r, aby użyć historii
drescherjm
4

Pozwoli to zachować ostatnie zduplikowane linie:

ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history
Lri
źródło
Mówiąc wprost, czy rozumiem, że pokazałeś tutaj dwa (wspaniałe) rozwiązania, a użytkownik musi wykonać tylko jedno z nich? Albo rubinowy, albo bashowy?
Jonathan Hartley
3

To stary post, ale wieczny problem dla użytkowników, którzy chcą mieć otwartych wiele terminali i synchronizować historię między oknami, ale nie powielać.

Moje rozwiązanie w .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
  • opcja histappend dodaje historię bufora na końcu pliku historii ($ HISTFILE)
  • ignoreboth i erasedups zapobiegają zapisywaniu zduplikowanych wpisów w $ HISTFILE
  • Polecenie monitu aktualizuje pamięć podręczną historii
    • history -n odczytuje wszystkie wiersze z $ HISTFILE, które mogły wystąpić w innym terminalu od ostatniego powrotu karetki
    • history -w zapisuje zaktualizowany bufor do $ HISTFILE
    • history -c czyści bufor, aby nie nastąpiło powielanie
    • history -r ponownie odczytuje $ HISTFILE, dołączając do teraz pustego bufora
  • skrypt awk przechowuje pierwsze wystąpienie każdego napotkanego wiersza. taccofa go, a następnie cofa do tyłu, aby można go było zapisać za pomocą najnowszych poleceń, wciąż najnowszych w historii
  • rm plik / tmp

Za każdym razem, gdy otwierasz nową powłokę, historia usuwa wszystkie duplikaty i za każdym razem, gdy naciśniesz Enterklawisz w innym oknie powłoki / terminala, aktualizuje tę historię z pliku.

uśmiechnięta żaba
źródło
Oto doskonałe wyjaśnienie tego w komentarzach
smilingfrog
Jeśli „ignorowanie obu i wymazywanie uniemożliwia zapisywanie duplikatów”, to dlaczego musisz także wykonać polecenie „awk”, aby usunąć duplikaty z pliku? Czy to dlatego, że „ignorowanie i usuwanie” zapobiega tylko zapisywaniu kolejnych duplikatów? Przepraszam, że jestem pedantyczny, staram się tylko zrozumieć.
Jonathan Hartley
1
wymazane usuwa tylko kolejne duplikaty. I masz rację, że polecenie awk powiela polecenie wymazanego, czyniąc go zbędnym.
smilingfrog
Dziękuję, to wyjaśnia mi, co się dzieje.
Jonathan Hartley
0

Jednoznaczne zarejestrowanie każdego nowego polecenia jest trudne. Najpierw musisz dodać ~/.profilelub podobne:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Następnie musisz dodać do ~/.bash_logout:

history -a
history -w
Steven Penny
źródło
Czy możesz mi pomóc zrozumieć, dlaczego po wylogowaniu musisz dołączyć niepisaną historię do pliku historii przed ponownym zapisaniem całego pliku historii? Nie możesz po prostu napisać całego pliku bez „dołączenia”?
Jonathan Hartley