Quels caractères doivent être échappés dans les arguments de ligne de commande?

14

Dans Bash, lorsque vous spécifiez des arguments de ligne de commande pour une commande, quels caractères doivent être échappés?

Sont - ils limités aux métacaractères de Bash: l' espace, onglet, |, &, ;, (, ), <et >?

Tim
la source
N'oubliez pas (possible) le remplacement du nom de fichier par * et?
Jeff Schaller
Merci. Pourriez-vous énumérer de manière exhaustive les types de caractères qui doivent être échappés dans les arguments de ligne cmd?
Tim
La liste est bonne à avoir, mais la chose la plus importante à comprendre à propos de la citation est la suivante: tout entre les guillemets simples est passé littéralement et sans fractionnement de mots. Aucune exception. (Cela signifie qu'il n'y a aucun moyen d'incorporer une citation unique dans des guillemets simples, en passant, mais c'est facile à contourner .)
Wildcard

Réponses:

22

Les caractères suivants ont une signification particulière pour le shell lui-même dans certains contextes et peuvent devoir être échappés dans les arguments:

Certains de ces personnages sont utilisés pour plus de choses et dans plus d'endroits que celui que j'ai lié.


Il existe quelques cas d'angle qui sont explicitement facultatifs:

  • !peut être désactivé avec set +H, qui est la valeur par défaut dans les shells non interactifs.
  • {peut être désactivé avec set +B.
  • *et ?peut être désactivé avec set -fouset -o noglob .
  • =Le signe égal (U + 003D) doit également être échappé si set -kouset -o keyword est activé.

Échapper à une nouvelle ligne nécessite des guillemets - les barres obliques inverses ne feront pas l'affaire. Tous les autres caractères répertoriés dans IFS auront besoin d'une gestion similaire. Vous n'avez pas besoin d'échapper ]ou }, mais vous ne devez échapper )parce que c'est un opérateur.

Certains de ces personnages ont des limites plus strictes quant au moment où ils ont vraiment besoin de s'échapper que d'autres. Par exemple, a#bc'est ok, mais a #bc'est un commentaire, alors >qu'il faudrait s'échapper dans les deux contextes. De toute façon, cela ne fait pas de mal de leur échapper de manière conservatrice, et c'est plus facile que de se souvenir des fines distinctions.

