Вопрос или проблема
Мне очень нравится использовать control+r
для рекурсивного поиска в истории команд. Я нашел несколько хороших параметров, которые мне нравится использовать с ним:
# игнорировать повторяющиеся команды, игнорировать команды, начинающиеся с пробела
export HISTCONTROL=erasedups:ignorespace
# сохранять последние 5000 записей
export HISTSIZE=5000
# добавлять в историю, вместо перезаписи (удобно для нескольких соединений)
shopt -s histappend
Единственная проблема для меня заключается в том, что erasedups
удаляет только последовательные дубликаты – таким образом, с этой строкой команд:
ls
cd ~
ls
Команда ls
будет записана дважды. Я думал о периодическом запуске с помощью 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
, чтобы проверить и убедиться, что нет активных сессий перед запуском скрипта фильтрации?
Я искал то же самое после того, как надоели дубликаты, и обнаружил, что если отредактировать мой ~/.bash_profile
или ~/.bashrc
, добавив:
export HISTCONTROL=ignoreboth:erasedups
Это делает почти то, что вы хотели (оно удаляет только подряд идущие дубликаты), оно сохраняет только последнюю из любой команды в одной сессии. ignoreboth
фактически аналогичен ignorespace:ignoredups
, и вместе с erasedups
это работает. По крайней мере у меня в терминале Mac с bash это работает отлично. Нашел здесь на askubuntu.com.
Обратите внимание, что ignoredups
не поможет с удалением непоследовательных дубликатов из существующего .bash_history
. Дубликаты все еще будут появляться в файле при использовании shopt -s histappend
.
Сортировка истории
Эта команда работает как sort|uniq
, но сохраняет строки на месте
nl|sort -k 2|uniq -f 1|sort -n|cut -f 2
По сути, добавляет к каждой строке ее номер. После выполнения sort|uniq
все строки снова сортируются в соответствии с их первоначальным порядком (используя поле номера строки), и поле номера строки удаляется из строк.
Этот метод имеет недостаток: неопределенно, какой экземпляр из группы одинаковых строк попадет в результат, и, следовательно, его положение в окончательном выводе также не определено. Однако, если нужно выбрать последний экземпляр, можно выполнить sort
входных данных по вторичному ключу:
nl|sort -k2 -k 1,1nr|uniq -f1|sort -n|cut -f2
Управление .bash_history
Для повторного считывания и записи истории можно использовать history -a
и history -w
соответственно.
Нашел это решение и протестировал:
awk '!x[$0]++'
В первый раз, когда встречается определенная строка ($0), значение x[$0] равно нулю.
Обращение нуля с помощью !
становится единицей.
Утверждение, оцениваемое в единицу, приводит к выполнению действия по умолчанию, то есть печати.
Таким образом, в первый раз, когда встречается конкретный $0
, он печатается.
Каждый следующий раз (повторы) значение x[$0]
уже увеличено,
и его обратное значение равно нулю, соответственно, утверждение оценивается в ноль и не печатается.
Чтобы сохранить последнее повторенное значение, разверните историю и используйте тот же awk:
awk '!x[$0]++' ~/.bash_history # сохранить первое повторенное значение.
tac ~/.bash_history | awk '!x[$0]++' | tac # сохранить последнее.
Расширяю ответ Клейтона:
tac $HISTFILE | awk '!x[$0]++' | tac | sponge $HISTFILE
tac
переворачивает файл.
Убедитесь, что у вас установлен moreutils
, чтобы у вас был доступен sponge
, в противном случае используйте временный файл. В отличие от перенаправления оболочки, sponge
впитывает весь ввод перед записью выходного файла. Это позволяет создавать конвейеры, которые читают и записывают в один и тот же файл.
Это старая тема, но вечная проблема для пользователей, которые хотят иметь несколько открытых терминалов и синхронизированную историю между окнами, но без дубликатов.
Мое решение в .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
записывает обновленный буфер в $HISTFILEhistory -c
очищает буфер, чтобы не возникало дублированияhistory -r
повторно считывает $HISTFILE, добавляя данные в новый чистый буфер
- скрипт awk сохраняет первое вхождение каждой строки, которую он встречает.
tac
переворачивает файл, а затем переворачивает его обратно, чтобы он мог быть сохранен с самыми последними командами в истории - очистка временного файла
Каждый раз, когда вы открываете новый шелл, все дубликаты истории удаляются,
и каждый раз, когда вы нажимаете клавишу Enter
в другом окне/окне терминала,
она обновляет эту историю из файла.
Эти методы сохранят последние дублированные строки:
ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history
Почти все ответы в этой теме не учитывают файлы истории с временными метками или многострочные записи истории.
Мне нужна была возможность объединить историю памяти и диска, когда моя сессия оболочки завершает работу (из нескольких терминалов) или просто объединить историю из одного терминала в другой.
Я долго искал, но ничего подходящего так и не нашел. Поэтому я в конце концов сделал решение своими руками…
Вот мое решение… Объединить хранящуюся на диске “.bash_history” с историей в памяти оболочки. Сохранение порядка временных меток и порядка команд в пределах этих временных меток.
При необходимости удаляя повторяющиеся команды (даже если они многострочные), и/или удаляя (очищая) простые и/или чувствительные команды в соответствии с определенными регулярными выражениями perl. Настройте как вам удобно!
Вот результат… https://antofthy.gitlab.io/software/history_merge.bash.txt
Вы можете настроить его, как хотите, или сделать его функцией bash. Или же настроить команды, которые очищаются из истории…
Я запускаю это по требованию, используя алиас (например, ‘hm’ для слияния истории) или когда оболочка выходит из системы (из “.bash_logout”), если я не отключил историю оболочки (расстоянием “$HISTFILE”, используя ‘hd’ алиас)
Наслаждайтесь.
У меня включены временные метки, поэтому большинство решений для изменения файлов не работают. У меня также есть каталог для файлов истории, чтобы они были специфичны для хостов. Я использовал некоторые из решений, найденных здесь, чтобы удалить дубликаты и так далее из истории перед записью обратно в файл истории, но иногда у меня открыто несколько оболочек на одном и том же хосте, что затем оставляет эти дубликаты. Мое решение для очистки этого беспорядка время от времени – создать исполняемый файл с следующим содержимым:
#!/bin/sh
for file in ~/.bash_history/*
do
tac "$file" | awk '!visited[$0]++' | tac | sed 'N;/^#.*\n#.*/!P;D' > tempfile;
mv tempfile "$file"
done
Сохраните его и выполните.
По сути: переворачивает файл и использует awk для очистки дубликатов, сохраняя последний, снова переворачивает, затем использует sed для удаления последовательных временных меток, оставляя последнюю. Сохраняет файл во временный файл, перемещает временный файл в файл истории. Мой каталог истории уменьшился с 109M до 1008K 🙂
Я написал небольшую программу, которая позволяет чистить вашу историю bash/shell, также ретроактивно и сохраняя ее порядок:
https://gitlab.com/vn971/shell-history-cleaner
USAGE:
shell-history-cleaner [OPTIONS] <TARGET_FILE>
ARGS:
<TARGET_FILE>
Целевой файл для очистки. Вы можете использовать "$HISTFILE", чтобы очистить историю оболочки.
OPTIONS:
-d, --dedup
Удаление дубликатов строк, чтобы оставить только одну последнюю встречу каждого дубликата. В отличие от встроенной функции удаления дубликатов bash, это также работает, если дубликаты не следуют друг за другом.
-r, --remove <REMOVE>
Строки для удаления. Например, 'yt-dlp.*' удалит строки, начинающиеся с 'yt-dlp'.
Может быть указано несколько раз.
Шаблоны - это регулярные выражения, предполагающие полное совпадение строки, как определено здесь: https://docs.rs/regex/latest/regex/#syntax
Другой пример из реальной жизни:
'(ps aux.*|git checkout .*|git branch .*| .*|yt-dlp .*|chmod .*|echo .*|man .*)'
-h, --help
Вывести справочную информацию
Чтобы уникально записывать каждую новую команду, нужно добавить в
~/.profile
или аналогичный файл:
HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'
Затем вам нужно добавить в ~/.bash_logout
:
history -a
history -w
Расширение ответа Али.
Файл .bash_history
может содержать или не содержать временные метки, и временные метки могут быть смешаны с записями без них, если HISTTIMEFORMAT
был включен или отключен. Этот скрипт сохраняет временные метки .bash_history
, где они присутствуют, и удаляет дубликаты записей, оставляя только последние:
tac $HISTFILE | awk '/^#/{if(l){print l;print;l=""}next} l{print l;l=""} !seen[$0]++{l=$0}' | tac
который затем можно sponge
записать обратно в $HISTFILE
.
Я использую этот код в .bash_profile
:
remove_history_duplicates () {
local i login_flag
[ -z "$(history 1)" ] && login_flag=1 && history -r
for i in $(history | awk '
$1 ~ "[0-9]+" {
id = $1
$1=""
if (uniq[$0]) {n++; print uniq[$0]}
uniq[$0] = id
}
END {if (n) print "найдено",n,"дубликатов" > "/dev/stderr"}
' | sort -nr); do history -d $i; done
if [[ -n $login_flag ]]; then
[[ -n $i ]] && history -w && echo "история записана"
history -c
fi
}
remove_history_duplicates
Он не редактирует файл истории напрямую, а с помощью команд bash. Последняя повторяющаяся команда сохраняется, все предыдущие удаляются.
Если вы используете временные метки в истории (например, HISTTIMEFORMAT='%F %T '
), замените строку $1=""
строкой $1=""; $2=""; $3=""
в коде awk.
Когда функция запускается при входе в систему, она автоматически загружает историю из файла, редактирует ее, затем очищает, поскольку bash загрузит ее после этого.
Используя Perl
Сохранение первого появления дубликата (сравните с awk
):
~$ perl -ne 'print unless $hash{$_}++' ~/.bash_history > outfile
Сохранение последнего появления дубликата (сравните с awk
):
~$ tac ~/.bash_history | perl -ne 'print unless $hash{$_}++' | tac > outfile
Используя Raku (ранее известный как Perl_6)
Сохранение первого появления дубликата (сравните с awk
):
~$ raku -ne 'state %hash; .put unless %hash{$_}++' ~/.bash_history > outfile
Сохранение последнего появления дубликата (сравните с awk
):
~$ tac ~/.bash_history | raku -ne 'state %hash; .put unless %hash{$_}++' | tac > outfile
Используя Raku (ранее известный как Perl_6)
Сохранение первого появления дубликата (сравните с ruby
):
~$ raku -e '.put for lines.unique;' ~/.bash_history > outfile
Сохранение последнего появления дубликата (сравните с ruby
):
~$ tac ~/.bash_history | raku -e '.put for lines.reverse.unique.reverse;' | tac > outfile
https://stackoverflow.com/q/1444406/7270649
https://stackoverflow.com/a/32513573/7270649 https://unix.stackexchange.com/a/11941/227738
https://perldoc.perl.org
https://docs.raku.org
Я новичок в bash и не понимаю этих ответов, так как они плохо документированы. Вот как я решил эту проблему:
Проблема в том, что command-1
command-2
command-1
command-2
(… 25 раз больше …) the-command-i-actually-want
.
Чтобы решить это, сначала я создаю скрипт Python, который очищает .bash_history
:
# cleanup_bash_history.py
from pathlib import Path
hispath = (Path.home() / ".bash_history").resolve(strict=True)
history: dict[str, int] = {}
with hispath.open("r", encoding="utf-8") as hisfile:
for linenum, line in enumerate(hisfile):
line = line.strip()
history[line] = linenum
with hispath.open("w", encoding="utf-8") as hisfile:
for _, line in sorted(
(linenum, line) for line, linenum in history.items()
):
hisfile.write(f"{line}\n")
Этот скрипт предполагает, что .bash_history
представляет собой просто список команд без временных меток.
Сначала все строки из .bash_history
читаются в дикт, где строка является ключом, а номер строки значением. Таким образом, в конце, у меня есть наибольший номер строки для строки и ни одного дубликата строк.
Затем я создаю генератор из этого дикта, (linenum, line) for line, linenum in history.items()
, который ведет себя как список кортежей (<linenum>, <line>)
. Я передаю это в sorted()
, чтобы кортежи были отсортированы по номеру строки.
Затем я записываю строку из каждого кортежа в этом порядке обратно в .bash_history
.
Далее, в .bashrc
в конце, я добавляю:
PROMPT_COMMAND="history -a; python ~/Sys/scripts/cleanup_bash_history.py; history -c; history -r; $PROMPT_COMMAND"
Я скопировал это из этого ответа, это будет работать даже если открыто несколько окон bash, и общая история будет обновляться каждый раз, когда выполняется команда.
И это все, дубликатов больше нет.
Дополнение: если у вас возникают проблемы с элементами истории, записываемыми VS Code, удалите их вручную в скрипте Python, либо оберните команду bash в следующую конструкцию
if [ "$TERM_PROGRAM" != "vscode" ]; then
...
fi
Другие способы сделать это здесь также:
https://stackoverflow.com/questions/338285/prevent-duplicates-from-being-saved-in-bash-history/7449399#7449399
Отличный ответ. Если вы предпочитаете сохранить хронологический
порядок (вместо порядка ввода) для своих команд, измените функциюdedup()
,
заменивawk '! x[$0]++' $@
наtac $@ | awk '! x[$0]++' | tac
–
trusktr
Мы можем устранить повторяющиеся строки, не сортируя файл, используя команду awk в следующем синтаксисе.
awk '!seen[$0]++' source.txt > target.txt
https://superuser.com/questions/722461/how-can-you-remove-duplicates-from-bash-history
Также
в nano используя регулярные выражения:
Нажмите
Ctrl + \
Введите строку поискаввод
Введите
строку заменыввод
НажмитеA
для замены всех вхождений
в vim
:sort u
Если некоторые из предложений, включая приведенные выше, не работают сразу. После выполнения кода я делаю следующее:
history -c
чтобы сначала очистить историю, затем восстанавливаю версию без дубликатов поверх нее:
cp temp.txt ~/.bash_history
Ответ или решение
Для решения проблемы с дублирующимися записями в файле .bash_history
необходимо использовать метод, который позволит удалять дубликаты, сохраняя при этом порядок команд. Важно отметить, что переменная HISTCONTROL=erasedups
удаляет только последовательные дубликаты, то есть, если команда присутствует в истории несколько раз на разных строках, они сохранятся. Этот аспект нас, очевидно, не устраивает. Попробуем решить проблему с помощью нескольких инструментальных команд и скриптов.
Теория
В Unix-подобных системах файл .bash_history
, как правило, хранит историю команд, которые пользователь вводил в командной оболочке bash. Проблема, описанная в запросе, определяется необходимостью удаления всех повторяющихся строк из этого файла, оставляя лишь их последнее вхождение, и при этом сохраняя порядок следования команд.
Стандартные Unix-утилиты, такие как sort
и uniq
, не подходят для этого сценария, поскольку они требуют предварительной сортировки, что приводит к потере изначального порядка команд. Однако, мы можем использовать инструменты, такие как awk
и tac
, которые позволяют работать с файлами построчно и обрабатывать их без нарушения порядка.
Пример
Рассмотрим пример на базе awk и других утилит. Представьте, что у вас есть следующая история команд:
ls
cd ~
ls
mkdir new_folder
cd new_folder
ls
Для удаления повторяющихся команд можно использовать следующую команду:
tac ~/.bash_history | awk '!seen[$0]++' | tac > ~/.bash_history.temp && mv ~/.bash_history.temp ~/.bash_history
Здесь tac
реверсирует файл, чтобы сохранить последнее вхождение команды, awk '!seen[$0]++'
удаляет дублирующиеся строки, и снова tac
возвращает изначальный порядок. Это позволяет сохранять историю так, как она вводилась, но с убранными дубликатами.
Применение
Чтобы внедрить это решение, можно создать скрипт, который автоматически будет вызывать эту команду в нужные моменты. Например, можно добавить его вызов в .bash_logout
, чтобы выполнение происходило при завершении сеанса, или настроить cron-задачу для периодической очистки файла от дублей.
# Добавьте следующую строку в ваш .bash_logout
tac ~/.bash_history | awk '!seen[$0]++' | tac > ~/.bash_history.temp && mv ~/.bash_history.temp ~/.bash_history
Кроме того, чтобы избежать воздействия на историю из других командных сеансов, вы можете использовать PROMPT_COMMAND
в .bashrc
:
export PROMPT_COMMAND="history -a; history -w; tac $HISTFILE | awk '!seen[$0]++' | tac > $HISTFILE"
Это позволит перед сохранением команды в файл истории применять фильтрацию дублей.
Экстра кредит:
Перезаписывание файла .bash_history
может быть потенциально рискованным, так как оно может возникнуть в процессе работы нескольких открытых терминалов. Однако, использование команды sponge
из пакета moreutils
поможет избежать проблем, связанных с несвоевременной обработкой входных данных:
tac ~/.bash_history | awk '!seen[$0]++' | tac | sponge ~/.bash_history
Таким образом, мы собираем все данные перед записью, избегая конфликтов.
Заключение
Описание такого подхода позволит вам элегантно управлять историей команд в bash, чистить её от дублей, не теряя при этом важного для продуктивности порядка команд. Теперь в вашей истории останутся только уникальные команды с учетом их последнего появления, что упростит процесс поиска команд с помощью Ctrl+R
и позволит быстрее находить нужную информацию.