Як я можу видалити дублікати з моєї .bash_history, зберігаючи порядок?


60

Мені дуже подобається використовувати control+rрекурсивний пошук моєї історії команд. Я знайшов кілька хороших варіантів, які я хотів би використовувати з ним:

# 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

Єдина проблема для мене полягає в тому, що erasedupsстирає лише послідовні дублікати, так що з цього рядка команд:

ls
cd ~
ls

lsКоманда на насправді буде записано двічі. Я думав про періодичне запуску w / cron:

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

Це дозволило б видалити дублікати, але, на жаль, порядок не буде збережено. Якщо я не sortзавантажую файл спочатку, я не вірю, що він uniqможе працювати належним чином.

Як я можу видалити дублікати з моєї .bash_history, зберігаючи порядок?

Додатковий кредит:

Чи є якісь проблеми з перезаписом .bash_historyфайлу через скрипт? Наприклад, якщо ви видалите файл журналу apache, я вважаю, що вам потрібно надіслати сигнал nohup / reset, killщоб він перетворився на з'єднання з файлом. Якщо це так з .bash_historyфайлом, можливо, я можу якось використати, psщоб перевірити і переконатися, що немає підключених сеансів до запуску сценарію фільтрації?


3
Спробуйте ignoredupsзамість цього erasedupsдеякий час і подивіться, як це працює для вас.
jw013

1
Я не думаю, що bash містить відкриту ручку файлу до файлу історії - він читає / записує, коли потрібно, тому слід (зауважте - повинен - я не перевіряв) безпечно перезаписати його з іншого місця.
D_Bye

1
Щойно я дізнався щось нове в першому реченні вашого запитання. Гарний трюк!
Рікардо

Мені не вдається знайти сторінку man для всіх варіантів historyкоманди. Де я повинен шукати?
Джонатан Хартлі

Параметри історії знаходяться в «man bash», знайдіть розділ «команд вбудованих оболонок», а потім «історію» під цим.
Джонатан Хартлі

Відповіді:


36

Сортування історії

Ця команда працює як sort|uniq, але тримає рядки на місці

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

В основному, попередньо додає до кожного рядка його номер. Після sort|uniq-ing всі рядки відсортовані відповідно до їх початкового порядку (використовуючи поле номера рядка) і поле номера рядка видаляється з рядків.

У цьому рішенні є недолік, що не визначено, який представник класу рівних ліній зробить його на виході, і тому його позиція в кінцевому результаті не визначена. Однак якщо слід вибрати останнього представника, ви можете sortввести другий ключ:

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

Управління .bash_history

Для повторного читання та повернення історії можна використовувати history -aі history -wвідповідно.


6
Версія deco-sort-undecorate , реалізована з інструментами оболонки. Приємно.
ire_and_curses

З sort, -rперемикач завжди змінює порядок сортування. Але це не дасть результату, який ви маєте на увазі. sortвважає два випадки lsоднаковими з результатом, що навіть при зворотному зміні можливий порядок залежить від алгоритму сортування. Але дивіться моє оновлення щодо іншої ідеї.
artistoex

1
Якщо ви не хочете змінювати .bash_history, ви можете помістити в .bashrc: alias history = 'history | сорт -k2 -k 1,1nr | uniq -f 1 | сортувати -n '
Натан

Що знаходиться nlна початку кожного рядка коду? Чи не повинно бути history?
AL

1
@AL nl додає номери рядків. Команда в цілому вирішує загальну проблему: видалення дублікатів при збереженні порядку. Вхід зчитується зі stdin.
artistoex

48

Тому я шукав те саме, що дратував дублікати, і виявив, що якщо я редагую свій ~ / .bash_profile (Mac) за допомогою:

export HISTCONTROL=ignoreboth:erasedups

Він робить саме те, що ви хотіли, він зберігає лише останню інформацію будь-якої команди. ignorebothнасправді так само, як робити, ignorespace:ignoredupsі разом з цим erasedupsвиконує роботу.

Принаймні, на моєму терміналі Mac з bash ця робота ідеальна. Знайшов його тут на askubuntu.com .


10
це має бути правильна відповідь
MitchBroadhead

тестували на Max OS X Yosemite та на Ubuntu 14_04
Рікардо

1
погоджуюся з @MitchBroadhead. це вирішує проблему в самому базі, без зовнішньої роботи. протестували його на ubuntu 17.04 та 16.04 LTS
Георг Юнг

