Comment échapper aux guillemets simples dans des chaînes entre guillemets simples


1018

Disons que vous avez un Bash aliascomme:

alias rxvt='urxvt'

qui fonctionne bien.

Toutefois:

alias rxvt='urxvt -fg '#111111' -bg '#111111''

ne fonctionnera pas, et non plus:

alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''

Alors, comment finissez-vous par faire correspondre les guillemets d'ouverture et de fermeture à l'intérieur d'une chaîne une fois que vous avez échappé les guillemets?

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''

semble disgracieux bien qu'il représenterait la même chaîne si vous êtes autorisé à les concaténer comme ça.


16
Vous rendez-vous compte que vous n'avez pas besoin d'utiliser des guillemets simples pour les alias? Les guillemets doubles sont beaucoup plus faciles.
teknopaul


3
Les guillemets doubles imbriqués peuvent être échappés, "\""donc ceux-ci doivent être utilisés de préférence à la réponse de @ liori dans la mesure du possible.
Alan

7
Les guillemets doubles se comportent assez différemment des guillemets simples dans * nix (y compris Bash et des outils connexes comme Perl), donc remplacer les guillemets doubles chaque fois qu'il y a un problème avec les guillemets simples n'est PAS une bonne solution. Les guillemets doubles spécifient que les variables $ ... doivent être remplacées avant l'exécution, tandis que les guillemets simples spécifient $ ... doivent être traités littéralement.
Chuck Kollars

Si vous pensez, j'ai utilisé des guillemets doubles mais cela ne fonctionne toujours pas , sourcez à nouveau votre script.
Samy Bencherif

Réponses:


1456

Si vous voulez vraiment utiliser des guillemets simples dans la couche la plus externe, n'oubliez pas que vous pouvez coller les deux types de devis. Exemple:

 alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
 #                     ^^^^^       ^^^^^     ^^^^^       ^^^^
 #                     12345       12345     12345       1234

Explication de la façon dont '"'"'est interprété simplement ':

  1. ' Fin de la première citation qui utilise des guillemets simples.
  2. " Commencez la deuxième citation en utilisant des guillemets doubles.
  3. ' Caractère cité.
  4. " Terminez la deuxième citation en utilisant des guillemets doubles.
  5. ' Commencez le troisième devis en utilisant des guillemets simples.

Si vous ne placez aucun espace entre (1) et (2), ou entre (4) et (5), le shell interprétera cette chaîne comme un seul mot long.


5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"Cela fonctionne quand il y a des guillemets simples et des guillemets doubles dans la chaîne d'alias!
Uphill_ What '1

17
Mon interprétation: bash concatène implicitement des expressions de chaîne citées différemment.
Benjamin Atkin

2
travaillé pour moi, exemple de guillemets simples à double alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
échappement

2
Certainement pas la solution la plus lisible. Il sur-utilise des guillemets simples là où ils ne sont pas vraiment nécessaires.
oberlies

26
Je soutiens que '\''c'est beaucoup plus lisible dans la plupart des contextes que '"'"'. En fait, le premier est presque toujours clairement distinct dans une chaîne entre guillemets simples, et il s'agit donc simplement de le mapper sémantiquement au sens "c'est une citation échappée", comme on le fait \"dans les chaînes entre guillemets doubles. Alors que ce dernier se fond dans une seule ligne de cotations et nécessite une inspection minutieuse dans de nombreux cas pour bien distinguer.
mtraceur

263

