Bagaimana saya bisa menghapus duplikat di .bash_history saya, menjaga ketertiban?

61

Saya sangat menikmati menggunakan control+rsecara rekursif mencari riwayat perintah saya. Saya telah menemukan beberapa opsi bagus yang ingin saya gunakan:

# 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

Satu-satunya masalah bagi saya adalah bahwa erasedupshanya menghapus duplikat berurutan - sehingga dengan serangkaian perintah ini:

ls
cd ~
ls

The lsperintah akan benar-benar disimpan dua kali. Saya sudah berpikir tentang menjalankan w / cron secara berkala:

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

Ini akan mencapai penghapusan duplikat, tetapi sayangnya pesanan tidak akan dipertahankan. Jika saya tidak sortfile terlebih dahulu saya tidak percaya uniqbisa berfungsi dengan baik.

Bagaimana saya bisa menghapus duplikat di .bash_history saya, menjaga ketertiban?

Kredit tambahan:

Apakah ada masalah dengan menimpa .bash_historyfile melalui skrip? Sebagai contoh, jika Anda menghapus file log apache saya pikir Anda perlu mengirim sinyal nohup / reset dengan killitu flush itu koneksi ke file. Jika demikian halnya dengan .bash_historyfile, mungkin saya entah bagaimana dapat menggunakan psuntuk memeriksa dan memastikan tidak ada sesi yang terhubung sebelum skrip penyaringan dijalankan?

cwd
sumber
3
Cobalah ignoredupsalih-alih erasedupssebentar dan lihat bagaimana itu bekerja untuk Anda.
jw013
1
Saya tidak berpikir bash memegang pegangan file terbuka ke file histori - ia membaca / menulisnya ketika perlu, jadi seharusnya (perhatikan - harus - saya belum menguji) aman untuk menimpanya dari tempat lain.
D_Bye
1
Saya baru belajar sesuatu yang baru pada kalimat pertama pertanyaan Anda. Trik bagus!
Ricardo
Saya gagal menemukan halaman manual untuk semua opsi pada historyperintah. Di mana saya harus mencari?
Jonathan Hartley
Opsi histori ada di 'man bash', cari bagian 'perintah shell builtin', lalu 'histori' di bawahnya.
Jonathan Hartley

Jawaban:

36

Menyortir sejarah

Perintah ini bekerja seperti sort|uniq, tetapi menjaga garis di tempatnya

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

Pada dasarnya, tambahkan setiap baris ke nomornya. Setelah sort|uniq-ing, semua baris diurutkan kembali sesuai dengan urutan aslinya (menggunakan bidang nomor baris) dan bidang nomor baris dihapus dari garis.

Solusi ini memiliki kekurangan bahwa tidak ditentukan perwakilan kelas yang mana dari garis yang sama yang akan membuatnya dalam output dan oleh karena itu posisinya dalam output akhir tidak ditentukan. Namun, jika perwakilan terbaru harus dipilih, Anda dapat sortmemasukkan dengan kunci kedua:

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

Mengelola .bash_history

Untuk membaca dan menulis kembali sejarah, Anda dapat menggunakan history -adan history -wmasing - masing.

artisoex
sumber
6
Versi menghias-sort-undecorate , diimplementasikan dengan alat shell. Bagus.
ire_and_curses
Dengan sort, -rsakelar selalu membalik urutan penyortiran. Tetapi ini tidak akan menghasilkan hasil yang Anda pikirkan. sortmenganggap dua kejadian lsidentik dengan hasil yang, bahkan ketika dibalik, urutan akhirnya tergantung pada algoritma pengurutan. Tetapi lihat pembaruan saya untuk ide lain.
artistoex
1
Jika Anda tidak ingin memodifikasi .bash_history, Anda dapat memasukkan yang berikut ini di .bashrc: alias history = 'history | sort -k2 -k 1,1nr | uniq -f 1 | sort -n '
Nathan
Apa yang ada nldi awal setiap baris kode? Bukankah seharusnya begitu history?
AL
1
@AL nl menambahkan nomor baris. Perintah secara keseluruhan memecahkan masalah umum: menghapus duplikat sambil mempertahankan pesanan. Input dibaca dari stdin.
artistoex
49

Jadi saya mencari hal yang persis sama setelah terganggu oleh duplikat, dan menemukan bahwa jika saya mengedit ~ / .bash_profile (Mac) saya dengan:

export HISTCONTROL=ignoreboth:erasedups

Itu tidak persis apa yang Anda inginkan, itu hanya membuat perintah terbaru. ignorebothsebenarnya hanya suka melakukan ignorespace:ignoredupsdan itu bersamaan dengan erasedupsmenyelesaikan pekerjaan.

Setidaknya di terminal Mac saya dengan bash ini berfungsi dengan sempurna. Ditemukan di sini di askubuntu.com .

sprite
sumber
10
ini seharusnya jawaban yang benar
MitchBroadhead
diuji pada Max OS X Yosemite dan di Ubuntu 14_04
Ricardo
1
setuju dengan @MitchBroadhead. ini menyelesaikan masalah di dalam bash itu sendiri, tanpa cron-job eksternal. mengujinya di ubuntu 17,04 dan 16,04 LTS
Georg Jung
bekerja di OpenBSD juga. Itu hanya menghapus dups dari perintah apa pun yang ditambahkan ke file riwayat, yang baik untuk saya. Ini memiliki efek menarik dari memperpendek file histori ketika saya memasukkan perintah yang sudah ada sebagai duplikat sebelumnya. Sekarang saya dapat membuat maks file histori saya lebih pendek.
WeakPointer
1
Ini hanya mengabaikan duplikat, perintah berturut-turut. Jika Anda berganti-ganti secara bergantian antara dua perintah yang diberikan, riwayat bash Anda akan terisi dengan duplikat
Dylanthepiguy
16