Si le nom de votre commande lui - même est un mot - clé shell ( if, for, do) alors vous aurez besoin d'échapper ou de citer aussi. La seule intéressante de celles-ci est in, car il n'est pas évident que c'est toujours un mot-clé. Vous n'avez pas besoin de le faire pour les mots clés utilisés dans les arguments, uniquement lorsque vous avez (stupidement!) Nommé une commande après l'un d'eux. Opérateurs Shell ( (, &, etc.) ont toujours besoin de citer , où qu'ils soient.


1 Stéphane a noté que tout autre caractère vierge à un octet de votre région doit également être échappé. Dans la plupart des environnements locaux sensibles, au moins ceux basés sur C ou UTF-8, ce ne sont que les espaces blancs ci-dessus. Dans certains environnements locaux ISO-8859-1, l'espace sans interruption U + 00A0 est considéré comme vide, y compris Solaris, les BSD et OS X (je pense à tort). Si vous avez affaire à un lieu inconnu arbitraire, cela peut inclure à peu près tout, y compris des lettres, alors bonne chance.

En théorie, un seul octet considéré comme vide pourrait apparaître dans un caractère multi-octets qui n'était pas vide, et vous n'auriez aucun moyen d'y échapper autre que de mettre le tout entre guillemets. Ce n'est pas une préoccupation théorique: dans un environnement local ISO-8859-1 d'en haut, cet A0octet qui est considéré comme un blanc peut apparaître dans des caractères multi-octets comme UTF-8 encodé "à" ( C3 A0). Pour gérer ces caractères en toute sécurité, vous devez les citer "à". Ce comportement dépend de la configuration locale dans l'environnement exécutant le script, pas celle dans laquelle vous l'avez écrit.

Je pense que ce comportement est brisé de plusieurs façons, mais nous devons jouer la main qui nous est distribuée. Si vous travaillez avec un jeu de caractères multi-octets non auto-synchronisé, le plus sûr serait de tout citer. Si vous êtes en UTF-8 ou C, vous êtes en sécurité (pour le moment).

Michael Homer
la source
D'autres espaces dans votre région devraient également s'échapper ( sauf actuellement celui à plusieurs octets en raison d'un bug )
Stéphane Chazelas
Vous ne devez vous échapper que !lorsque l'expansion de l'historique csh est activée, généralement pas dans les scripts. [ ! -f a ]ou find . ! -name...bien. Cela est couvert par votre section sur les limites plus strictes, mais cela mérite peut-être d'être mentionné explicitement.
Stéphane Chazelas
Notez qu'il existe des contextes où d' autres personnages ont besoin comme citant: hash[foo"]"]=, ${var-foo"}"}, [[ "!" = b ]], [[ a = "]]" ]], les opérateurs de regexp pour [[ x =~ ".+[" ]]. D' autres mots - clés que {( if, while, for...) devront être cité afin qu'ils ne sont pas reconnus comme tels ...
Stéphane Chazelas
Dans la mesure où ce sont des arguments de ligne de commande, l'interprétation dépend de la commande en question (tout comme ]), donc je ne les énumère pas. Je ne pense pas qu'un mot-clé doive être cité en position d'argument.
Michael Homer
2
La citation de commandes intégrées, de tirets ou de% ne fait rien.
Michael Homer
3

Dans GNU Parallel, cela est testé et largement utilisé:

$a =~ s/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\'\202-\377]/\\$&/go;
# quote newline as '\n'                                                                                                         
$a =~ s/[\n]/'\n'/go;

Il est testé bash, dash, ash, ksh, zshet fish. Certains des personnages n'ont pas besoin d'être cités dans certaines (versions) des shells, mais ce qui précède fonctionne dans tous les shells testés.

Si vous voulez simplement une chaîne entre guillemets, vous pouvez la diriger vers parallel --shellquote:

printf "&*\t*!" | parallel --shellquote
Ole Tange
la source
Comment n'ai-je pas entendu parler de parallèle avant ...
Tom H
@TomH Ce sera apprécié si vous pouvez passer 5 minutes à réfléchir à la façon dont nous aurions pu vous joindre.
Ole Tange
Je pense que c'est un problème de progression. la plupart des gens n'ont pas besoin ou ne comprennent pas le parallèle jusqu'à ce qu'ils aient progressé à travers certaines étapes de complexité. À ce moment-là, ils ont rencontré des xargs, des nohup et des trucs comme ça. De plus, je ne vois pas beaucoup de gens utiliser le parallèle pour résoudre des problèmes d'échange de pile ou lorsque je recherche des solutions aux problèmes de bash sur Google
Tom H
1

Pour une solution d'échappement légère en Perl, je suis le principe des guillemets simples. Une chaîne Bash entre guillemets simples peut avoir n'importe quel caractère, à l'exception de la guillemet simple elle-même.

Mon code:

my $bash_reserved_characters_re = qr([ !"#$&'()*;<>?\[\\`{|~\t\n]);

while(<>) {
    if (/$bash_reserved_characters_re/) {
        my $quoted = s/'/'"'"'/gr;
        print "'$quoted'";
    } else {
        print $_;
    }
}

Exemple d'exécution 1:

$ echo -n "abc" | perl escape_bash_special_chars.pl
abc

Exemple d'exécution 2:

echo "abc" | perl escape_bash_special_chars.pl
'abc
'

Exemple d'exécution 3:

echo -n 'ab^c' | perl escape_bash_special_chars.pl
ab^c

Exemple d'exécution 4:

echo -n 'ab~c' | perl escape_bash_special_chars.pl
'ab~c'

Exemple d'exécution 5:

echo -n "ab'c" | perl escape_bash_special_chars.pl
'ab'"'"'c'

echo 'ab'"'"'c'
ab'c
Jari Turkia
la source
Oui, validez cela. À mon avis, la plupart des gens atterriront sur cette page, car ils ont un problème à résoudre. Pas parce que cela fait un débat académique intéressant. C'est pourquoi j'aimerais proposer des solutions et discuter de leurs mérites, même en étant légèrement hors sujet.
Jari Turkia
Mon code n'est qu'une implémentation de la réponse de Michael Homer. Je n'avais pas l'intention d'apporter plus d'informations que ce qu'il a fait.
Jari Turkia