Échapper une variable pour l'utiliser comme contenu d'un autre script

20

Cette question n'est pas sur la façon d'écrire un littéral de chaîne correctement échappé. Je n'ai trouvé aucune question connexe qui ne concerne pas comment échapper des variables pour une consommation directe dans un script ou par d'autres programmes.

Mon objectif est de permettre à un script de générer d'autres scripts. En effet, les tâches dans les scripts générés s'exécuteront de 0 à n fois sur une autre machine, et les données à partir desquelles elles sont générées peuvent changer avant d'être exécutées (à nouveau), de sorte que les opérations directement, sur un réseau ne fonctionne pas.

Étant donné une variable connue qui peut contenir des caractères spéciaux tels que des guillemets simples, je dois l'écrire comme un littéral de chaîne complètement échappé, par exemple une variable foocontenant bar'bazdevrait apparaître dans le script généré comme:

qux='bar'\''baz'

qui serait écrit en ajoutant "qux=$foo_esc"aux autres lignes de script. Je l'ai fait en utilisant Perl comme ceci:

foo_esc="'`perl -pe 's/('\'')/\\1\\\\\\1\\1/g' <<<"$foo"`'"

mais cela semble exagéré.

Je n'ai pas réussi à le faire avec bash seul. J'ai essayé de nombreuses variantes de celles-ci:

foo_esc="'${file//\'/\'\\\'\'}'"
foo_esc="'${file//\'/'\\''}'"

mais soit des barres obliques supplémentaires apparaissent dans la sortie (quand je le fais echo "$foo"), soit elles provoquent une erreur de syntaxe (en attendant des entrées supplémentaires si elles sont effectuées à partir du shell).

Walf
la source
aliaset / ou setassez universellement
mikeserv
@mikeserv Désolé, je ne sais pas ce que vous voulez dire.
Walf
alias "varname=$varname" varnameouvar=value set
mikeserv
2
@mikeserv Ce n'est pas assez de contexte pour comprendre ce que votre suggestion est censée faire, ni comment je l'utiliserais comme méthode générique pour échapper à une variable. C'est un problème résolu, mec.
Walf

Réponses:

26

Bash a une option d'extension de paramètre pour exactement ce cas :

${parameter@Q}L'expansion est une chaîne qui est la valeur du paramètre cité dans un format qui peut être réutilisé en entrée.

Donc dans ce cas:

foo_esc="${foo@Q}"

Ceci est pris en charge dans Bash 4.4 et versions ultérieures. Il existe également plusieurs options pour d'autres formes d'extension et pour générer spécifiquement des instructions d'affectation complètes ( @A).

Michael Homer
la source
7
Neat, mais seulement 4.2 qui donne bad substitution.
Walf
3
L'équivalent du shell Z est "${foo:q}".
JdeBP
Vraiment me sauver la vie! "${foo@Q}"travaux!
hao
@JdeBP que l'équivalent du shell Z ne fonctionne pas. D'autres idées pour zsh?
Steven Shaw
1
J'ai trouvé la réponse: "$ {(@ qq) foo}"
Steven Shaw
10

Bash fournit un printfspécificateur de %qformat intégré , qui effectue l'échappement du shell pour vous, même dans les anciennes versions (<4.0) de Bash:

printf '[%q]\n' "Ne'er do well"
# Prints [Ne\'er\ do\ well]

printf '[%q]\n' 'Sneaky injection $( whoami ) `ls /root`'
# Prints [Sneaky\ injection\ \$\(\ whoami\ \)\ \`ls\ /root\`]

Cette astuce peut également être utilisée pour renvoyer des tableaux de données à partir d'une fonction:

function getData()
{
  printf '%q ' "He'll say hi" 'or `whoami`' 'and then $( byebye )'
}

declare -a DATA="( $( getData ) )"
printf 'DATA: [%q]\n' "${DATA[@]}"
# Prints:
# DATA: [He\'ll\ say\ hi]
# DATA: [or\ \`whoami\`]
# DATA: [and\ then\ \$\(\ byebye\ \)]

