Quels personnages doivent être échappés lors de l'utilisation de Bash?

206

Existe-t-il une liste complète des personnages qui doivent être échappés dans Bash? Peut-il être vérifié uniquement avec sed?

En particulier, je vérifiais s'il %fallait échapper ou non. j'ai essayé

echo "h%h" | sed 's/%/i/g'

et a bien fonctionné, sans s'échapper %. Cela signifie-t-il qu'il %ne faut pas s'échapper? Était-ce un bon moyen de vérifier la nécessité?

Et plus généralement: sont-ils les mêmes personnages pour s'échapper dans shellet bash?

fedorqui 'SO arrête de nuire'
la source
4
En général, si vous vous souciez, vous vous trompez. La gestion des données ne doit jamais impliquer de les exécuter via le processus d'analyse et d'évaluation utilisé pour le code, ce qui rend l'échappement sans objet. Il s'agit d'un parallèle très proche des meilleures pratiques pour SQL - où la bonne chose est d'utiliser des variables de liaison et la mauvaise chose est d'essayer de «nettoyer» les données injectées via des substitutions de chaînes.
Charles Duffy
8
@CharlesDuffy Oui, mais parfois ce que fait le moteur d'instructions préparé sur le backend est juste d'échapper aux choses. SO "fait-il mal" parce qu'ils échappent aux commentaires soumis par les utilisateurs avant de les afficher dans le navigateur? Non, ils empêchent XSS. Ne pas s'en soucier du tout, c'est le mal.
Parthian Shot
@ParthianShot, si le moteur d'instructions préparé ne garde pas les données complètement hors bande du code, les personnes qui les ont écrites doivent être abattues. Oui, je sais que le protocole de câblage de MySQL est implémenté de cette façon; ma déclaration demeure.
Charles Duffy
@CharlesDuffy Et mon point de vue - que parfois vos options sont de faire fonctionner quelque chose en toute sécurité en utilisant une chaîne d'outils qui ferait grincer des dents puriste, ou de couler huit fois plus de temps et d'efforts pour le rendre joli - tient toujours.
Parthian Shot

Réponses:

282

Il existe deux règles simples et sûres qui fonctionnent non seulement dans shmais aussi bash.

1. Mettez la chaîne entière entre guillemets simples

Cela fonctionne pour tous les caractères, sauf les guillemets simples. Pour échapper au guillemet simple, fermez le guillemet avant, insérez le guillemet simple et rouvrez le guillemet.

'I'\''m a s@fe $tring which ends in newline
'

commande sed: sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2. Échapper à tous les caractères avec une barre oblique inverse

Cela fonctionne pour tous les personnages sauf le saut de ligne. Pour les caractères de nouvelle ligne, utilisez des guillemets simples ou doubles. Les chaînes vides doivent toujours être traitées - remplacer par""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

commande sed: sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2b. Version plus lisible de 2

Il y a un jeu de caractères sûr et facile, comme [a-zA-Z0-9,._+:@%/-], qui peut être laissé sans échappement pour le rendre plus lisible

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

commande sed: LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.


Notez que dans un programme sed, on ne peut pas savoir si la dernière ligne d'entrée se termine par un octet de nouvelle ligne (sauf lorsqu'il est vide). C'est pourquoi les deux commandes sed ci-dessus supposent que non. Vous pouvez ajouter manuellement une nouvelle ligne entre guillemets.

Notez que les variables shell ne sont définies que pour le texte au sens POSIX. Le traitement des données binaires n'est pas défini. Pour les implémentations importantes, le binaire fonctionne à l'exception des octets NUL (car les variables sont implémentées avec des chaînes C et destinées à être utilisées comme des chaînes C, à savoir des arguments de programme), mais vous devez passer à un environnement local "binaire" tel que latin1 .


(Vous pouvez facilement valider les règles en lisant la spécification POSIX pour sh. Pour bash, consultez le manuel de référence lié par @AustinPhillips)