Je remplace toujours chaque guillemet simple incorporé par la séquence: '\''(c'est-à-dire guillemet entre guillemets) qui ferme la chaîne, ajoute un guillemet simple échappé et rouvre la chaîne.


Je crée souvent une fonction "quotify" dans mes scripts Perl pour le faire pour moi. Les étapes seraient les suivantes:

s/'/'\\''/g    # Handle each embedded quote
$_ = qq['$_']; # Surround result with single quotes.

Cela s'occupe à peu près de tous les cas.

La vie devient plus amusante lorsque vous introduisez evaldans vos scripts shell. Vous devez essentiellement tout re-citer à nouveau!

Par exemple, créez un script Perl appelé quotify contenant les instructions ci-dessus:

#!/usr/bin/perl -pl
s/'/'\\''/g;
$_ = qq['$_'];

puis utilisez-le pour générer une chaîne correctement citée:

$ quotify
urxvt -fg '#111111' -bg '#111111'

résultat:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

qui peut ensuite être copié / collé dans la commande alias:

alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

(Si vous devez insérer la commande dans un eval, exécutez à nouveau le guillemet:

 $ quotify
 alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

résultat:

'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

qui peut être copié / collé dans un eval:

eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

1
Mais ce n'est pas perl. Et comme Steve B l'a souligné ci-dessus, avec sa référence au "manuel de référence gnu", vous ne pouvez pas échapper les guillemets en bash dans le même type de guillemet. Et en fait, il n'est pas nécessaire de les échapper dans des guillemets alternatifs, par exemple "" "est une chaîne de guillemet simple valide et" "" est une chaîne de guillemet double valide sans nécessiter aucun échappement.
nicerobot

8
@nicerobot: j'ai ajouté un exemple montrant que: 1) je n'essaye pas d'échapper les guillemets dans le même type de devis, 2) ni dans les guillemets alternatifs, et 3) Perl est utilisé pour automatiser le processus de génération d'un valide bash string contenant des guillemets intégrés
Adrian Pronk

18
Le premier paragraphe en lui-même est la réponse que je cherchais.
Dave Causey

9
C'est aussi ce que bash fait, tapez set -xet echo "here's a string"et vous verrez que bash s'exécute echo 'here'\''s a string'. ( set +xpour revenir à un comportement normal)
arekolek

196

Depuis la syntaxe Bash 2.04$'string' (au lieu de juste 'string'; avertissement: ne pas confondre avec $('string')) est un autre mécanisme de citation qui permet des séquences d'échappement de type C ANSI et fait une expansion vers une version entre guillemets simples.

Exemple simple:

  $> echo $'aa\'bb'
  aa'bb

  $> alias myvar=$'aa\'bb'
  $> alias myvar
  alias myvar='aa'\''bb'

Dans ton cas:

$> alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''
$> alias rxvt
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Les séquences d'échappement courantes fonctionnent comme prévu:

\'     single quote
\"     double quote
\\     backslash
\n     new line
\t     horizontal tab
\r     carriage return

Vous trouverez ci-dessous la documentation relative à la copie et au collage de man bash(version 4.4):

Les mots de la forme $ 'string' sont traités spécialement. Le mot se développe en chaîne, avec des caractères d'échappement antislash remplacés comme spécifié par la norme C ANSI. Les séquences d'échappement de barre oblique inverse, si elles sont présentes, sont décodées comme suit:

    \a     alert (bell)
    \b     backspace
    \e
    \E     an escape character
    \f     form feed
    \n     new line
    \r     carriage return
    \t     horizontal tab
    \v     vertical tab
    \\     backslash
    \'     single quote
    \"     double quote
    \?     question mark
    \nnn   the eight-bit character whose value is the octal 
           value nnn (one to three digits)
    \xHH   the eight-bit character whose value is the hexadecimal
           value HH (one or two hex digits)
    \uHHHH the Unicode (ISO/IEC 10646) character whose value is 
           the hexadecimal value HHHH (one to four hex digits)
    \UHHHHHHHH the Unicode (ISO/IEC 10646) character whose value 
               is the hexadecimal value HHHHHHHH (one to eight 
               hex digits)
    \cx    a control-x character

Le résultat étendu est guillemet simple, comme si le signe dollar n'était pas présent.


Voir Quotes and escape: ANSI C like strings sur bash-hackers.org wiki pour plus de détails. Notez également que le fichier "Bash Changes" ( aperçu ici ) mentionne beaucoup de changements et de corrections de bogues liés au $'string'mécanisme de cotation.

D'après unix.stackexchange.com Comment utiliser un caractère spécial comme un caractère normal? cela devrait fonctionner (avec quelques variantes) dans bash, zsh, mksh, ksh93 et ​​FreeBSD et busybox sh.


pourrait être utilisé mais la chaîne entre guillemets simple ici n'est pas une vraie chaîne entre guillemets, le contenu de cette chaîne peut être interprété par le shell: echo $'foo\'b!ar'=> !ar': event not found
regilero

2
Sur ma machine > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41

1
Oui, c'est la raison de "mai", je l'ai eu sur un Red Hat 6.4, certainement une ancienne version bash.
regilero

Bash ChangeLog contient de nombreuses corrections de bogues liées à la $'façon la plus simple de l'essayer vous-même sur les anciens systèmes.
mj41

soyez conscient: e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.Tiré de tiswww.case.edu/php/chet/bash/CHANGES . Fonctionne toujours en 4.3.42 mais pas en 4.3.48.
stiller_leser

49

Je ne vois pas l'entrée sur son blog (lien pls?) Mais selon le manuel de référence gnu :

Le fait de mettre des caractères entre guillemets simples ('' ') préserve la valeur littérale de chaque caractère dans les guillemets. Un guillemet simple ne peut pas apparaître entre guillemets simples, même lorsqu'il est précédé d'une barre oblique inverse.

donc bash ne comprendra pas:

alias x='y \'z '

cependant, vous pouvez le faire si vous entourez de guillemets doubles:

alias x="echo \'y "
> x
> 'y


Le contenu entouré de guillemets doubles est en cours d'évaluation, de sorte que le fait de ne placer que des guillemets simples entre guillemets comme suggéré par liori semble être la bonne solution.
Piotr Dobrogost

3
C'est la vraie réponse à la question. Bien que la réponse acceptée puisse fournir une solution, elle répond techniquement à une question qui n'a pas été posée.
Matthew G

3
Matthew, la question était d'échapper aux guillemets simples à l'intérieur des guillemets simples. Cette réponse demande à l'utilisateur de modifier son comportement, et si vous avez un obstacle à l'utilisation de guillemets doubles (comme le suggère le titre de la question), cette réponse n'aiderait pas. C'est assez utile cependant (quoique évident), et en tant que tel mérite un vote positif, mais la réponse acceptée répond au problème précis que Op a posé.
Fernando Cordeiro

Pas besoin de citer une seule citation dans une chaîne de guillemets doubles.
Matthew D. Scholefield

32

Je peux confirmer que l'utilisation '\''d'un guillemet simple à l'intérieur d'une chaîne entre guillemets fonctionne dans Bash, et cela peut être expliqué de la même manière que l'argument "collage" plus haut dans le fil. Supposons que nous ayons une chaîne entre guillemets: 'A '\''B'\'' C'(toutes les guillemets ici sont des guillemets simples). Si elle est passée à l' écho, il imprime ce qui suit: A 'B' C. Dans chacun, '\''le premier guillemet ferme la chaîne entre guillemets actuelle, ce qui suit \'colle un guillemet simple à la chaîne précédente ( \'est un moyen de spécifier un guillemet simple sans démarrer une chaîne entre guillemets), et le dernier guillemet ouvre une autre chaîne entre guillemets simples.


2
C'est trompeur, cette syntaxe '\' 'ne va pas "à l'intérieur" d'une seule chaîne entre guillemets. Dans cette déclaration 'A' \ '' B '\' 'C', vous concaténez 5 chaînes d'échappement et de guillemets simples
teknopaul

1
@teknopaul L'affectation se alias something='A '\''B'\'' C'traduit par somethingêtre une seule chaîne, donc même si le côté droit de l'affectation n'est pas techniquement une seule chaîne, je ne pense pas que cela ait beaucoup d'importance.
Teemu Leisti

Bien que cela fonctionne dans votre exemple, il ne fournit pas techniquement une solution pour insérer un guillemet simple dans une seule chaîne entre guillemets. Vous l'avez déjà expliqué, mais oui, ça marche 'A ' + ' + 'B' + ' + ' C'. En d'autres termes, une solution pour insérer des guillemets simples dans une chaîne entre guillemets devrait me permettre de créer une telle chaîne par elle-même et de l'imprimer. Cependant, cette solution ne fonctionnera pas dans ce cas. STR='\''; echo $STR. Tel que conçu, BASH ne le permet pas vraiment.
krb686

@mikhail_b, oui, '\''fonctionne pour bash. Pourriez-vous indiquer quelles sections de gnu.org/software/bash/manual/bashref.html spécifient un tel comportement?
Jingguo Yao

20

Les deux versions fonctionnent, soit avec concaténation en utilisant le caractère guillemet simple échappé (\ '), soit avec concaténation en enfermant le caractère guillemet simple entre guillemets doubles ("" ").

L'auteur de la question n'a pas remarqué qu'il y avait une citation supplémentaire (') à la fin de sa dernière tentative d'évasion:

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
           │         │┊┊|       │┊┊│     │┊┊│       │┊┊│
           └─STRING──┘┊┊└─STRIN─┘┊┊└─STR─┘┊┊└─STRIN─┘┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      └┴─────────┴┴───┰───┴┴─────────┴┘│
                          All escaped single quotes    │
                                                       │
                                                       ?

Comme vous pouvez le voir dans la belle œuvre d'art ASCII / Unicode précédente, le dernier guillemet simple échappé (\ ') est suivi d'un guillemet simple inutile ('). L'utilisation d'un surligneur de syntaxe comme celui présent dans Notepad ++ peut s'avérer très utile.

Il en va de même pour un autre exemple comme le suivant:

alias rc='sed '"'"':a;N;$!ba;s/\n/, /g'"'"
alias rc='sed '\'':a;N;$!ba;s/\n/, /g'\'

Ces deux belles instances d'alias montrent de manière très complexe et obscurcie comment un fichier peut être aligné. Autrement dit, à partir d'un fichier avec beaucoup de lignes, vous obtenez une seule ligne avec des virgules et des espaces entre le contenu des lignes précédentes. Afin de donner un sens au commentaire précédent, voici un exemple:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214

3
Upwoted pour l'illustration de la table ASCII :)
php-dev

16

Exemple simple d'échapper des citations dans le shell:

$ echo 'abc'\''abc'
abc'abc
$ echo "abc"\""abc"
abc"abc

Cela se fait en terminant un ( ') déjà ouvert , en plaçant un ( \') échappé , puis en ouvrant un autre ( '). Cette syntaxe fonctionne pour toutes les commandes. C'est une approche très similaire à la 1ère réponse.


15

Je n'aborde pas spécifiquement la question des citations parce que, parfois, il est tout simplement raisonnable d'envisager une approche alternative.

rxvt() { urxvt -fg "#${1:-000000}" -bg "#${2:-FFFFFF}"; }

que vous pouvez ensuite appeler comme:

rxvt 123456 654321

l'idée étant que vous pouvez maintenant alias cela sans souci de guillemets:

alias rxvt='rxvt 123456 654321'

ou, si vous devez inclure le #dans tous les appels pour une raison quelconque:

rxvt() { urxvt -fg "${1:-#000000}" -bg "${2:-#FFFFFF}"; }

que vous pouvez ensuite appeler comme:

rxvt '#123456' '#654321'

alors, bien sûr, un alias est:

alias rxvt="rxvt '#123456' '#654321'"

(oups, je suppose que j'ai en quelque sorte abordé la citation :)


1
J'essayais de mettre quelque chose entre guillemets simples qui était entre guillemets doubles qui étaient, à leur tour, entre guillemets simples. Oui. Merci pour votre réponse "essayez une approche différente". Cela a fait la différence.
Clinton Blackmore

1
J'ai 5 ans de retard, mais ne manquez-vous pas une seule citation dans votre dernier alias?
Julien

1
@Julien Je ne vois pas de problème ;-)
nicerobot

11

Puisqu'on ne peut pas mettre de guillemets simples dans des chaînes entre guillemets simples, l'option la plus simple et la plus lisible est d'utiliser une chaîne HEREDOC

command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)

