Preserva o histórico bash em várias janelas de terminal

475

Eu sempre tenho mais de um terminal aberto. Em qualquer lugar de dois a dez, fazendo vários bits e bobs. Agora digamos que eu reinicie e abra outro conjunto de terminais. Alguns lembram certas coisas, alguns esquecem.

Eu quero uma história que:

  • lembra de tudo de cada terminal
  • É instantaneamente acessível de todos os terminais (por exemplo, se eu ls em um, alternar para outro terminal já em execução e, em seguida, pressionar para cima, ls aparece)
  • Não se esqueça do comando se houver espaços na frente do comando.

Algo que eu possa fazer para fazer o bash funcionar mais assim?

    
por Oli 26.08.2010 / 15:04

20 respostas

296

Adicione o seguinte a ~ / .bashrc

# Avoid duplicates
export HISTCONTROL=ignoredups:erasedups  
# When the shell exits, append to the history file instead of overwriting it
shopt -s histappend

# After each command, append to the history file and reread it
export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"
    
por 26.08.2010 / 16:37
230

Então, essa é toda a minha coisa .bashrc relacionada ao histórico:

export HISTCONTROL=ignoredups:erasedups  # no duplicate entries
export HISTSIZE=100000                   # big big history
export HISTFILESIZE=100000               # big big history
shopt -s histappend                      # append to history, don't overwrite it

# Save and reload the history after each command finishes
export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"

Testado com o bash 3.2.17 no Mac OS X 10.5, bash 4.1.7 no 10.6.

    
por 19.09.2008 / 19:49
111

Aqui está minha tentativa de compartilhar o histórico da sessão do Bash. Isso permitirá o compartilhamento de histórico entre as sessões do bash de forma que o contador de histórico não seja misturado e a expansão do histórico como !number funcione (com algumas restrições).

Usando o Bash versão 4.1.5 no Ubuntu 10.04 LTS (Lucid Lynx).

HISTSIZE=9000
HISTFILESIZE=$HISTSIZE
HISTCONTROL=ignorespace:ignoredups

_bash_history_sync() {
  builtin history -a         #1
  HISTFILESIZE=$HISTSIZE     #2
  builtin history -c         #3
  builtin history -r         #4
}

history() {                  #5
  _bash_history_sync
  builtin history "$@"
}

PROMPT_COMMAND=_bash_history_sync

Explicação:

  1. Anexe a linha que acabou de inserir ao $HISTFILE (o padrão é .bash_history ). Isso fará com que $HISTFILE cresça em uma linha.

  2. Configurar a variável especial $HISTFILESIZE para algum valor fará com que o Bash trunque $HISTFILE para não mais do que $HISTFILESIZE linhas, removendo as entradas mais antigas.

  3. Limpe o histórico da sessão em execução. Isso reduzirá o contador de histórico pela quantidade de $HISTSIZE .

  4. Leia o conteúdo de $HISTFILE e insira-os no histórico atual da sessão em execução. Isso aumentará o contador de histórico pela quantidade de linhas em $HISTFILE . Observe que a contagem de linhas de $HISTFILE não é necessariamente $HISTFILESIZE .

  5. A função history() substitui o histórico integrado para garantir que o histórico seja sincronizado antes de ser exibido. Isso é necessário para a expansão do histórico por número (mais sobre isso depois).

Mais explicações:

  • A etapa 1 garante que o comando da sessão em execução atual seja gravado no arquivo de histórico global.

  • A etapa 4 garante que os comandos das outras sessões sejam lidos no histórico da sessão atual.

  • Como o passo 4 vai aumentar o contador de histórico, precisamos reduzir o contador de alguma forma. Isso é feito na etapa 3.

  • No passo 3, o contador de histórico é reduzido em $HISTSIZE . Na etapa 4, o contador de histórico é aumentado pelo número de linhas em $HISTFILE . Na etapa 2, garantimos que a contagem de linhas de $HISTFILE seja exatamente $HISTSIZE (isso significa que $HISTFILESIZE deve ser igual a $HISTSIZE ).

Sobre as restrições da expansão do histórico:

Ao usar a expansão do histórico por número, você deve sempre procurar o número imediatamente antes de usá-lo. Isso significa que não é exibido nenhum aviso bash entre procurar o número e usá-lo. Isso geralmente significa não entrar nem ctrl + c.

Geralmente, uma vez que você tenha mais de uma sessão de Bash, não há garantia de que uma expansão de histórico por número manterá seu valor entre duas exibições de prompt do Bash. Porque quando PROMPT_COMMAND é executado, o histórico de todas as outras sessões de Bash é integrado no histórico da sessão atual. Se qualquer outra sessão bash tiver um novo comando, os números do histórico da sessão atual serão diferentes.

Eu acho essa restrição razoável. Eu tenho que procurar o número todas as vezes, porque não consigo me lembrar de números de histórico arbitrários.

Normalmente eu uso a expansão do histórico por número como este

$ history | grep something #note number
$ !number

Eu recomendo usar as seguintes opções de Bash.

## reedit a history substitution line if it failed
shopt -s histreedit
## edit a recalled history line before executing
shopt -s histverify

Insetos estranhos:

A execução do comando history canalizado para qualquer coisa resultará na listagem desse comando no histórico duas vezes. Por exemplo:

$ history | head
$ history | tail
$ history | grep foo
$ history | true
$ history | false

Todos serão listados no histórico duas vezes. Não faço ideia do porquê.

Idéias para melhorias:

  • Modifique a função _bash_history_sync() para que não seja executada toda vez. Por exemplo, ele não deve ser executado após um CTRL+C no prompt. Costumo usar CTRL+C para descartar uma linha de comando longa quando decido que não quero executar essa linha. Às vezes eu tenho que usar CTRL+C para parar um script de conclusão do Bash.

  • Os comandos da sessão atual devem sempre ser os mais recentes no histórico da sessão atual. Isso também terá o efeito colateral de que um dado número de histórico mantém seu valor para entradas de histórico desta sessão.

por 16.06.2010 / 18:11
39

Não conheço nenhuma maneira de usar bash . Mas é um dos recursos mais populares de zsh .
Pessoalmente eu prefiro zsh over bash , então eu recomendo tentar.

Aqui está a parte do meu .zshrc que lida com o histórico:

SAVEHIST=10000 # Number of entries
HISTSIZE=10000
HISTFILE=~/.zsh/history # File
setopt APPEND_HISTORY # Don't erase history
setopt EXTENDED_HISTORY # Add additional data to history like timestamp
setopt INC_APPEND_HISTORY # Add immediately
setopt HIST_FIND_NO_DUPS # Don't show duplicates in search
setopt HIST_IGNORE_SPACE # Don't preserve spaces. You may want to turn it off
setopt NO_HIST_BEEP # Don't beep
setopt SHARE_HISTORY # Share history between session/terminals
    
por 26.08.2010 / 15:20
15

Para fazer isso, você precisará adicionar duas linhas ao seu ~/.bashrc :

shopt -s histappend
PROMPT_COMMAND="history -a;history -c;history -r;$PROMPT_COMMAND"

De man bash :

If the histappend shell option is enabled (see the description of shopt under SHELL BUILTIN COMMANDS below), the lines are appended to the history file, otherwise the history file is over-written.

    
por 25.11.2011 / 16:46
9

Você pode editar o seu prompt BASH para executar o "histórico -a" e o "histórico -r" que Muerr sugeriu:

savePS1=$PS1

(no caso de você estragar algo, o que é quase garantido)

PS1=$savePS1'history -a;history -r'