працює і на OpenBSD. Він видаляє лише дупи будь-якої команди, яку вона додає до файлу історії, що для мене добре. Це цікаво впливає на скорочення файлу історії, оскільки я ввожу команди, які раніше існували як дублікати. Тепер я можу скоротити свій файл історії максимум.
Слабкий покажчик

1
Це ігнорує лише повторювані послідовні команди. Якщо ви будете неодноразово чергувати дві задані команди, ваша історія
башів

16

Знайшли це рішення в дикій природі і випробували:

awk '!x[$0]++'

Перший раз, коли видно конкретне значення рядка ($ 0), значення x [$ 0] дорівнює нулю.
Значення нуля перевертається !і стає одиницею.
Заява, що оцінюється до одного, викликає дію за замовчуванням, а саме друк.

Тому перший раз, коли $0видно конкретне , воно друкується.

Кожен наступний раз (повторення) значення x[$0]інкретується,
його заперечне значення дорівнює нулю, а вислів, що оцінює нуль, не друкується.

Щоб зберегти останнє повторне значення, поверніть історію та використовуйте той самий awk:

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

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

Оце Так! Це просто спрацювало. Але це прибирає все, крім першої появи, я здогадуюсь. Перед тим, як запустити це, я змінив упорядкування рядків за допомогою піднесеного тексту. Тепер я поверну її ще раз, щоб отримати чисту історію з останньою появою всіх дублікатів, що залишилися позаду. Дякую.
trss

Перевір мою відповідь!
Алі Шакіба

Приємна чиста і загальна відповідь (не обмежена лише випадком використання історії) без запуску базових підпроцесів ;-)
JepZ

9

Розширення відповіді Клейтона:

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

tacпереверніть файл, переконайтесь, що ви встановили, moreutilsщоб у вас був spongeдоступ, інакше використовуйте тимчасовий файл.


1
Для тих, хто працює на Mac, використовуйте brew install coreutilsта зауважте, що всі утиліти GNU мають попередньо попередження, gщоб уникнути плутанини із вбудованими командами Mac BSD (наприклад, gsed є GNU, тоді як sed - BSD). Тож використовуйте gtac.
tralston

Мені знадобилася історія -c та історія -r, щоб змусити її використовувати історію
drescherjm

4

Вони зберігатимуть останні дублюються рядки:

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

Якщо явно, я правильно розумію, що ви показали тут два (чудові) рішення, а користувачеві потрібно виконати лише одне з них? Або рубіновий, або Баш?
Джонатан Хартлі

3

Це стара публікація, але вічна проблема для користувачів, які хочуть відкрити кілька терміналів і синхронізувати історію між вікнами, але не дублювати.

Моє рішення в .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
  • Параметр histappend додає історію буфера до кінця файла історії ($ HISTFILE)
  • ignoreboth та erasedups запобігають збереженню дублікатів у $ HISTFILE
  • Команда підказок оновлює кеш історії
    • history -n читає всі рядки з $ HISTFILE, які могли виникнути в іншому терміналі з моменту останнього повернення каретки
    • history -w записує оновлений буфер в $ HISTFILE
    • history -c витирає буфер, щоб не виникало дублювання
    • history -r знову читає $ HISTFILE, додаючи до тепері порожнього буфера
  • скрипт awk зберігає перше виникнення кожного рядка, з яким він стикається. tacповертає його назад, а потім повертає назад, щоб його можна було зберегти за допомогою останніх команд, які все ще є останніми в історії
  • rm файл / tmp

Кожного разу, коли ви відкриваєте нову оболонку, історія стирає всі дупи, і кожен раз, коли ви натискаєте Enterклавішу в іншому вікні оболонки / терміналу, вона оновлює цю історію з файлу.



Якщо "ignoreboth та erasedups запобігають збереженню дуепсів", то чому ви також повинні виконувати команду "awk", щоб видалити копії з файлу? Це тому, що "ігнорування та стирання" лише запобігають збереженню послідовних обманів ? Вибачте за педантичність, я просто намагаюся зрозуміти.
Джонатан Хартлі

1
erasedups стирає лише послідовні дублікати. І ви праві, що команда awk дублює команду erasedupes, роблячи її зайвою.
smilefrog

Дякую, що це дає зрозуміти мені, що відбувається.
Джонатан Хартлі

0

Uniqely записувати кожну нову команду складно. Спочатку потрібно додати ~/.profileабо подібне:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Потім потрібно додати до ~/.bash_logout:

history -a
history -w

Чи можете ви допомогти мені зрозуміти, чому під час виходу потрібно додати неписану історію до файлу історії, перш ніж переписати весь файл історії? Ви не можете просто написати весь файл без "додати"?
Джонатан Хартлі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.