web-development-kb-pt.site

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

Consistentemente, tenho mais de um terminal aberto. Em qualquer lugar de duas a dez, fazendo várias coisas. Agora, digamos que eu reinicie e abro outro conjunto de terminais. Alguns se lembram de certas coisas, outros se esquecem.

Eu quero uma história que:

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

Qualquer coisa que eu possa fazer para que o bash funcione dessa maneira?

565
Oli

Adicione o seguinte a ~/.bashrc

# Avoid duplicates
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
Prompt_COMMAND="${Prompt_COMMAND:+$Prompt_COMMAND$'\n'}history -a; history -c; history -r"
355
Pablo R.

Então, isso é tudo sobre a minha história .bashrc coisa:

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, o bash 4.1.7 no 10.6.

257
kch

Aqui está minha tentativa de compartilhar o histórico de sessões do Bash. Isso permitirá o compartilhamento do histórico entre as sessões do bash de uma maneira que o contador do histórico não se confunda 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 "[email protected]"
}

Prompt_COMMAND=_bash_history_sync

Explicação:

  1. Anexe a linha recém-inserida ao $HISTFILE (O padrão é .bash_history). Isso fará com que $HISTFILE Cresça em uma linha.

  2. Definir a variável especial $HISTFILESIZE Para algum valor fará com que o Bash trunque $HISTFILE Não exceda as linhas $HISTFILESIZE Removendo as entradas mais antigas.

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

  4. Leia o conteúdo de $HISTFILE E insira-o no histórico atual da sessão em execução. isso aumentará o contador do 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 interno 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 posteriormente).

Mais explicações:

  • A etapa 1 garante que o comando da sessã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 de sessões atual.

  • Como a etapa 4 elevará o contador do histórico, precisamos reduzir o contador de alguma forma. Isso é feito na etapa 3.

  • Na etapa 3, o contador de histórico é reduzido em $HISTSIZE. Na etapa 4, o contador do 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 o mesmo que $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 o prompt do bash entre procurar o número e usá-lo. Isso geralmente significa não entrar e não ctrl + c.

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

Acho essa restrição razoável. De qualquer forma, tenho que procurar o número sempre, porque não consigo me lembrar de números arbitrários do histórico.

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 do Bash.

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

Erros estranhos:

A execução do comando history canalizado para qualquer coisa fará com que esse comando seja listado duas vezes no histórico. Por exemplo:

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

Todos serão listados no histórico duas vezes. Eu não tenho ideia do porquê.

Ideias para melhorias:

  • Modifique a função _bash_history_sync() para que não seja executada todas as vezes. Por exemplo, ele não deve ser executado após um CTRL+C No prompt. Costumo usar CTRL+C Para descartar uma longa linha de comando quando decido que não quero executar essa linha. Às vezes, tenho que usar CTRL+C Para interromper 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 determinado número de histórico mantenha seu valor para as entradas de histórico desta sessão.

123
lesmana

Eu não estou ciente de nenhuma maneira usando bash. Mas é um dos recursos mais populares de zsh .
Pessoalmente, prefiro zsh sobre bash, por isso recomendo tentar.

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

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
45
Maciej Piechotka

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:

Se a opção Histappend Shell estiver ativada (consulte a descrição do shopt em Shell BUILTIN COMMANDS abaixo), as linhas serão anexadas ao arquivo de histórico, caso contrário, o arquivo de histórico será sobrescrito.

17
Chris Down

Você pode editar seu prompt do BASH para executar os "history -a" e "history -r" que Muerr sugeriu:

savePS1=$PS1

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

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

(observe que esses são back-tiquetaques; eles executam o histórico -a e o histórico -r em todos os Prompt. Como não produzem texto, o Prompt não será alterado.

Depois de configurar sua variável PS1 da maneira que desejar, defina-a permanentemente no seu arquivo ~/.bashrc.

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

PS1=$savePS1

Fiz testes básicos para garantir que funcione, mas não consigo falar com nenhum efeito colateral da execução history -a;history -r em todos os Prompt.

11
Schof

Se você precisar de uma solução de sincronização de histórico do bash ou zsh que também resolva o problema abaixo, consulte-o em http://ptspts.blogspot.com/2011/03/how-to-automatically-synchronize-Shell.html

O problema é o seguinte: Eu tenho duas janelas A e B. do Shell. Na janela A do Shell, eu executo sleep 9999 e (sem aguardar o término do sono) na janela B do Shell, desejo poder ver sleep 9999 na história do bash.

A razão pela qual a maioria das outras soluções aqui não resolve esse problema é que elas estão gravando as alterações de histórico no arquivo de histórico usando Prompt_COMMAND ou PS1, os quais estão sendo executados tarde demais, somente após o sleep 9999 comando finalizado.

10
pts

Certo, então 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 isso faz é uma espécie de amálgama do que foi dito neste segmento, exceto que eu não entendo por que você recarregaria a história global após cada comando. Eu raramente me importo com o que acontece em outros terminais, mas 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

De jeito nenhum eu gostaria que o comando fosse compartilhado

9
Yarek T

Você pode usar history -a para anexar o histórico da sessão atual ao arquivo de histórico e use history -r nos outros terminais para ler o arquivo histórico.

9
jtimberman

Aqui está uma alternativa que eu uso. É complicado, mas aborda o problema que o @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, outro para vim, etc.).

