Quando é possível uma entrada de histórico de múltiplas linhas (também conhecida como lithist) na bash?

5

No manual de referência da bash , ele afirma:

lithist If enabled, and the cmdhist option is enabled, multi-line commands are saved to the history with embedded newlines rather than using semicolon separators where possible.

com isso, portanto, minha pergunta: onde / quando / como isso é possível ?

Eu tentei ativar e testar o recurso no meu GNU bash, versão 4.4.12 (1) -release fazendo isso:

shopts -s cmdhist
shopts -s lithist
echo -n "this is a test for a ";\
echo "multiline command"

Eu então fiz um

history | tail 

esperando alguma saída parecida com isto:

101 shopts -s cmdlist
102 shopts -s lithist
103 echo -n "this is a test for a ";\
    echo "multiline command"
104 history

ainda assim, consiga isto:

101 shopts -s cmdlist
102 shopts -s lithist
103 echo -n "this is a test for a "; echo "multiline command"
104 history

Como é óbvio, o comando multiline (aquele com o número 103 do histórico do bash) não foi armazenado com "novas linhas incorporadas rathar do que usando separadores de ponto-e-vírgula" . Por que lithist não foi possível aqui? O que fiz de errado?

    
por humanityANDpeace 23.03.2017 / 18:47

3 respostas

6

Um \<new line> não é a maneira correta de obter um <new line> no histórico.

Memória

Vamos lidar apenas com as linhas do histórico, pois elas são mantidas na memória shell (não no disco).

Permite digitar alguns comandos como você fez:

$ echo -n "this is a test for a ";\
> echo "two line command"

O que foi armazenado na memória como a linha que acabou de ser escrita?

$ history 2
514  echo -n "this is a test for a ";echo "two line command"
515  history 2

Como você pode ver, a "continuação de linha", uma barra invertida seguida por uma nova linha, foi removida.
Como deveria (do homem bash):

If a \ pair appears, and the backslash is not itself quoted, the \ is treated as a line continuation (that is, it is removed from the input stream and effectively ignored).

Podemos receber uma nova linha se citá-la:

$ echo " A test of 
> a new line"
A test of 
a new line

E, nesse ponto, a história refletirá isso:

$ history 2
  518  echo "A test of
a new line"
  519  history 2

Um verdadeiro comando de várias linhas:

Um exemplo possível de um comando com várias linhas é:

$ for a in one two
> do echo "test $a"
> done
test one
test two

Que será reduzido em uma linha do histórico se cmdhist is set :

$ shopt -p cmdhist lithist
shopt -s cmdhist                                                                                                       
shopt -u lithist

$ history 3
   24  for a in one two; do echo "test $a"; done                                                                       
   25  shopt -p cmdhist lithist                                                                                        
   26  history 3     

Os números de cada comando mudaram porque em algum momento eu limpei o histórico (na memória) com um history -c .

Se você não selecionar o cmdhist, você verá isto:

$ shopt -u cmdhist
$ for a in one two
> do echo "test $a"
> done
test one
test two

$ history 5
    5  shopt -u cmdhist
    6  for a in one two
    7  do echo "test $a"
    8  done
    9  history 5

Cada linha (não um comando completo) estará em uma linha separada no histórico.

Mesmo se o lithist estiver definido:

$ shopt -s lithist
$ for a in one two
> do echo "test $a"
> done
test one
test two
$ history 5
   12  shopt -s lithist
   13  for a in one two
   14  do echo "test $a"
   15  done
   16  history 5

Mas se ambos estiverem definidos:

$ shopt -s cmdhist lithist
$ for a in one two
> do echo "test $a"
> done

$ history 5
   23  history 15
   24  shopt -p cmdhist lithist
   25  shopt -s cmdhist lithist
   26  for a in one two
do echo "test $a"
done
   27  history 5

O comando for foi armazenado como um comando multilinha com "novas linhas" em vez de ponto e vírgula ( ; ). Compare com acima onde o lithist não foi definido.

Disco

Todos os itens acima foram explicados usando a lista de comandos mantidos na memória do shell. Nenhum comando foi gravado no disco. O arquivo (padrão) ~/.bash_history não foi alterado.

Esse arquivo será alterado quando o shell em execução for encerrado. Nesse momento, o histórico sobrescreverá o arquivo (se histappend não estiver definido) ou será anexado de outra forma.

Se você quer que os comandos sejam enviados para o disco, você precisa tenha esse conjunto :

export PROMPT_COMMAND='history -a'

Isso fará com que cada linha de comando seja anexada ao arquivo em cada nova linha de comando.

Agora, vamos ao que interessa: cmdhist e lithist. Não é tão simples quanto parece. Mas não se preocupe, tudo ficará claro em um momento.

Digamos que você tenha tempo para digitar todos os comandos abaixo (não há atalho, sem alias, sem função, você precisa dos comandos reais, desculpe). Para limpar primeiro todo o histórico na memória ( history -c ) e no disco ( faça um backup ) ( history -w ) e tente três vezes:

  • Com os valores padrão de cmdhist (set) e lithist (unset).
  • com o conjunto
  • Com ambos, não definidos
  • Configurar o lithist com um cmdhist não definido não faz sentido (você pode testá-lo).

Lista de comandos para executar:

$ history -c ; history -w           # Clear all history ( Please backup).

