Pourquoi le point d'exclamation «!» Dérange parfois bash?

14

Je me rends compte que cela !a une signification particulière sur la ligne de commande dans le contexte de l'historique de la ligne de commande, mais à part cela, dans un script d'exécution, le point d'exclamation peut parfois provoquer une erreur d'analyse.
Je pense que cela a quelque chose à voir avec un event, mais je n'ai aucune idée de ce qu'est un événement ou de ce qu'il fait. Même ainsi, la même commande peut se comporter différemment dans différentes situations.
Le dernier exemple, ci-dessous, provoque une erreur; mais pourquoi, quand le même code a fonctionné en dehors de la substitution de commande? .. en utilisant 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
Peter.O
la source
@Warren .. Merci. J'avais vu ce QA, mais il ne parle vraiment que de la façon d'échapper à la barre oblique inverse ... Mon problème concerne plus la raison pour laquelle le code apparemment déjà échappé fonctionne dans une situation et pas dans une autre ...
Peter.O
@fred: "apparemment déjà échappé"? Je ne vois aucune échappatoire du tout et vous utilisez des guillemets doubles. Voir ma réponse (révisée). Selon vous, quelle partie s'est échappée?
Caleb
@Caleb. Oui, j'ai utilisé le mauvais terme .. protectedaurait été plus approprié. (protégé par des `` guillemets simples '')
Peter.O
Si vous n'êtes concerné que par des affectations simples, vous pouvez utiliser var=$(…)(pas de guillemets doubles), et cela fonctionnera comme (je pense) que vous attendez. Ceci est encore « sûr » parce que la partie de la valeur d'une affectation simple n'est pas soumise à la séparation de mots ou englobement (bien que cela ne peut être vrai des missions effectuées par builtins (par exemple export, localetc.) sous toutes les coquilles). Malheureusement, cela ne va pas au-delà des affectations simples, car les guillemets doubles sont le moyen de se protéger contre le fractionnement et la globalisation des mots tout en obtenant d'autres types d'expansion dans d'autres contextes.
Chris Johnsen

Réponses:

12

Le !personnage invoque la substitution d'historique de bash. Lorsqu'il est suivi d'une chaîne (comme dans votre exemple défaillant), il essaie de se développer jusqu'au dernier événement d'historique qui a commencé avec cette chaîne. Tout comme $varest étendu à la valeur de cette chaîne, !echos'étendrait à la dernière commande echo de votre historique.

L'espace est un personnage de rupture dans de telles extensions. Notez d'abord comment cela fonctionnerait avec des variables:

# 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 même chose se produira pour l'expansion de l'histoire. Le caractère bang ( !) démarre une séquence de remplacement d'historique, mais uniquement s'il est suivi d'une chaîne. Le suivre avec un espace en fait un coup littéral au lieu d'une partie d'une séquence de remplacement.

Vous pouvez éviter ce type de remplacement à la fois pour les expansions de variables et d'historique en utilisant des guillemets simples. Vos premiers exemples ont utilisé des guillemets simples et se sont donc bien déroulés. Vos derniers exemples sont entre guillemets et bash les a donc scannés pour les séquences d'expantion avant qu'il ne fasse autre chose. La seule raison pour laquelle le premier ne s'est pas déclenché est que l'espace est un caractère de pause comme indiqué ci-dessus.

Caleb
la source
Merci Caleb .. Une autre de mes pré-conceptions a été rejetée ... Je pensais que l'analyse bash a été effectuée à partir du support ou de l'accolade le plus intérieur, puis a travaillé vers l'extérieur ... Il semble que bash analyse différemment de mon hypothèse.
Peter.O
1
Citer est assez déroutant dans bash sans qu'il change dans les chaînes imbriquées. En l'état, les remplacements se produisent très tôt dans le processus. Considérez cet exemple:var=word; echo "test '$var'"; echo 'test "$var"'
Caleb
.. Oui non compris. J'étais conscient de l'imbrication de guillemets dans les guillemets ... Mon malentendu était que je pensais que le code entre crochets de la substitution de commande serait analysé séparément, à ce qui entoure ces crochets; mais apparemment pas .. merci.
Peter.O
6

Comme déjà dit par Caleb , !est utilisé pour invoquer la substitution d'historique de bash.

Si comme moi vous sentez que vous n'avez pas besoin d'une telle fonctionnalité, vous pouvez la désactiver en insérant la ligne suivante ~/.bashrc:

set +H

Je ne ai pas besoin parce que l'histoire peut être récupéré par la flèche haut et Ctrl- rrecherche inversée incrémentale. Voir la page de manuel de bash, section Commandes de manipulation de l'historique pour une liste détaillée des raccourcis.

enzotib
la source
2
Comment vivez-vous sans !!?
Caleb
Merci., Je pense que cela pourrait être un problème, en termes de portabilité, mais l'utilisation set +Hdans le script fonctionne tout aussi bien :) +1
Peter.O
2
@fred: étrange, l'extension de l'histoire est généralement "activée" uniquement pour les shells interactifs.
enzotib
@enzo .. Merci encore .. Je l'avais testé depuis la ligne de commande .. Ah! Si l'apprentissage n'était pas si amusant, ce serait fastidieux ... Ai-je mentionné le café? ça aide aussi :)
Peter.O
Ya, c'est un piège. J'ai copié du code collé à partir de scripts qui ont échoué sur la ligne de commande pour cette raison. L'expansion de l'historique n'était pas une préoccupation dans le script mais elle se fait sur un shell interactif.
Caleb
2

votre premier exemple:

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

pourrait être réduit à

echo '! p' 
echo '!p'

Dans les guillemets simples, tous les caractères conservent leurs valeurs littérales. Ainsi !a perdu son sens particulier et l'expansion de l'histoire n'est pas préformée.

vos deuxième et troisième exemples:

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

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

pourrait être réduit à

echo "'! p'"

echo "'!p'"

'! p'et '!p'font essentiellement partie des chaînes entre guillemets doubles.

Entre guillemets, tous les caractères conservent leurs valeurs littérales , à l' exception $ , `, \et !.

Cela implique les guillemets simples de '! p'et '!p'ont perdu leur signification spéciale (c'est-à-dire: incapable de s'échapper !) mais !conserve toujours sa signification spéciale, ainsi une expansion de l'histoire est effectuée.

Cependant, lorsqu'il !est suivi d'un caractère espace, l'expansion de l'historique n'est pas effectuée.

Citant de man bash:

CITATION

[...]

La présence de caractères entre guillemets simples préserve la valeur littérale de chaque caractère dans les guillemets. [...]

La présence de caractères entre guillemets doubles conserve la valeur littérale de tous les caractères entre guillemets, à l'exception de $, `, \ et, lorsque l'expansion de l'historique est activée,!. [...] Si activé, l'expansion de l'historique sera effectuée sauf si! apparaissant entre guillemets est échappé à l'aide d'une barre oblique inverse. La barre oblique inverse précédant le! n'est pas supprimé.

EXPANSION DE L'HISTOIRE

[...]

Les extensions d'histoire sont introduites par l'apparition du personnage d'extension d'histoire, qui est! par défaut. Seule la barre oblique inverse (\) et les guillemets simples peuvent citer le caractère d'expansion de l'historique.

Plusieurs caractères inhibent l'expansion de l'historique s'ils sont trouvés immédiatement après le caractère d'extension de l'historique, même s'il n'est pas cité: espace, tabulation, retour à la ligne, retour chariot et =. Si l'option shell extglob est activée, (inhibera également l'expansion.

cychoi
la source