Como posso remover duplicatas no meu .bash_history, preservando a ordem?

61

Eu realmente gosto de usar control+rpara pesquisar recursivamente meu histórico de comandos. Encontrei algumas boas opções que gosto de usar:

# 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

O único problema para mim é que erasedupsapenas apaga duplicatas sequenciais - de modo que, com esta sequência de comandos:

ls
cd ~
ls

O lscomando será realmente gravado duas vezes. Eu pensei em executar periodicamente w / cron:

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

Isso conseguiria remover as duplicatas, mas infelizmente o pedido não seria preservado. Se eu não fizer sorto arquivo primeiro, não acredito que uniqpossa funcionar corretamente.

Como posso remover duplicatas no meu .bash_history, preservando a ordem?

Crédito extra:

Há algum problema com a substituição do .bash_historyarquivo por um script? Por exemplo, se você remover um arquivo de log do apache, acho que você precisará enviar um sinal de nohup / reset killpara que ele libere sua conexão com o arquivo. Se esse for o caso do .bash_historyarquivo, talvez eu possa usar de alguma forma pspara verificar e garantir que não haja sessões conectadas antes da execução do script de filtragem?

cwd
fonte
3
Tente em ignoredupsvez de erasedupspor um tempo e veja como isso funciona para você.
jw013
11
Eu não acho que o bash mantém um identificador de arquivo aberto no arquivo de histórico - ele lê / grava quando necessário, por isso deve (note - deveria - eu não testei) ser seguro substituí-lo de outro lugar.
D_Bye
11
Acabei de aprender algo novo na 1ª frase da sua pergunta. Bom truque!
Ricardo
Não estou conseguindo encontrar a página do manual para todas as opções do historycomando. Onde eu deveria estar olhando?
Jonathan Hartley
As opções de histórico estão em 'man bash', procure a seção 'comandos internos do shell' e, em seguida, 'histórico' abaixo disso.
Jonathan Hartley

Respostas:

36

Classificando o histórico

Este comando funciona como sort|uniq, mas mantém as linhas no lugar

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

Basicamente, acrescenta a cada linha seu número. Após a sort|uniqintrodução, todas as linhas são ordenadas de volta de acordo com a ordem original (usando o campo número da linha) e o campo número da linha é removido das linhas.

Essa solução tem a falha de que é indefinido qual representante de uma classe de linhas iguais o fará na saída e, portanto, sua posição na saída final é indefinida. No entanto, se o representante mais recente for escolhido, você poderá sortinserir uma segunda tecla:

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

Gerenciando .bash_history

Para reler e escrever de volta o histórico, você pode usar history -ae history -wrespectivamente.

artistoex
fonte
6
Uma versão do decorate-sort-undecorate , implementada com ferramentas de shell. Agradável.
ire_and_curses
Com sort, a -ropção sempre reverte a ordem de classificação. Mas isso não produzirá o resultado que você tem em mente. sortconsidera as duas ocorrências lsidênticas ao resultado que, mesmo quando revertido, a ordem final depende do algoritmo de classificação. Mas veja minha atualização para outra ideia.
Artistoex
11
Caso não queira modificar .bash_history, você pode colocar o seguinte em .bashrc: alias history = 'history | ordenar -k2 -k 1,1nr | uniq -f 1 | tipo -n'
Nathan
O que está nlno início de cada linha de código? Não deveria ser history?
AL
11
@AL nl adiciona números de linha. O comando como um todo resolve o problema geral: remover duplicatas enquanto preserva a ordem. A entrada é lida a partir de stdin.
Artistoex
49

Então, eu estava procurando exatamente a mesma coisa depois de me incomodar com duplicatas, e descobri que se eu editar meu ~ / .bash_profile (Mac) com:

export HISTCONTROL=ignoreboth:erasedups

Ele faz exatamente o que você queria, apenas mantém o mais recente de qualquer comando. ignorebothé realmente como fazer ignorespace:ignoredupse, junto com erasedupso trabalho, é feito.

Pelo menos no meu terminal Mac com bash, este trabalho é perfeito. Encontrei aqui no askubuntu.com .

sprite
fonte
10
esta deve ser a resposta correta
MitchBroadhead
testado no Max OS X Yosemite e no Ubuntu 14_04
Ricardo
11
concorde com @MitchBroadhead. isso resolve o problema no próprio bash, sem cron-job externo. testei no ubuntu 17.04 e 16.04 LTS
Georg Jung
também funciona no OpenBSD. Ele remove apenas os dups de qualquer comando anexado ao arquivo de histórico, o que é bom para mim. Ele tem o efeito interessante de encurtar o arquivo de histórico quando eu insiro comandos que já existiam como duplicados antes. Agora posso diminuir meu arquivo de histórico.
WeakPointer 19/01
11
Isso ignora apenas comandos duplicados e consecutivos. Se você alternar repetidamente entre dois comandos dados, seu histórico de festa vai encher-se com duplicatas
Dylanthepiguy
16