Notez que le Bash printfintégré est différent de l' printfutilitaire fourni avec la plupart des systèmes d'exploitation de type Unix. Si, pour une raison quelconque, la printfcommande appelle l'utilitaire au lieu du module intégré, vous pouvez toujours l'exécuter à la builtin printfplace.

Dejay Clayton
la source
Je ne sais pas comment cela peut aider si ce que j'avais besoin d' imprimer serait 'Ne'\''er do well', etc., c'est-à-dire des citations incluses dans la sortie.
Walf
1
@Walf Je pense que vous ne comprenez pas que les deux formes sont équivalentes et que les deux sont parfaitement aussi sûres l'une que l'autre. Par exemple, [[ 'Ne'\''er do well' == Ne\'er\ do\ well ]] && echo 'equivalent!'fera échoequivalent!
Dejay Clayton
Cela m'a manqué: P mais je préfère le formulaire cité car il est plus facile à lire dans un visualiseur / éditeur mettant en évidence la syntaxe.
Walf
@Walf, il semble que votre approche soit assez dangereuse, étant donné que dans votre exemple Perl, le passage d'une valeur comme 'hello'entraîne une valeur incorrecte ''\''hello'', qui a une chaîne vide de début inutile (les deux premiers guillemets simples) et une guillemet simple de fin inappropriée.
Dejay Clayton
1
@Walf, pour clarification, $'escape-these-chars'est la fonction de citation ANSI-C de Bash qui provoque l' échappement de tous les caractères de la chaîne spécifiée. Ainsi, pour créer facilement un littéral de chaîne qui contient une nouvelle ligne dans le nom de fichier (par exemple $'first-line\nsecond-line'), utiliser \ndans cette construction.
Dejay Clayton
8

Je suppose que je n'ai pas RTFM. Cela peut se faire comme suit:

q_mid=\'\\\'\'
foo_esc="'${foo//\'/$q_mid}'"

echo "$foo_esc"Donne ensuite l'attendu'bar'\''baz'


Comment je l'utilise réellement avec une fonction:

function esc_var {
    local mid_q=\'\\\'\'
    printf '%s' "'${1//\'/$mid_q}'"
}

...

foo_esc="`esc_var "$foo"`"

Modifier cela pour utiliser la printfsolution intégrée de Dejay:

function esc_vars {
    printf '%q' "$@"
}
Walf
la source
3
J'utiliserais la solution @ michael-homer si ma version la supportait.
Walf
4

Il existe plusieurs solutions pour citer une valeur var:

  1. alias
    Dans la plupart des shells (où l'alias est disponible) (sauf csh, tcsh et probablement d'autres comme csh):

    $ alias qux=bar\'baz
    $ alias qux
    qux='bar'\''baz'

    Oui, cela fonctionne dans de nombreuses shcoquilles comme le tableau de bord ou la cendre.

  2. mettre
    également dans la plupart des coquilles (encore une fois, non csh):

    $ qux=bar\'baz
    $ set | grep '^qux='
    qux='bar'\''baz'
  3. typeset
    Dans quelques coquilles (ksh, bash et zsh au moins):

    $ qux=bar\'baz
    $ typeset -p qux
    typeset qux='bar'\''baz'             # this is zsh, quoting style may
                                         # be different for other shells.
  4. Exporter d'
    abord faire:

    export qux=bar\'baz

    Ensuite, utilisez:
    export -p | grep 'qux=' export -p | grep 'qux='
    export -p qux

  5. citer
    echo "${qux@Q}"
    echo "${(qq)qux}" # de un à quatre q peut être utilisé.

Isaac
la source
L'approche d'alias est intelligente et semble être spécifiée par POSIX. Pour une portabilité maximale, je pense que c'est la voie à suivre. Je crois que les suggestions concernant grepavec exportou setpeuvent briser sur les variables contenant caractère de nouvelle ligne.
jw013