Jo So
la source
Remarque: une bonne variation sur # 1 peut être vue ici: github.com/scop/bash-completion/blob/… . Il ne nécessite pas de s'exécuter sed, mais nécessite bash.
jwd
4
Remarque pour quiconque (comme moi!) Qui a du mal à les faire fonctionner .... on dirait que la saveur de sed que vous obtenez sur OSX n'exécute pas correctement ces commandes sed. Ils fonctionnent bien sur Linux!
dalelane
@dalelane: impossible de tester ici. Veuillez modifier lorsque vous avez une version qui fonctionne sur les deux.
Jo So
Il semble que vous ayez raté la chaîne si la chaîne commence par un «-» (moins), ou cela s'applique-t-il uniquement aux noms de fichiers? - dans ce dernier cas besoin d'un './' devant.
slashmais
Je ne sais pas ce que tu veux dire. Avec ces commandes sed, la chaîne d'entrée est tirée de stdin.
Jo So
59

format qui peut être réutilisé comme entrée shell

Il existe une directive de format spécial printf ( %q) conçue pour ce type de demande:

printf [-v var] format [arguments]

 %q     causes printf to output the corresponding argument
        in a format that can be reused as shell input.

Certains échantillons:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

Cela pourrait également être utilisé par le biais de variables:

printf -v var "%q" "$foo
"
echo "$var"
$'Hello world\n'

Vérification rapide avec tous les (128) octets ascii:

Notez que tous les octets de 128 à 255 doivent être échappés.

for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column

Cela doit rendre quelque chose comme:

00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h      
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i      
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j      
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k      
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l      
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m      
06 E $'\006'    20 E \          3A - :          54 - T          6E - n      
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o      
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p      
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q      
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r      
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s      
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t      
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u      
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v      
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w      
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x      
11 E $'\021'    2B - +          45 - E          5F - _          79 - y      
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z      
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{     
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|     
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}     
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~     
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f      
19 E $'\031'    33 - 3          4D - M          67 - g      

Où le premier champ est la valeur hexa de l'octet, le second contient Esi le caractère doit être échappé et le troisième champ affiche la présentation échappée du caractère.

Pourquoi ,?

Vous pouvez voir des personnages qui ne sont pas toujours besoin d'être échappé, comme ,, }et {.

Donc pas toujours mais parfois :

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

ou

echo test { 1, 2, 3 }
test { 1, 2, 3 }

mais attention:

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3 
F. Hauri
la source
Cela a le problème que, en appelant pritnf via bash / sh, la chaîne doit d'abord être échappée par shell pour bash / sh
ThorSummoner
1
@ThorSummoner, pas si vous passez la chaîne en tant qu'argument littéral au shell à partir d'une langue différente (où vous savez probablement déjà comment citer). En Python: subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()vous donnera une version correctement citée de shell arbitrary_string.
Charles Duffy
1
FYI bash's a %qété brisé pendant longtemps - Si mon esprit me sert bien, une erreur a été corrigée (mais pourrait encore être brisée) en 2013 après avoir été brisée pendant environ 10 ans. Alors ne vous y fiez pas.
Jo So
@CharlesDuffy Bien sûr, une fois que vous êtes en terre Python, shlex.quote()(> = 3.3, pipes.quote()- non documenté - pour les anciennes versions) fera également le travail et produira une version plus lisible par l'homme (en ajoutant des guillemets et en s'échappant, si nécessaire) de la plupart des chaînes, sans avoir besoin de faire apparaître un obus.
Thomas Perl
1
Merci d'ajouter des notes spéciales sur ,. J'ai été surpris d'apprendre que Bash intégré printf -- %q ','donne \,, mais /usr/bin/printf -- %q ','donne ,(non échappé). Idem pour d' autres caractères: {, |, }, ~.
kevinarpe
34

Pour éviter à quelqu'un d'autre d'avoir à RTFM ... en bash :

Caractères Enfermer guillemets préserve la valeur littérale de tous les caractères dans les citations, à l'exception de $, `, \et, lorsque l' expansion de l' histoire est activée, !.

... donc si vous y échappez (et la citation elle-même, bien sûr), vous êtes probablement d'accord.