alias rxvt=$command

Dans le code ci-dessus, le HEREDOC est envoyé à la catcommande et la sortie de celui-ci est affectée à une variable via la notation de substitution de commande$(..)

Il est nécessaire de mettre une seule citation autour du HEREDOC car il se $()


J'aurais aimé avoir fait défiler la liste jusqu'ici - j'ai réinventé cette approche et je suis venu ici pour la publier! C'est beaucoup plus propre et plus lisible que toutes les autres approches qui s'échappent. Non, cela ne fonctionnera pas sur certains shells non-bash, comme celui dashqui est le shell par défaut dans les scripts Ubuntu upstart et ailleurs.
Korny

Je vous remercie! que ce que je cherchais, la façon de définir une commande telle qu'elle est via heredoc et de passer la commande auto escape à ssh. BTW cat << COMMAND sans guillemets permet d'interpoler les variables à l'intérieur de la commande et fonctionne également pour cette approche.
Igor Tverdovskiy

10

J'utilise simplement des codes shell .. par exemple \x27ou \\x22selon le cas. Pas de soucis, jamais vraiment.


Pourriez-vous en montrer un exemple en fonctionnement? Pour moi, il imprime juste un littéral x27(sur Centos 6.6)
Will Sheppard

6

La plupart de ces réponses concernent le cas spécifique dont vous parlez. Il y a une approche générale qu'un ami et moi avons développé qui permet arbitraire de citer dans le cas où vous avez besoin de citer bash commandes à travers plusieurs couches d'expansion de la coquille, par exemple, par le biais de ssh, su -c, bash -c, etc. Il y a un noyau vous primitif besoin, ici en bash natif:

