Por que o ponto de exclamação '!' às vezes chateado bash?

12

Eu percebo que ! tem um significado especial na linha de comando no contexto do histórico da linha de comando, mas, além disso, em um script de execução, o ponto de exclamação às vezes pode causar um erro de análise. Eu acho que tem algo a ver com um event , mas não tenho idéia do que é um evento ou o que ele faz. Mesmo assim, o mesmo comando pode se comportar de maneira diferente em situações diferentes.
O último exemplo, abaixo, causa um erro; mas por que, quando o mesmo código funcionava fora da substituição do comando? .. usando GNU bash 4.1.5

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found
    
por Peter.O 23.08.2011 / 14:30

3 respostas

10

O caractere ! invoca a substituição do histórico do bash. Quando seguido por uma string (como em seu exemplo com falha), ele tenta expandir para o último evento de histórico que começou com essa string. Assim como $var é expandido para o valor dessa string, !echo seria expandido para o último comando de eco em seu histórico.

O espaço é um personagem que quebra em tais expansões. Primeiro, observe como isso funcionaria com as variáveis:

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

A mesma coisa acontecerá para a expansão da história. O caractere bang ( ! ) inicia uma sequência de substituição de histórico, mas apenas se for seguido por uma string. Segui-lo com um espaço faz com que seja literal bang em vez de parte de uma sequência de substituição.

Você pode evitar esse tipo de substituição para variáveis e para a execução de histórico usando aspas simples. Seus primeiros exemplos usavam aspas simples e, portanto, funcionavam bem. Seus últimos exemplos estão entre aspas duplas e, assim, bash os escaneou para seqüências de expansão antes de fazer qualquer outra coisa. A única razão pela qual o primeiro não desarmou é que o espaço é um caractere de quebra, como mostrado acima.

    
por 23.08.2011 / 14:36
6

Como já dito pelo Caleb , ! é usado para invocar a substituição do histórico do bash.

Se você gosta de mim, não precisa de um recurso desse tipo, pode desativá-lo inserindo a seguinte linha em ~/.bashrc :

set +H

Eu não preciso disso porque o histórico pode ser recuperado pela seta para cima e Ctrl - r busca reversa incremental. Veja a página de manual do bash, seção Comandos para Manipulação do Histórico para uma lista detalhada de atalhos.

    
por 23.08.2011 / 14:47
2

seu primeiro exemplo:

{ echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }

pode ser reduzido para

echo '! p' 
echo '!p'

Dentro de aspas simples, todos os caracteres preservam seus valores literais. Assim, ! perdeu seu significado especial e a expansão da história não é pré-formada.

seu segundo e terceiro exemplos:

var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"

var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"

pode ser reduzido para

echo "'! p'"

echo "'!p'"

'! p' e '!p' são essencialmente partes das cadeias duplas entre aspas.

Dentro de aspas duplas, todos os caracteres preservam seus valores literais exceto $ , ' , \ e ! .

Isso implica que as aspas simples de '! p' e '!p' perderam seus significados especiais (ou seja, incapazes de escapar de ! ), mas ! ainda retém seu significado especial, assim a expansão de histórico é realizada.

No entanto, quando ! é seguido por um caractere de espaço, a expansão do histórico não é executada.

Citações de man bash :

QUOTING

[...]

Enclosing characters in single quotes preserves the literal value of each character within the quotes. [...]

Enclosing characters in double quotes preserves the literal value of all characters within the quotes, with the exception of $, ', \, and, when history expansion is enabled, !. [...] If enabled, history expansion will be performed unless an ! appearing in double quotes is escaped using a backslash. The backslash preceding the ! is not removed.

HISTORY EXPANSION

[...]

History expansions are introduced by the appearance of the history expansion character, which is ! by default. Only backslash (\) and single quotes can quote the history expansion character.

Several characters inhibit history expansion if found immediately following the history expansion character, even if it is unquoted: space, tab, newline, carriage return, and =. If the extglob shell option is enabled, ( will also inhibit expansion.

    
por 14.05.2015 / 10:20