Temukan solusi ini di alam liar dan teruji:

awk '!x[$0]++'

Pertama kali nilai spesifik garis ($ 0) terlihat, nilai x [$ 0] adalah nol.
Nilai nol dibalik dengan !dan menjadi satu.
Pernyataan yang mengevaluasi ke satu menyebabkan tindakan default, yang dicetak.

Oleh karena itu, pertama kali spesifik $0terlihat, itu dicetak.

Setiap waktu berikutnya (pengulangan) nilai x[$0]telah bertambah,
nilai negasinya adalah nol, dan pernyataan yang mengevaluasi nol tidak dicetak.

Untuk menjaga nilai berulang yang terakhir, balikkan sejarah dan gunakan awk yang sama:

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

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.
Clayton Stanley
sumber
Wow! Itu baru saja berhasil. Tapi itu menghilangkan semua kecuali kejadian pertama kurasa. Saya telah membalik urutan garis menggunakan Sublime Text sebelum menjalankan ini. Sekarang saya akan membalikkannya lagi untuk mendapatkan riwayat bersih dengan hanya kejadian terakhir dari semua duplikat yang tertinggal. Terima kasih.
trss
Lihatlah jawaban saya!
Ali Shakiba
Jawaban bersih dan umum yang bagus (tidak terbatas pada case-use history) tanpa meluncurkan sub-proses
bazilion
9

Memperluas jawaban Clayton:

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

tacbalikkan file, pastikan Anda telah menginstal moreutilssehingga Anda telah spongetersedia, jika tidak gunakan file temp.

Ali Shakiba
sumber
1
Bagi mereka yang menggunakan Mac, gunakan brew install coreutils, dan perhatikan bahwa semua util GNU memiliki kemampuan guntuk menghindari kebingungan dengan perintah Mac bawaan BSD (mis. Gsed adalah GNU sedangkan sed adalah BSD). Jadi gunakan gtac.
tralston
Saya membutuhkan history -c dan history -r untuk menggunakannya menggunakan sejarah
drescherjm
4

Ini akan menjaga baris yang digandakan terakhir:

ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history
Lri
sumber
Secara eksplisit, apakah saya mengerti benar bahwa Anda telah menunjukkan dua solusi (luar biasa) di sini, dan pengguna hanya perlu menjalankan salah satu dari mereka? Entah yang ruby, atau yang Bash?
Jonathan Hartley
3

Ini adalah posting lama, tetapi merupakan masalah abadi bagi pengguna yang ingin memiliki beberapa terminal terbuka, dan memiliki sejarah yang disinkronkan di antara windows, tetapi tidak diduplikasi.

Solusi saya di .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
  • opsi histappend menambahkan histori buffer ke akhir file histori ($ HISTFILE)
  • ignoreboth dan er terhapus mencegah entri duplikat disimpan di $ HISTFILE
  • Perintah prompt memperbarui cache riwayat
    • history -n membaca semua baris dari $ HISTFILE yang mungkin terjadi di terminal berbeda sejak carriage return terakhir
    • history -w menulis buffer yang diperbarui ke $ HISTFILE
    • history -c menyeka buffer sehingga tidak terjadi duplikasi
    • history -r baca kembali $ HISTFILE, tambahkan ke buffer yang sekarang kosong
  • skrip awk menyimpan kemunculan pertama dari setiap baris yang ditemuinya. tacmembalikkannya, lalu membalikkannya kembali sehingga dapat disimpan dengan perintah terbaru yang paling baru dalam sejarah
  • rm file / tmp

Setiap kali Anda membuka shell baru, histori memiliki semua dupes dihapus, dan setiap kali Anda menekan Enterkunci di jendela shell / terminal yang berbeda, itu memperbarui sejarah ini dari file.

katak tersenyum
sumber
Jika "bodoh dan terhapus mencegah dupes dari disimpan", lalu mengapa Anda juga perlu melakukan perintah "awk" untuk menghapus dupes dari file? Apakah karena "orang bodoh dan terhapus" hanya mencegah dupe berturut-turut disimpan? Maaf karena bertele-tele, aku hanya berusaha mengerti.
Jonathan Hartley
1
menghapus hanya menghapus duplikat berurutan. Dan Anda benar bahwa perintah awk menduplikasi perintah terhapus membuatnya berlebihan.
smilingfrog
Terima kasih, itu menjelaskan kepada saya apa yang terjadi.
Jonathan Hartley
0

Untuk merekam secara unik setiap perintah baru itu rumit. Pertama, Anda perlu menambahkan ~/.profileatau serupa:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Maka Anda perlu menambahkan ~/.bash_logout:

history -a
history -w
Steven Penny
sumber
Bisakah Anda membantu saya memahami mengapa, pada saat logout, Anda perlu menambahkan riwayat tidak tertulis ke file riwayat sebelum kemudian menulis ulang seluruh file riwayat? Tidak bisakah Anda menulis seluruh file tanpa 'tambahkan'?
Jonathan Hartley