quote_args() {
    local sq="'"
    local dq='"'
    local space=""
    local arg
    for arg; do
        echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
        space=" "
    done
}

Cela fait exactement ce qu'il dit: il cite chaque argument individuellement (après l'expansion bash, bien sûr):

$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'

Cela fait la chose évidente pour une couche d'expansion:

$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2

(Notez que les guillemets autour $(quote_args ...)sont nécessaires pour transformer le résultat en un seul argument bash -c.) Et il peut être utilisé plus généralement pour citer correctement à travers plusieurs couches d'expansion:

$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2

L'exemple ci-dessus:

  1. shell-quote chaque argument à l'intérieur quote_argsindividuellement, puis combine la sortie résultante en un seul argument avec les doubles guillemets intérieurs.
  2. shell-quotes bash, -cet le résultat déjà cité une fois de l'étape 1, puis combine le résultat en un seul argument avec les doubles guillemets externes.
  3. envoie ce désordre comme argument à l'extérieur bash -c.

C'est l'idée en un mot. Vous pouvez faire des choses assez compliquées avec cela, mais vous devez faire attention à l'ordre d'évaluation et aux sous-chaînes qui sont citées. Par exemple, ce qui suit fait les mauvaises choses (pour une définition de «mauvais»):

$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure

Dans le premier exemple, bash se développe immédiatement quote_args cd /; pwd 1>&2en deux commandes distinctes quote_args cd /et pwd 1>&2, par conséquent, le CWD est toujours /tmplorsque la pwdcommande est exécutée. Le deuxième exemple illustre un problème similaire pour la globalisation. En effet, le même problème de base se produit avec toutes les extensions bash. Le problème ici est qu'une substitution de commande n'est pas un appel de fonction: elle évalue littéralement un script bash et utilise sa sortie dans le cadre d'un autre script bash.