Si vous adoptez une approche plus conservatrice «en cas de doute, échappez-vous à lui», il devrait être possible d'éviter à la place d'obtenir des caractères ayant une signification spéciale en n'échappant pas aux caractères d'identification (c.-à-d. Lettres ASCII, chiffres ou «_»). Il est très peu probable que ceux-ci aient jamais (c'est-à-dire dans un shell POSIX-ish étrange) une signification particulière et doivent donc être échappés.

Matthieu
la source
1
voici le manuel cité ci-dessus: gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk
C'est une réponse courte, douce et surtout correcte (+1 pour cela) mais peut-être est-il encore mieux d'utiliser des guillemets simples - voir ma réponse plus longue.
Jo So
26

En utilisant la print '%q' technique , nous pouvons exécuter une boucle pour découvrir quels caractères sont spéciaux:

#!/bin/bash
special=$'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
for ((i=0; i < ${#special}; i++)); do
    char="${special:i:1}"
    printf -v q_char '%q' "$char"
    if [[ "$char" != "$q_char" ]]; then
        printf 'Yes - character %s needs to be escaped\n' "$char"
    else
        printf 'No - character %s does not need to be escaped\n' "$char"
    fi
done | sort

Il donne cette sortie:

No, character % does not need to be escaped
No, character + does not need to be escaped
No, character - does not need to be escaped
No, character . does not need to be escaped
No, character / does not need to be escaped
No, character : does not need to be escaped
No, character = does not need to be escaped
No, character @ does not need to be escaped
No, character _ does not need to be escaped
Yes, character   needs to be escaped
Yes, character ! needs to be escaped
Yes, character " needs to be escaped
Yes, character # needs to be escaped
Yes, character $ needs to be escaped
Yes, character & needs to be escaped
Yes, character ' needs to be escaped
Yes, character ( needs to be escaped
Yes, character ) needs to be escaped
Yes, character * needs to be escaped
Yes, character , needs to be escaped
Yes, character ; needs to be escaped
Yes, character < needs to be escaped
Yes, character > needs to be escaped
Yes, character ? needs to be escaped
Yes, character [ needs to be escaped
Yes, character \ needs to be escaped
Yes, character ] needs to be escaped
Yes, character ^ needs to be escaped
Yes, character ` needs to be escaped
Yes, character { needs to be escaped
Yes, character | needs to be escaped
Yes, character } needs to be escaped

Certains des résultats, comme l' ,air un peu suspect. Il serait intéressant d'obtenir les contributions de @ CharlesDuffy à ce sujet.

codeforester
la source
2
Vous pouvez lire la réponse pour avoir l' ,air un peu suspect au dernier paragraphe de ma réponse
F. Hauri
2
Gardez à l'esprit que vous %qne savez pas où vous envisagez d'utiliser le personnage dans le shell, il échappera donc à tous les personnages qui peuvent avoir une signification particulière dans n'importe quel contexte de shell possible. ,elle-même n'a pas de signification particulière pour elle, mais comme @ F.Hauri l'a souligné dans sa réponse, elle a une signification particulière dans l' {...}expansion de l'accolade: gnu.org/savannah-checkouts/gnu/bash/manual/… C'est comme! qui ne nécessite également une expansion que dans des situations spécifiques, pas en général: echo Hello World!fonctionne très bien, mais echo test!testéchouera.
Mecki
18

Les caractères qui doivent s'échapper sont différents dans Bourne ou dans le shell POSIX que dans Bash. Généralement (très) Bash est un sur-ensemble de ces obus, donc tout ce que vous échappez shelldoit être échappé dans Bash.

Une belle règle générale serait "en cas de doute, échappez-vous". Mais échapper à certains personnages leur donne une signification particulière, comme \n. Ceux-ci sont répertoriés dans les man bashpages sous Quotinget echo.

A part ça, échappez à tout caractère qui n'est pas alphanumérique, c'est plus sûr. Je ne connais pas de liste définitive.

Les pages de manuel les répertorient toutes quelque part, mais pas au même endroit. Apprenez la langue, c'est le moyen d'en être sûr.

Celui qui m'a rattrapé est !. Il s'agit d'un caractère spécial (extension historique) dans Bash (et csh) mais pas dans le shell Korn. echo "Hello world!"Donne même des problèmes. L'utilisation de guillemets simples, comme d'habitude, supprime la signification spéciale.

cdarke
la source
1
J'aime particulièrement le conseil Une belle règle générale serait "en cas de doute, échappez-vous" . J'ai toujours le doute que vérifier avec sedest suffisant pour voir s'il doit être échappé. Merci pour votre réponse!
fedorqui 'SO arrête de nuire'
2
@fedorqui: Vérifier avec sedn'est pas nécessaire, vous pouvez vérifier avec presque n'importe quoi. sedn'est pas le problème, bashest. À l'intérieur des guillemets simples, il n'y a pas de caractères spéciaux (sauf les guillemets simples), vous ne pouvez même pas y échapper. Une sedcommande doit généralement se trouver entre guillemets simples car les métacaractères RE ont trop de chevauchements avec les métacaractères shell pour être sûrs. L'exception est lors de l'incorporation de variables shell, ce qui doit être fait avec soin.
cdarke
5
Vérifiez avec echo. Si vous sortez ce que vous avez mis, il n'a pas besoin d'être échappé. :)
Mark Reed
6

Je suppose que vous parlez de chaînes bash. Il existe différents types de chaînes qui ont un ensemble d'exigences différent pour l'échappement. par exemple. Les chaînes de guillemets simples sont différentes des chaînes entre guillemets doubles.

La meilleure référence est la Citant section du manuel bash.

Il explique quels personnages doivent s'échapper. Notez que certains caractères peuvent nécessiter un échappement selon les options activées telles que l'expansion de l'historique.

Austin Phillips
la source
3
Cela confirme donc que l'évasion est une telle jungle sans solution facile, devra vérifier chaque cas. Merci!
fedorqui 'SO arrêtez de nuire'
@fedorqui Comme avec n'importe quelle langue, il y a un ensemble de règles à suivre. Pour l'échappement de chaîne bash, l'ensemble de règles est assez petit comme décrit dans le manuel. La chaîne la plus simple à utiliser est les guillemets simples car rien ne doit s'échapper. Cependant, il n'existe aucun moyen d'inclure un guillemet simple dans une seule chaîne entre guillemets.
Austin Phillips
@fedorqui. Ce n'est pas une jungle. S'échapper est tout à fait faisable. Voir mon nouveau post.
Jo So
@fedorqui Vous ne pouvez pas utiliser de guillemet simple à l'intérieur d'une chaîne entre guillemets simples mais vous pouvez "l'échapper" avec quelque chose comme: "texte" "" "" plus de texte "
CR.
4

J'ai remarqué que bash échappe automatiquement certains caractères lors de l'utilisation de la saisie semi-automatique.

Par exemple, si vous avez un répertoire nommé dir:A, bash se complétera automatiquement pourdir\:A

En utilisant cela, j'ai exécuté quelques expériences en utilisant des caractères de la table ASCII et dérivé les listes suivantes:

Caractères que bash échappe lors de la saisie semi-automatique : (inclut l'espace)

 !"$&'()*,:;<=>?@[\]^`{|}

Les personnages que bash n'échappe pas :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(J'ai exclu /, car il ne peut pas être utilisé dans les noms de répertoire)

yuri
la source
2
Si vous vouliez vraiment avoir une liste complète, je suggérerais de voir quels personnages printf %qmodifient et ne modifient pas s'ils sont passés en argument - idéalement, en parcourant l'ensemble du jeu de caractères.
Charles Duffy
Dans certains cas, même avec la chaîne d'apostrophe, vous souhaiterez peut-être échapper des lettres et des chiffres pour produire des caractères spéciaux. Par exemple: tr '\ n' '\ t' qui traduit les caractères de nouvelle ligne en caractères de tabulation.
Dick Guertin