Eu 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 no seu diretório pessoal.

Também tenho uma tecla de atalho separada para pesquisar no arquivo de histórico mestre:

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

Eu uso gato | grep porque deixa o cursor no final para inserir meu 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 meus propósitos. Periodicamente, também retirarei o histórico de outros hosts em que trabalhei e anexarei esse histórico ao meu arquivo master_history.txt.

É sempre bom poder pesquisar rapidamente e encontrar o regex complicado que você usou ou o estranho liner Perl que você inventou há 7 meses.

8
Toby

Posso oferecer uma correção para essa última: verifique se a variável env HISTCONTROL não especifica "ignorespace" (ou "ignoreboth").

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

7
jmanning2k

Aqui está o meu aprimoramento para @ lesmana's answer . A principal diferença é que as janelas simultâneas não compartilham histórico. Isso significa que você pode continuar trabalhando nas janelas, sem ter o contexto de outras janelas sendo carregado nas janelas atuais.

Se você digitar explicitamente 'history', OR se você abrir uma nova janela, obterá 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 "[email protected]"
}

export HISTTIMEFORMAT="%y/%m/%d %H:%M:%S   "
Prompt_COMMAND='history 1 >> ${HOME}/.bash_eternal_history'
Prompt_COMMAND=_bash_history_sync;$Prompt_COMMAND
7
rouble

Eu escolhi colocar o histórico em um arquivo por tty, pois várias pessoas podem trabalhar 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

A história agora se parece com:

[email protected]:~# test 123
[email protected]:~# test 5451
[email protected]:~# 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:

[email protected]:~# 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
6
Litch

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 sempre que você executa 'source ~/.bashrc'. Você pode verificar Prompt_COMMAND após cada vez que executar 'source ~/.bashrc' executando 'echo $ Prompt_COMMAND'.

Você pode ver alguns comandos aparentemente quebrados: "history -n history -a". Mas a boa notícia é que ele ainda funciona, porque outras partes ainda formam uma sequência de comandos válida (apenas envolve 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, embora não exista esse problema como mencionado acima.

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

$ Prompt_COMMAND="echo 123"

output (Assim como executar 'Prompt_COMMAND = "eco 123"; $ Prompt_COMMAND'):

123

Execute o seguinte:

$ echo 3

resultado:

3
123

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

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

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

Consulte a explicação do comando history aqui: http://ss64.com/bash/history.html

PS: Como outros usuários apontaram, a exportação é desnecessária. Veja: sando exportação em .bashrc

5
fstang

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 os que mais me interessam e é mais fácil recuperá-los do que passar por todos os comandos. Minha versão também lista todos os arquivos de histórico e fornece a capacidade de pesquisar por todos eles.

Fonte completa: https://github.com/simotek/scripts-config/blob/master/hiset.sh

3
simotek

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

Basicamente, é necessário armazenar o histórico de cada sessão separadamente e recriá-lo em todos os Prompt. Sim, ele usa mais recursos, mas não é tão lento quanto parece - o atraso começa a ser perceptível apenas se você tiver mais de 100000 entradas no histórico.

Aqui está 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 este Gist para obter uma solução completa, incluindo algumas salvaguardas e otimizações de desempenho.

3
Jan Warchoł

Porque eu prefiro a história infinita que foi salva no arquivo personalizado. Eu crio essa configuração com base em https://stackoverflow.com/a/19533853/4632019 :

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

export HISTFILE=~/.bash_myhistory
Prompt_COMMAND="history -a; history -r; $Prompt_COMMAND"
2
Eugen Konkov

Isso funciona para o 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
2
Mulki

Eu desejava isso há muito tempo, especialmente a capacidade de recuperar um comando por onde ele foi executado para executar novamente em um novo projeto (ou encontrar um diretório por um comando). Então, juntei esta ferramenta , que combina soluções anteriores para armazenar um histórico global da CLI com uma ferramenta de grepping interativa chamada percol (mapeada para C ^ R). Ainda está bom na primeira máquina que comecei a usá-la, agora com um histórico de CLI com mais de 2 anos.

Ele não mexe com o histórico da CLI local no que diz respeito às teclas de seta, mas permite acessar o histórico global com bastante facilidade (que você também pode mapear para algo diferente de C ^ R)

2
Gordon Wells

Aqui está o trecho do meu .bashrc e breves explicações 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

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

0
Hopping Bunny