Si vous essayez d'échapper simplement aux opérateurs shell, vous échouerez car la chaîne résultante transmise à bash -cn'est qu'une séquence de chaînes entre guillemets individuels qui ne sont alors pas interprétées comme des opérateurs, ce qui est facile à voir si vous faites écho à la chaîne qui ont été passés à bash:

$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'

Le problème ici est que vous surestimez. Ce dont vous avez besoin, c'est que les opérateurs ne soient pas entre guillemets en entrée du boîtier bash -c, ce qui signifie qu'ils doivent être en dehors de la $(quote_args ...)substitution de commande.

Par conséquent, ce que vous devez faire dans le sens le plus général est de citer chaque mot de la commande non destiné à être développé au moment de la substitution de commande séparément, et de n'appliquer aucune citation supplémentaire aux opérateurs du shell:

$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success

Une fois que vous avez fait cela, la chaîne entière est un jeu équitable pour continuer à citer des niveaux d'évaluation arbitraires:

$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success

etc.

Ces exemples peuvent sembler surmené étant donné que des mots comme success, sbinet pwdne doivent pas nécessairement être cité shell, mais le point essentiel à retenir lors de l' écriture d' un script prenant entrée arbitraire est que vous voulez citer tout ce que vous n'êtes pas absolument sûr doesn » t ont besoin de citer, parce que vous ne savez jamais quand un utilisateur va jeter dans un Robert'; rm -rf /.

Pour mieux comprendre ce qui se passe sous les couvertures, vous pouvez jouer avec deux petites fonctions d'aide:

debug_args() {
    for (( I=1; $I <= $#; I++ )); do
        echo -n "$I:<${!I}> " 1>&2
    done
    echo 1>&2
}

debug_args_and_run() {
    debug_args "$@"
    "$@"
}

qui énumérera chaque argument d'une commande avant de l'exécuter:

$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

Salut Kyle. Votre solution fonctionnait très bien pour un cas que j'avais, quand je devais passer un groupe d'arguments comme un seul argument: vagrant ssh -c {single-arg} guest. Le {single-arg}doit être traité comme un seul argument car vagabond prend l'argument suivant après comme nom d'invité. La commande ne peut pas être modifiée. Mais j'avais besoin de passer une commande et ses arguments à l'intérieur {single-arg}. Donc , je vous quote_args()cite la commande et ses arguments, et de mettre des guillemets doubles autour du résultat, et cela a fonctionné comme un charme: vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Merci!!!
Andreas Maier

6

À mon humble avis, la vraie réponse est que vous ne pouvez pas échapper aux guillemets simples dans les chaînes entre guillemets simples.

C'est impossible.

Si nous supposons que nous utilisons bash.

Du manuel bash ...

Enclosing characters in single quotes preserves the literal value of each
character within the quotes.  A single quote may not occur
between single quotes, even when preceded by a backslash.

Vous devez utiliser l'un des autres mécanismes d'échappement de chaîne "ou \

Il n'y a rien de magique aliasqui exige qu'il utilise des guillemets simples.

Les deux travaux suivants dans bash.

alias rxvt="urxvt -fg '#111111' -bg '#111111'"
alias rxvt=urxvt\ -fg\ \'#111111\'\ -bg\ \'#111111\'

Ce dernier utilise \ pour échapper au caractère espace.

# 111111 n'a rien de magique non plus qui nécessite des guillemets simples.

Les options suivantes permettent d'obtenir le même résultat que les deux autres options, en ce que l'alias rxvt fonctionne comme prévu.

alias rxvt='urxvt -fg "#111111" -bg "#111111"'
alias rxvt="urxvt -fg \"#111111\" -bg \"#111111\""

Vous pouvez également échapper directement au # gênant

alias rxvt="urxvt -fg \#111111 -bg \#111111"

"La vraie réponse est que vous ne pouvez pas échapper aux guillemets simples dans les chaînes entre guillemets simples." C'est techniquement vrai. Mais vous pouvez avoir une solution qui commence par un guillemet simple, se termine par un guillemet simple et ne contient que des guillemets simples au milieu. stackoverflow.com/a/49063038
wisbucky

Pas en s'échappant, seulement par concaténation.
teknopaul

4

Dans l'exemple donné, utilisez simplement des guillemets doubles au lieu de guillemets simples comme mécanisme d'échappement externe:

alias rxvt="urxvt -fg '#111111' -bg '#111111'"

Cette approche convient à de nombreux cas où vous souhaitez simplement passer une chaîne fixe à une commande: vérifiez simplement comment le shell interprétera la chaîne entre guillemets doubles à travers un echo, et échappez les caractères avec barre oblique inverse si nécessaire.

Dans l'exemple, vous verriez que les guillemets doubles sont suffisants pour protéger la chaîne:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'

4

De toute évidence, il serait plus facile de simplement entourer de guillemets doubles, mais quel est le défi à cela? Voici la réponse en utilisant uniquement des guillemets simples. J'utilise une variable au lieu de aliasc'est donc plus facile à imprimer pour la preuve, mais c'est la même chose que d'utiliseralias .

$ rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'
$ echo $rxvt
urxvt -fg '#111111' -bg '#111111'

Explication

La clé est que vous pouvez fermer le devis unique et le rouvrir autant de fois que vous le souhaitez. Par exemple, foo='a''b'c'est la même chose quefoo='ab' . Vous pouvez donc fermer le guillemet simple, ajouter un guillemet simple littéral\' , puis rouvrir le guillemet simple suivant.

Diagramme de répartition

Ce diagramme le montre clairement en utilisant des crochets pour montrer où les guillemets simples sont ouverts et fermés. Les citations ne sont pas "imbriquées" comme peuvent l'être les parenthèses. Vous pouvez également faire attention à la surbrillance des couleurs, qui est correctement appliquée. Les cordes citées sont marron, tandis que le \'noir est.

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'    # original
[^^^^^^^^^^] ^[^^^^^^^] ^[^^^^^] ^[^^^^^^^] ^    # show open/close quotes
 urxvt -fg   ' #111111  '  -bg   ' #111111  '    # literal characters remaining

(Il s'agit essentiellement de la même réponse que celle d'Adrian, mais je pense que cela l'explique mieux. De plus, sa réponse contient 2 guillemets simples superflus à la fin.)


+1 pour l'utilisation de la '\''méthode que je recommande par rapport à la '"'"'méthode qui est souvent plus difficile à lire pour les humains.
mtraceur

3

Voici une élaboration sur The One True Answer référencée ci-dessus:

Parfois, je téléchargerai en utilisant rsync sur ssh et je devrai échapper un nom de fichier avec un 'TWICE! (OMG!) Une fois pour bash et une fois pour ssh. Le même principe d'alternance des délimiteurs de devis est à l'œuvre ici.

Par exemple, disons que nous voulons obtenir: Les histoires de Louis Theroux ...

  1. D'abord, vous mettez Louis Theroux entre guillemets simples pour bash et guillemets doubles pour ssh: '"Louis Theroux"'
  2. Ensuite, vous utilisez des guillemets simples pour échapper à un guillemet double "" "
  3. L'utilisation de guillemets doubles pour échapper à l'apostrophe "'"
  4. Ensuite, répétez # 2, en utilisant des guillemets simples pour échapper à un guillemet double "" "
  5. Ensuite, mettez LA Stories entre guillemets simples pour bash et guillemets doubles pour ssh: '"LA Stories"'

Et voici! Vous vous retrouvez avec ceci:

rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'

ce qui représente énormément de travail pour un tout petit - mais voilà


3
shell_escape () {
    printf '%s' "'${1//\'/\'\\\'\'}'"
}

Explication de la mise en œuvre:

  • guillemets doubles afin que nous puissions facilement sortir des guillemets simples et utiliser la ${...}syntaxe

  • La recherche et le remplacement de bash ressemblent à ceci: ${varname//search/replacement}

  • nous remplaçons 'par'\''

  • '\''encode un single 'comme ceci:

    1. ' termine la citation unique

    2. \'code un '(la barre oblique inverse est nécessaire car nous ne sommes pas entre guillemets)

    3. ' recommence le guillemet simple

    4. bash concatène automatiquement les chaînes sans espace blanc entre

  • il y a un \avant chaque \et 'parce que ce sont les règles d'échappement pour ${...//.../...}.

string="That's "'#@$*&^`(@#'
echo "original: $string"
echo "encoded:  $(shell_escape "$string")"
echo "expanded: $(bash -c "echo $(shell_escape "$string")")"

PS Encode toujours les chaînes entre guillemets simples car elles sont bien plus simples que les chaînes entre guillemets doubles.


2

Une autre façon de résoudre le problème de trop de couches de devis imbriqués:

Vous essayez de trop entasser dans un espace trop petit, utilisez donc une fonction bash.

Le problème est que vous essayez d'avoir trop de niveaux d'imbrication et que la technologie d'alias de base n'est pas assez puissante pour s'adapter. Utilisez une fonction bash comme celle-ci pour que les guillemets simples et doubles et les paramètres passés soient tous traités normalement comme nous nous y attendrions:

lets_do_some_stuff() {
    tmp=$1                       #keep a passed in parameter.
    run_your_program $@          #use all your passed parameters.
    echo -e '\n-------------'    #use your single quotes.
    echo `date`                  #use your back ticks.
    echo -e "\n-------------"    #use your double quotes.
}
alias foobarbaz=lets_do_some_stuff

Ensuite, vous pouvez utiliser vos variables $ 1 et $ 2 ainsi que les guillemets simples, doubles et les ticks arrière sans vous soucier de la fonction d'alias qui détruit leur intégrité.

Ce programme imprime:

el@defiant ~/code $ foobarbaz alien Dyson ring detected @grid 10385
alien Dyson ring detected @grid 10385
-------------
Mon Oct 26 20:30:14 EDT 2015
-------------

2

Si GNU Parallel est installé, vous pouvez utiliser sa citation interne:

$ parallel --shellquote
L's 12" record
<Ctrl-D>
'L'"'"'s 12" record'
$ echo 'L'"'"'s 12" record'
L's 12" record

Depuis la version 20190222, vous pouvez même --shellquoteplusieurs fois:

$ parallel --shellquote --shellquote --shellquote
L's 12" record
<Ctrl-D>
'"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
$ eval eval echo '"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
L's 12" record

Il cite la chaîne dans tous les shells pris en charge (pas seulement bash).


1

Cette fonction:

quote () 
{ 
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

permet de citer l' 'intérieur '. Utilisez comme ceci:

$ quote "urxvt -fg '#111111' -bg '#111111'"
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Si la ligne à citer devient plus complexe, comme les guillemets doubles mélangés à des guillemets simples, il peut devenir assez difficile d'obtenir la chaîne à citer dans une variable. Lorsque de tels cas apparaissent, écrivez la ligne exacte que vous devez citer dans un script (similaire à ceci).

#!/bin/bash

quote ()
{
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

while read line; do
    quote "$line"
done <<-\_lines_to_quote_
urxvt -fg '#111111' -bg '#111111'
Louis Theroux's LA Stories
'single quote phrase' "double quote phrase"
_lines_to_quote_

Sortira:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
'Louis Theroux'\''s LA Stories'
''\''single quote phrase'\'' "double quote phrase"'

Toutes les chaînes correctement citées dans des guillemets simples.


1

Si vous générez la chaîne shell dans Python 2 ou Python 3, les éléments suivants peuvent aider à citer les arguments:

#!/usr/bin/env python

from __future__ import print_function

try:  # py3
    from shlex import quote as shlex_quote
except ImportError:  # py2
    from pipes import quote as shlex_quote

s = """foo ain't "bad" so there!"""

print(s)
print(" ".join([shlex_quote(t) for t in s.split()]))

Cela produira:

foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'

1

Voici mes deux cents - dans le cas où l'on veut être sh-portable, pas seulement- bashspécifique (la solution n'est cependant pas trop efficace, car elle démarre un programme externe - sed):

  • mettez ça quote.sh(ou juste quote) quelque part sur votre PATH:
# cela fonctionne avec une entrée standard (stdin)
citation() {
  echo -n "'";
  sed 's / \ ([' "'"'] ['"'" '] * \) /' "'"' "\ 1" '"'" '/ g';
  echo -n "'"
}

cas "$ 1" dans
 -) citation ;;
 *) echo "usage: cat ... | quote - # simple-quotes input for Bourne shell" 2> & 1 ;;
esac

Un exemple:

$ echo -n "Bonjour, mec!" | ./quote.sh -
'Bonne journée mon pote!'

Et, bien sûr, cela se reconvertit:

$ echo 'G' "'"' jour, mec! '
Bonne journée mon pote!

Explication: fondamentalement, nous devons entourer l'entrée de guillemets ', puis remplacer également toute guillemet simple à l'intérieur par ce micro-monstre: '"'"'(terminer la guillemet d'ouverture par un appariement ', échapper à la guillemet simple trouvé en l'enveloppant avec des guillemets doubles - "'", et puis enfin émettre un nouveau devis unique d'ouverture ', ou pseudo-notation: ' + "'" + ' == '"'"')

Une façon standard de le faire serait d'utiliser sedla commande de substitution suivante:

s/\(['][']*\)/'"\1"'/g 

Un petit problème, cependant, est que pour utiliser cela dans le shell, il faut échapper à tous ces caractères de guillemet simple dans l'expression sed elle-même - ce qui conduit à quelque chose comme

sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g' 

(et une bonne façon de construire ce résultat est d'alimenter l'expression originale s/\(['][']*\)/'"\1"'/gdes scripts de Kyle Rose ou de George V. Reilly).

Enfin, il est logique de s'attendre à ce que l'entrée provienne stdin- car la passer à travers des arguments de ligne de commande pourrait déjà être trop difficile.

(Oh, et peut-être que nous voulons ajouter un petit message d'aide afin que le script ne se bloque pas lorsque quelqu'un l'exécute simplement en se ./quote.sh --helpdemandant ce qu'il fait.)


0

Voici une autre solution. Cette fonction prendra un seul argument et le citera de manière appropriée en utilisant le caractère de guillemet simple, comme l'explique la réponse votée ci-dessus:

single_quote() {
  local quoted="'"
  local i=0
  while [ $i -lt ${#1} ]; do
    local ch="${1:i:1}"
    if [[ "$ch" != "'" ]]; then
      quoted="$quoted$ch"
    else
      local single_quotes="'"
      local j=1
      while [ $j -lt ${#1} ] && [[ "${1:i+j:1}" == "'" ]]; do
        single_quotes="$single_quotes'"
        ((j++))
      done
      quoted="$quoted'\"$single_quotes\"'"
      ((i+=j-1))
    fi
    ((i++))
  done
  echo "$quoted'"
}

Vous pouvez donc l'utiliser de cette façon:

single_quote "1 2 '3'"
'1 2 '"'"'3'"'"''

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.