(observe que são back-ticks; eles executam o histórico -a e history -r em todos os avisos. Como eles não exibem nenhum texto, seu prompt não será alterado.

Uma vez que você tenha sua variável PS1 configurada do jeito que você deseja, configure-a permanentemente no seu arquivo ~ / .bashrc.

Se você quiser voltar ao seu prompt original durante o teste, faça:

PS1=$savePS1

Eu fiz testes básicos para garantir que funciona, mas não posso falar com nenhum efeito colateral de executar history -a;history -r em cada prompt.

    
por 19.09.2008 / 21:38
8

Se você precisar de uma solução de sincronização de histórico bash ou zsh que também solucione o problema abaixo, consulte-a em link

O problema é o seguinte: Eu tenho duas janelas de shell A e B. Na janela de shell A, eu corro sleep 9999 e (sem esperar que o sono termine) na janela de shell B, eu quero poder veja sleep 9999 no histórico bash.

A razão pela qual a maioria das outras soluções aqui não resolverá esse problema é que elas estão gravando suas alterações de histórico no arquivo de histórico usando PROMPT_COMMAND ou PS1 , sendo que ambas executam tarde demais, somente após a% O comandosleep 9999 terminou.

    
por 25.03.2011 / 18:40
6

Você pode usar history -a para anexar o histórico da sessão atual ao histfile e, em seguida, usar history -r nos outros terminais para ler o histfile.

    
por 19.09.2008 / 19:38
6

Aqui está uma alternativa que eu uso. É complicado, mas aborda o problema que @axel_c mencionou onde às vezes você pode querer ter uma instância de histórico separada em cada terminal (um para make, um para monitoramento, um para vim, etc).

Mantenho um arquivo de histórico anexado separado que atualizo constantemente. Eu tenho o seguinte mapeado para uma tecla de atalho:

history | grep -v history >> ~/master_history.txt

Isso anexa todo o histórico do terminal atual a um arquivo chamado master_history.txt em seu diretório inicial.

Eu também tenho uma tecla de atalho separada para pesquisar no arquivo de histórico principal:

cat /home/toby/master_history.txt | grep -i

eu uso cat | grep porque deixa o cursor no final para entrar na minha regex. Uma maneira menos feia de fazer isso seria adicionar alguns scripts ao seu caminho para realizar essas tarefas, mas as teclas de atalho funcionam para os meus propósitos. Eu também periodicamente retirarei o histórico de outros hosts em que trabalhei e adicionarei esse histórico ao meu arquivo master_history.txt.

É sempre bom poder pesquisar e descobrir rapidamente o regex complicado que você usou ou aquele estranho one-liner que você criou há 7 meses.

    
por 20.11.2014 / 15:53
5

Eu posso oferecer uma correção para o último: certifique-se de que a variável env HISTCONTROL não especifique "ignorespace" (ou "ignoreboth").

Mas sinto sua dor com várias sessões simultâneas. Simplesmente não é tratado bem no bash.

    
por 26.08.2010 / 15:59
5

Certo, finalmente isso me incomodou em encontrar uma solução decente:

# Write history after each command
_bash_history_append() {
    builtin history -a
}
PROMPT_COMMAND="_bash_history_append; $PROMPT_COMMAND"

O que isto faz é uma espécie de amalgamação do que foi dito neste tópico, exceto que eu não entendo por que você recarregaria o histórico global após cada comando. Eu raramente me importo com o que acontece em outros terminais, mas eu sempre executo uma série de comandos, digamos em um terminal:

make
ls -lh target/*.foo
scp target/artifact.foo vm:~/

(exemplo simplificado)

E em outro:

pv ~/test.data | nc vm:5000 >> output
less output
mv output output.backup1

Não gostaria que o comando fosse compartilhado

    
por 23.07.2015 / 11:05
3

Aqui está meu aprimoramento para a resposta do @ lesmana. A principal diferença é que as janelas concorrentes não compartilham o histórico. Isso significa que você pode continuar trabalhando em suas janelas, sem ter o contexto de outras janelas sendo carregadas em suas janelas atuais.

Se você digitar explicitamente 'histórico', OU se você abrir uma nova janela, você verá o histórico de todas as janelas anteriores.

Além disso, eu uso esta estratégia para arquivar todos os comandos já digitados na minha máquina.

# Consistent and forever bash history
HISTSIZE=100000
HISTFILESIZE=$HISTSIZE
HISTCONTROL=ignorespace:ignoredups

_bash_history_sync() {
  builtin history -a         #1
  HISTFILESIZE=$HISTSIZE     #2
}

_bash_history_sync_and_reload() {
  builtin history -a         #1
  HISTFILESIZE=$HISTSIZE     #2
  builtin history -c         #3
  builtin history -r         #4
}

history() {                  #5
  _bash_history_sync_and_reload
  builtin history "$@"
}

export HISTTIMEFORMAT="%y/%m/%d %H:%M:%S   "
PROMPT_COMMAND='history 1 >> ${HOME}/.bash_eternal_history'
PROMPT_COMMAND=_bash_history_sync;$PROMPT_COMMAND
    
por 15.04.2017 / 19:43
1

Eu escrevi um script para definir um arquivo de histórico por sessão ou tarefa com base no seguinte.

        # write existing history to the old file
        history -a

        # set new historyfile
        export HISTFILE="$1"
        export HISET=$1

        # touch the new file to make sure it exists
        touch $HISTFILE
        # load new history file
        history -r $HISTFILE

Não é necessário salvar todos os comandos do histórico, mas salva aqueles com os quais eu me importo e é mais fácil recuperá-los, passando por todos os comandos. Minha versão também lista todos os arquivos do histórico e oferece a capacidade de pesquisar todos eles.

Fonte completa: link

    
por 01.06.2014 / 08:02
1

Eu escolhi colocar o histórico em um arquivo por sessão, já que várias pessoas podem estar trabalhando no mesmo servidor - separar os comandos de cada sessão facilita a auditoria.

# Convert /dev/nnn/X or /dev/nnnX to "nnnX"
HISTSUFFIX='tty | sed 's/\///g;s/^dev//g''
# History file is now .bash_history_pts0
HISTFILE=".bash_history_$HISTSUFFIX"
HISTTIMEFORMAT="%y-%m-%d %H:%M:%S "
HISTCONTROL=ignoredups:ignorespace
shopt -s histappend
HISTSIZE=1000
HISTFILESIZE=5000

O histórico agora parece:

user@host:~# test 123
user@host:~# test 5451
user@host:~# history
1  15-08-11 10:09:58 test 123
2  15-08-11 10:10:00 test 5451
3  15-08-11 10:10:02 history

Com os arquivos parecidos com:

user@host:~# ls -la .bash*
-rw------- 1 root root  4275 Aug 11 09:42 .bash_history_pts0
-rw------- 1 root root    75 Aug 11 09:49 .bash_history_pts1
-rw-r--r-- 1 root root  3120 Aug 11 10:09 .bashrc
    
por 11.08.2015 / 02:15
1

Aqui vou apontar um problema com

export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"

e

PROMPT_COMMAND="$PROMPT_COMMAND;history -a; history -n"

Se você executar o source ~ / .bashrc, o $ PROMPT_COMMAND será como

"history -a; history -c; history -r history -a; history -c; history -r"

e

"history -a; history -n history -a; history -n"

Essa repetição ocorre toda vez que você executa 'source ~ / .bashrc'. Você pode verificar PROMPT_COMMAND depois de executar 'source ~ / .bashrc' executando 'echo $ PROMPT_COMMAND'.

Você pode ver que alguns comandos aparentemente estão quebrados: "history -n history -a". Mas a boa notícia é que ele ainda funciona, porque outras partes ainda formam uma sequência de comando válida (Apenas envolvendo algum custo extra devido à execução de alguns comandos repetidamente. E não tão limpo.)

Pessoalmente, uso a seguinte versão simples:

shopt -s histappend
PROMPT_COMMAND="history -a; history -c; history -r"

que possui a maioria das funcionalidades sem nenhum problema, como mencionado acima.

Outro ponto a ser feito é: não há magia mágica . PROMPT_COMMAND é apenas uma variável de ambiente simples. Os comandos nele são executados antes de você receber o prompt do bash (o sinal $). Por exemplo, o seu PROMPT_COMMAND é "echo 123" e você executa "ls" no seu terminal. O efeito é como executar "ls; echo 123".

$ PROMPT_COMMAND="echo 123"

saída (como a execução de 'PROMPT_COMMAND="echo 123"; $ PROMPT_COMMAND'):

123

Execute o seguinte:

$ echo 3

saída:

3
123

"history -a" é usado para escrever os comandos do histórico na memória para ~ / .bash_history

"history -c" é usado para limpar os comandos do histórico na memória

"history -r" é usado para ler os comandos de histórico de ~ / .bash_history para a memória

Veja a explicação do comando de histórico aqui: link

PS: Como outros usuários apontaram, export é desnecessário. Veja: usando exportação em .bashrc

    
por 10.09.2016 / 21:30
0

Aqui está o trecho do meu .bashrc e explicações curtas, sempre que necessário:

# The following line ensures that history logs screen commands as well
shopt -s histappend

# This line makes the history file to be rewritten and reread at each bash prompt
PROMPT_COMMAND="$PROMPT_COMMAND;history -a; history -n"
# Have lots of history
HISTSIZE=100000         # remember the last 100000 commands
HISTFILESIZE=100000     # start truncating commands after 100000 lines
HISTCONTROL=ignoreboth  # ignoreboth is shorthand for ignorespace and     ignoredups

O HISTFILESIZE e HISTSIZE são preferências pessoais e você pode alterá-las conforme seu gosto.

    
por 13.05.2015 / 06:48
0

Isso funciona para ZSH

##############################################################################
# History Configuration for ZSH
##############################################################################
HISTSIZE=10000               #How many lines of history to keep in memory
HISTFILE=~/.zsh_history     #Where to save history to disk
SAVEHIST=10000               #Number of history entries to save to disk
#HISTDUP=erase               #Erase duplicates in the history file
setopt    appendhistory     #Append history to the history file (no overwriting)
setopt    sharehistory      #Share history across terminals
setopt    incappendhistory  #Immediately append to the history file, not just when a term is killed
    
por 24.07.2017 / 22:49
0

Eu quis muito isso, especialmente a capacidade de recuperar um comando de onde ele foi executado para re-executar em um novo projeto (ou encontrar um diretório por um comando). Então eu coloquei esta ferramenta juntos, que combina soluções anteriores para armazenar um histórico CLI global com uma ferramenta interativa chamada percol (mapeada para C ^ R). Ainda está bem na primeira máquina que eu comecei a usá-lo, agora com uma história de CLI de dois anos de idade.

Ele não bagunça o histórico CLI local no que diz respeito às teclas de seta, mas permite que você acesse o histórico global com bastante facilidade (que também pode ser mapeado para algo diferente de C ^ R)

    
por 18.08.2017 / 00:31
0

Aqui está uma solução que não mistura histórias de sessões individuais!

Basicamente, é preciso armazenar o histórico de cada sessão separadamente e recriá-lo em cada prompt. Sim, ele usa mais recursos, mas não é tão lento quanto parece - o atraso só começa a ser perceptível se você tiver mais de 100.000 entradas de histórico.

Veja a lógica principal:

# on every prompt, save new history to dedicated file and recreate full history
# by reading all files, always keeping history from current session on top.
update_history () {
  history -a ${HISTFILE}.$$
  history -c
  history -r
  for f in 'ls ${HISTFILE}.[0-9]* | grep -v "${HISTFILE}.$$\$"'; do
    history -r $f
  done
  history -r "${HISTFILE}.$$"
}
export PROMPT_COMMAND='update_history'

# merge session history into main history file on bash exit
merge_session_history () {
  cat ${HISTFILE}.$$ >> $HISTFILE
  rm ${HISTFILE}.$$
}
trap merge_session_history EXIT

Consulte esta questão para obter uma solução completa, incluindo algumas salvaguardas e otimizações de desempenho.

    
por 14.03.2018 / 10:09
0

Porque eu prefiro o histórico infinito que salvou no arquivo personalizado. Eu criei essa configuração com base no link :

export HISTFILESIZE=
export HISTSIZE=
export HISTTIMEFORMAT="[%F %T] "

export HISTFILE=~/.bash_myhistory
PROMPT_COMMAND="history -a; history -r; $PROMPT_COMMAND"
    
por 25.05.2018 / 15:50