$ shopt -s cmdhist; shopt -u lithist
$ for a in one two
> do echo "test $a"
> done

$ shopt -s cmdhist; shopt -s lithist
$ for a in one two
> do echo "test $a"
> done

$ shopt -u cmdhist; shopt -u lithist
$ for a in one two
> do echo "test $a"
> done

Você terminará com isso (na memória):

$ history
    1  shopt -s cmdhist; shopt -u lithist
    2  for a in one two; do echo "test $a"; done
    3  shopt -s cmdhist; shopt -s lithist
    4  for a in one two
do echo "test $a"
done
    5  shopt -u cmdhist; shopt -u lithist
    6  for a in one two
    7  do echo "test $a"
    8  done
    9  history

Os três comandos multilinha terminam da seguinte forma:

  • um na linha numerada 2 (uma linha única, um comando).
  • um em uma multilinha numerada 4 (um comando em várias linhas)
  • um em várias linhas numeradas de 6 a 8

Ok, mas o que acontece no arquivo? diga isso ...

finalmente, no arquivo:

Simples, escreva no arquivo e gata para ver isso:

$ history -w ; cat "$HISTFILE"
shopt -s cmdhist; shopt -u lithist
for a in one two; do echo "test $a"; done
shopt -s cmdhist; shopt -s lithist
for a in one two
do echo "test $a"
done
shopt -u cmdhist; shopt -u lithist
for a in one two
do echo "test $a"
done
history
history -w ; cat "$HISTFILE"

Não há números de linha, apenas comandos, não é possível saber onde uma multilinha é iniciada e onde ela termina. Não há como saber se existe uma multilinha.

Na verdade, é exatamente isso que acontece, se os comandos forem gravados no arquivo acima, quando o arquivo for lido novamente, qualquer informação sobre multilinhas será perdida.

Existe apenas um delimitador (a nova linha), cada linha é lida de volta como um comando.

Existe uma solução para isso, sim, para usar um delimitador adicional.
O tipo HISTTIMEFORMAT faz isso.

HISTTIMEFORMAT

Quando esta variável é definida para algum valor, a hora em que cada comando foi executado é armazenada no arquivo como os segundos desde epoch (sim, sempre segundos) após um caractere de comentário ( # ).

Se definirmos a variável e reescrevermos o arquivo ~/.bash_history , obteremos isso:

$ HISTTIMEFORMAT='%F'
$ history -w ; cat "$HISTFILE"
#1490321397
shopt -s cmdhist; shopt -u lithist
#1490321397
for a in one two; do echo "test $a"; done
#1490321406
shopt -s cmdhist; shopt -s lithist
#1490321406
for a in one two
do echo "test $a"
done
#1490321418
shopt -u cmdhist; shopt -u lithist
#1490321418
for a in one two
#1490321418
do echo "test $a"
#1490321420
done
#1490321429
history
#1490321439
history -w ; cat "$HISTFILE"
#1490321530
HISTTIMEFORMAT='%FT%T '
#1490321571
history -w ; cat "$HISTFILE"

Agora você pode dizer onde e qual linha é uma multilinha.

O formato '%FT%T ' mostra a hora, mas apenas ao usar o comando history:

$ history
    1  2017-03-23T22:09:57 shopt -s cmdhist; shopt -u lithist
    2  2017-03-23T22:09:57 for a in one two; do echo "test $a"; done
    3  2017-03-23T22:10:06 shopt -s cmdhist; shopt -s lithist
    4  2017-03-23T22:10:06 for a in one two
do echo "test $a"
done
    5  2017-03-23T22:10:18 shopt -u cmdhist; shopt -u lithist
    6  2017-03-23T22:10:18 for a in one two
    7  2017-03-23T22:10:18 do echo "test $a"
    8  2017-03-23T22:10:20 done
    9  2017-03-23T22:10:29 history
   10  2017-03-23T22:10:39 history -w ; cat "$HISTFILE"
   11  2017-03-23T22:12:10 HISTTIMEFORMAT='%F'
   12  2017-03-23T22:12:51 history -w ; cat "$HISTFILE"
   13  2017-03-23T22:15:30 history
   14  2017-03-23T22:16:29 HISTTIMEFORMAT='%FT%T'
   15  2017-03-23T22:16:31 history
   16  2017-03-23T22:16:35 HISTTIMEFORMAT='%FT%T '
   17  2017-03-23T22:16:37 history
    
por 23.03.2017 / 20:18
3

Isso não parece ser um comando multilinha (porque você escapou do retorno). Se você fizer um com retorno real, há uma diferença.

$ for i in /tmp/g*
> do echo $i
> done
/tmp/gigabyte
$ shopt -s lithist
$ for i in /tmp/g*
> do echo $i
> done
/tmp/gigabyte
$ history 
[...]
  517  for i in /tmp/g* ; do echo $i ; done
  518  shopt -s lithist
  519  for i in /tmp/g*
do echo $i
done
  520  history
    
por 23.03.2017 / 19:08
0

O iTerm lida com comandos de múltiplas linhas perfeitamente, ele salva o comando de múltiplas linhas como um comando, então nós podemos usar Cmd + Shift + ; para navegar no histórico. Confira mais dicas do iTerm em Trabalhando efetivamente com o iTerm

    
por 21.06.2018 / 02:03