Encontrou esta solução na natureza e testada:

awk '!x[$0]++'

A primeira vez que um valor específico de uma linha ($ 0) é visto, o valor de x [$ 0] é zero.
O valor de zero é invertido !e se torna um.
Uma declaração que é avaliada como uma causa a ação padrão, que é impressa.

Portanto, a primeira vez que um específico $0é visto, ele é impresso.

Toda vez que a repetição x[$0]for incrementada,
o valor negado será zero e uma declaração avaliada como zero não será impressa.

Para manter o último valor repetido, inverta o histórico e use o mesmo awk:

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

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.
Clayton Stanley
fonte
Uau! Isso apenas funcionou. Mas remove tudo, exceto a primeira ocorrência, eu acho. Eu havia revertido a ordem das linhas usando o Texto Sublime antes de executar isso. Agora vou revertê-lo novamente para obter um histórico limpo, com apenas a última ocorrência de todas as duplicatas deixadas para trás. Obrigado.
trss 27/08/14
Confira minha resposta!
Ali Shakiba
Resposta limpo e em geral agradável (não se restringe à história de caso de uso) sem lançar um bazilion sub-processos ;-)
JepZ
9

Estendendo a resposta de Clayton:

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

tacinverta o arquivo, verifique se você instalou moreutilspara ter spongedisponibilidade, caso contrário, use um arquivo temporário.

Ali Shakiba
fonte
11
Para aqueles no Mac, use brew install coreutilse observe que todos os utilitários do GNU têm um ganexo para evitar confusão com os comandos do BSD para o Mac (por exemplo, gsed é GNU enquanto sed é BSD). Então use gtac.
tralston
Eu precisava do history -c e history -r para usá-lo
drescherjm
4

Eles manteriam as últimas linhas duplicadas:

ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history
Lri
fonte
Para ser explícito, entendo direito que você mostrou duas soluções (esplêndidas) aqui e um usuário precisa executar apenas uma delas? Ou o rubi, ou o Bash?
Jonathan Hartley
3

Esta é uma publicação antiga, mas um problema permanente para usuários que desejam ter vários terminais abertos e ter o histórico sincronizado entre janelas, mas não duplicado.

Minha solução em .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
  • A opção histappend adiciona o histórico do buffer ao final do arquivo de histórico ($ HISTFILE)
  • ignoreboth e apagados evitam que entradas duplicadas sejam salvas no diretório $ HISTFILE
  • O comando prompt atualiza o cache do histórico
    • history -n lê todas as linhas de $ HISTFILE que podem ter ocorrido em um terminal diferente desde o último retorno de carro
    • history -w grava o buffer atualizado em $ HISTFILE
    • history -c limpa o buffer para que não ocorra duplicação
    • history -r relê o $ HISTFILE, anexando ao buffer agora em branco
  • o script awk armazena a primeira ocorrência de cada linha que encontrar. tacreverte-o e depois reverte-o para que possa ser salvo com os comandos mais recentes ainda mais recentes da história
  • rm o arquivo / tmp

Toda vez que você abre um novo shell, todo o histórico é apagado e toda vez que você pressiona a Entertecla em uma janela diferente do terminal / shell, ele atualiza esse histórico a partir do arquivo.

sorrindo
fonte
Se "ignoreboth e apagados backups impedem que os dupes sejam salvos", por que você também precisa executar o comando "awk" para remover dupes do arquivo? É porque "ignorar os dois e apagar as cópias" apenas impede que os dupes consecutivos sejam salvos? Desculpe ser pedante, só estou tentando entender.
Jonathan Hartley
11
apaga apenas apaga duplicatas consecutivas. E você está certo de que o comando awk duplica o comando erasedupes, tornando-o supérfluo.
smilingfrog
Obrigado, isso deixa claro para mim o que está acontecendo.
Jonathan Hartley
0

Registrar uniqely todos os novos comandos é complicado. Primeiro você precisa adicionar ~/.profileou similar:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Então você precisa adicionar a ~/.bash_logout:

history -a
history -w
Steven Penny
fonte
Você pode me ajudar a entender por que, no logout, você precisa anexar o histórico não escrito ao arquivo de histórico antes de reescrever o arquivo de histórico inteiro? Você não pode simplesmente escrever o arquivo inteiro sem o 'acréscimo'?
Jonathan Hartley