Perché il punto esclamativo `!` A volte sconvolge bash?


14

Mi rendo conto che !ha un significato speciale sulla riga di comando nel contesto della cronologia della riga di comando, ma a parte questo, in uno script corrente il punto esclamativo può talvolta causare un errore di analisi.
Penso che abbia qualcosa a che fare con un event, ma non ho idea di cosa sia un evento o cosa faccia. Anche così, lo stesso comando può comportarsi diversamente in situazioni diverse.
L'ultimo esempio, di seguito, causa un errore; ma perché, quando lo stesso codice ha funzionato al di fuori della sostituzione del 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


@Warren .. Grazie. Avevo visto quel QA, ma in realtà parla solo di come sfuggire alla barra rovesciata ... Il mio problema riguarda più il motivo per cui il codice apparentemente già sfuggito funziona in una situazione e non in un'altra ...
Peter.O

@fred: "apparentemente già scappato"? Non vedo nessuna via di fuga e stai usando virgolette doppie. Vedi la mia risposta (rivista). Quale parte pensi sia sfuggita?
Caleb,

@Caleb. Sì, ho usato il termine sbagliato .. protectedsarebbe stato più appropriato. (protetto da "virgolette singole")
Peter.O

Se ti occupi solo di compiti semplici, puoi usare var=$(…)(senza virgolette doppie) e funzionerà come (penso) ti aspetti. Questo è ancora “sicuro”, perché la parte di valore di un incarico semplice, non è soggetta alla suddivisione in parole o globbing (anche se questo potrebbe non essere vero per le assegnazioni fatte attraverso builtins (ad esempio export, locale così via) in tutte le conchiglie). Sfortunatamente, questo non si estende oltre i semplici compiti poiché le doppie virgolette sono il modo di proteggere dalla divisione delle parole e dai globbing pur ottenendo altri tipi di espansione in altri contesti.
Chris Johnsen,

Risposte:


12

Il !personaggio invoca la sostituzione della storia di Bash. Quando è seguito da una stringa (come nell'esempio fallito) tenta di espandersi all'ultimo evento della cronologia che è iniziato con quella stringa. Proprio come $varviene espanso al valore di quella stringa, !echosi espande fino all'ultimo comando echo nella tua cronologia.

Lo spazio è un personaggio di rottura in tali espansioni. Prima nota come funzionerebbe con le variabili:

# 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

La stessa cosa accadrà per l'espansione della storia. Il carattere bang ( !) inizia una sequenza di sostituzione della cronologia, ma solo se seguito da una stringa. Seguirlo con uno spazio lo rende letteralmente botto anziché parte di una sequenza di sostituzione.

Puoi evitare questo tipo di sostituzione sia per la variabile che per la cronologia usando le virgolette singole. I tuoi primi esempi hanno usato virgolette singole e quindi hanno funzionato bene. I tuoi ultimi esempi sono tra virgolette doppie e quindi li hanno scansionati per le sequenze di espansione prima di fare qualsiasi altra cosa. L'unica ragione per cui il primo non è inciampato è che lo spazio è un carattere di rottura come mostrato sopra.


Grazie Caleb ... Un'altra delle mie pre-concezioni è stata respinta ... Pensavo che l'analisi bash fosse eseguita dalla parentesi più interna o parentesi graffa, e poi ha funzionato verso l'esterno ... Sembra che bash analizzi diversamente dalla mia ipotesi.
Peter

1
La citazione è abbastanza confusa in bash senza che cambi nelle stringhe nidificate. Così com'è, le sostituzioni avvengono molto presto nel processo. Considera questo esempio:var=word; echo "test '$var'"; echo 'test "$var"'
Caleb,

.. Sì male. Ero consapevole di annidare le virgolette tra virgolette ... Il mio malinteso era che pensavo che il codice tra le parentesi della sostituzione del comando sarebbe stato analizzato separatamente, in ciò che circonda quelle parentesi; ma apparentemente no .. grazie.
Peter

6

Come già detto da Caleb , !viene usato per invocare la sostituzione della storia di Bash.

Se come me ritieni di non aver bisogno di una tale funzionalità, puoi disabilitarla inserendo la seguente riga in ~/.bashrc:

set +H

Non ne ho bisogno perché la cronologia può essere recuperata dalla freccia su e Ctrl- rricerca inversa incrementale. Vedi la pagina del manuale di bash, sezione Comandi per la manipolazione della cronologia per un elenco dettagliato delle scorciatoie.


2
Come vivi senza !!?
Caleb,

Grazie. set +H
Penserei

2
@fred: strano, generalmente l'espansione della storia è "attiva" solo per le shell interattive.
enzotib,

@enzo .. Grazie ancora .. L'avevo provato dalla riga di comando .. Ah! Se l'apprendimento non fosse così divertente, sarebbe noioso ... ho già parlato del caffè? aiuta anche :)
Peter.O

Ya, quello è un gotcha. Ho copiato il codice incollato dagli script non riusciti sulla riga di comando proprio per questo motivo. L'espansione della cronologia non è stata una preoccupazione nella sceneggiatura ma è su una shell interattiva.
Caleb,

2

il tuo primo esempio:

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

potrebbe essere ridotto a

echo '! p' 
echo '!p'

All'interno di virgolette singole, tutti i caratteri conservano i loro valori letterali. Così !ha perso il suo significato speciale e l'espansione della storia non è preformata.

il tuo secondo e terzo esempio:

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

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

potrebbe essere ridotto a

echo "'! p'"

echo "'!p'"

'! p'e '!p'sono essenzialmente parti delle stringhe tra virgolette doppie.

Tra virgolette, tutti i personaggi conservano i loro valori letterali ad eccezione $ , `, \e !.

Ciò implica le singole virgolette di '! p'e '!p'ha perso i loro significati speciali (cioè: incapace di scappare !), ma !conserva ancora il suo significato speciale, quindi viene eseguita l'espansione della storia.

Tuttavia, quando !è seguito da un carattere spazio, l'espansione della cronologia non viene eseguita.

Citando da man bash:

PREVENTIVAZIONE

[...]

Racchiudere i caratteri tra virgolette singole conserva il valore letterale di ciascun carattere tra virgolette. [...]

Racchiudere i caratteri tra virgolette doppie conserva il valore letterale di tutti i caratteri tra virgolette, ad eccezione di $, `, \ e, quando l'espansione della cronologia è abilitata,!. [...] Se abilitato, l'espansione della cronologia verrà eseguita a meno che un! apparire tra virgolette viene evitato usando una barra rovesciata. La barra rovesciata che precede il! non viene rimosso.

ESPANSIONE DI STORIA

[...]

Le espansioni della storia sono introdotte dall'aspetto del personaggio di espansione della storia, che è! per impostazione predefinita. Solo la barra rovesciata (\) e le virgolette singole possono citare il carattere di espansione della cronologia.

Numerosi personaggi inibiscono l'espansione della cronologia se si trovano immediatamente dopo il carattere di espansione della cronologia, anche se non quotato: spazio, tabulazione, riga nuova, ritorno a capo e =. Se l'opzione shell extglob è abilitata (inibirà anche l'espansione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.