Как я могу удалить дубликаты в моем .bash_history, сохраняя порядок?

61

Я действительно наслаждаюсь 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
Я только что узнал что-то новое в 1-м предложении вашего вопроса. Хороший трюк!
Рикардо
Я не могу найти справочную страницу по всем параметрам 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соответственно.

artistoex
источник
6
Версия decorate-sort-undecorate , реализованная с помощью инструментов оболочки. Приятно.
ire_and_curses
При sortэтом -rпереключатель всегда меняет порядок сортировки. Но это не даст желаемого результата. sortрасценивает два вхождения lsкак идентичные с результатом, который, даже когда полностью изменен, возможный порядок зависит от алгоритма сортировки. Но посмотрите мое обновление для другой идеи.
artistoex
1
В случае, если вы не хотите изменять .bash_history, вы можете поместить в .bashrc следующее: alias history = 'history | сортировать -k2 -k 1,1nr | uniq -f 1 | sort -n '
Натан
Что находится nlв начале каждой строки кода? Не должно ли это быть history?
AL
1
@AL nl добавляет номера строк. Команда в целом решает общую проблему: удаление дубликатов при сохранении порядка. Ввод читается из стандартного ввода.
artistoex
49

Поэтому я искал точно такую ​​же вещь после раздражения от дубликатов и обнаружил, что если я отредактирую свой ~ / .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. это решает проблему в самом bash, без внешней работы cron. проверил его на Ubuntu 17.04 и 16.04 LTS
Георг Юнг
работает на OpenBSD тоже. Он только удаляет дубликаты любой команды, которую он добавляет в файл истории, что хорошо для меня. Интересный эффект заключается в сокращении файла истории, когда я ввожу команды, которые существовали как дубликаты ранее. Теперь я могу сделать мой файл истории максимально коротким.
WeakPointer
2
Это игнорирует только повторяющиеся последовательные команды. Если вы неоднократно
переключаетесь
16

Нашел это решение в дикой природе и протестировал:

awk '!x[$0]++'

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

Поэтому, когда в первый раз $0виден конкретный , он печатается.

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

Чтобы сохранить последнее повторенное значение, измените историю и используйте тот же awk:

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

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.
Клейтон Стэнли
источник
Вау! Это просто сработало. Но это удаляет все, кроме первого случая, я думаю. Я перевернул порядок строк, используя Sublime Text, прежде чем запускать это. Теперь я переверну это снова, чтобы получить чистую историю с последним появлением всех дубликатов. Спасибо.
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.
Тралстон
Мне нужны были история -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
LRI
источник
Если говорить прямо, правильно ли я понимаю, что вы показали два (великолепных) решения здесь, и пользователю нужно выполнить только одно из них? Или рубиновый или Баш?
Джонатан Хартли
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
  • Команда prompt обновляет кэш истории
    • history -n читает все строки из $ HISTFILE, которые могли возникнуть в другом терминале с момента последнего возврата каретки
    • history -w записывает обновленный буфер в $ HISTFILE
    • history -c стирает буфер, чтобы не возникало дублирования
    • history -r перечитывает $ HISTFILE, добавляя пустой буфер
  • Скрипт awk хранит первое вхождение каждой встреченной строки. tacпереворачивает его, а затем переворачивает обратно, чтобы его можно было сохранить с помощью самых последних команд, еще самых последних в истории
  • rm файл / tmp

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

smilingfrog
источник
Вот отличное объяснение этому в комментариях
Smilingfrog
Если «ignoreboth и erasedups не позволяют сохранять дубликаты», то зачем вам также нужно использовать команду «awk» для удаления дубликатов из файла? Это потому, что «ignoreboth and erasedups» только предотвращают сохранение последовательных дублировок? Извините за педантизм, я просто пытаюсь понять.
Джонатан Хартли
1
стирание только удаляет последовательные дубликаты. И вы правы, что команда awk дублирует команду erasedupes, делая ее лишней.
Smilingfrog
Спасибо, это проясняет мне, что происходит.
Джонатан Хартли
0

Для уникальной записи каждой новой команды сложно. Сначала вы должны добавить ~/.profileили аналогичный:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Затем вам нужно добавить в ~/.bash_logout:

history -a
history -w
Стивен Пенни
источник
Можете ли вы помочь мне понять, почему при выходе из системы вам необходимо добавить неписанную историю в файл истории, а затем переписать весь файл истории? Разве вы не можете просто написать весь файл без «добавления»?
Джонатан Хартли