Escapar de una variable para usar como contenido de otro script

20

Esta pregunta no se trata de cómo escribir un literal de cadena con escape correcto. No pude encontrar ninguna pregunta relacionada que no se trata de cómo escapar de las variables para el consumo directo dentro de un script o por otros programas.

Mi objetivo es habilitar un script para generar otros scripts. Esto se debe a que las tareas en los scripts generados se ejecutarán en cualquier lugar de 0 a n veces en otra máquina, y los datos a partir de los cuales se generan pueden cambiar antes de ejecutarse (nuevamente), por lo que realizar las operaciones directamente, a través de una red no trabajo.

Dada una variable conocida que puede contener caracteres especiales como comillas simples, necesito escribirla como un literal de cadena completamente escapado, por ejemplo, una variable que foocontenga bar'bazdebería aparecer en el script generado como:

qux='bar'\''baz'

que se escribiría agregando "qux=$foo_esc"a las otras líneas del guión. Lo hice usando Perl así:

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

Pero esto parece una exageración.

No he tenido éxito en hacerlo solo con bash. He intentado muchas variaciones de estos:

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

pero aparecen barras diagonales adicionales en la salida (cuando lo hago echo "$foo"), o causan un error de sintaxis (esperando una entrada adicional si se realiza desde el shell).

Walf
fuente
aliasy / o setbastante universalmente
mikeserv
@mikeserv Lo siento, no estoy seguro de lo que quieres decir.
Walf
alias "varname=$varname" varnameovar=value set
mikeserv
2
@mikeserv Eso no es suficiente contexto para entender lo que se supone que debe hacer su sugerencia, ni cómo lo usaría como un método genérico para escapar de cualquier variable. Es un problema resuelto, amigo.
Walf

Respuestas:

26

Bash tiene una opción de expansión de parámetros para exactamente este caso :

${parameter@Q}La expansión es una cadena que es el valor del parámetro citado en un formato que se puede reutilizar como entrada.

Entonces en este caso:

foo_esc="${foo@Q}"

Esto es compatible con Bash 4.4 y versiones posteriores. También hay varias opciones para otras formas de expansión y para generar específicamente declaraciones de asignación completas ( @A).

Michael Homer
fuente
77
Aseado, pero solo tiene 4.2 que da bad substitution.
Walf
3
El equivalente de shell Z es "${foo:q}".
JdeBP
¡Realmente salve mi vida! "${foo@Q}"¡trabajos!
hao
@JdeBP ese equivalente de Z shell no funciona. ¿Alguna otra idea para zsh?
Steven Shaw
1
Encontré la respuesta: "$ {(@ qq) foo}"
Steven Shaw
10

Bash proporciona una printforden interna con el %qespecificador de formato, que realiza shell de escape para que, incluso en (<4.0) versiones anteriores 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\`]

Este truco también se puede usar para devolver matrices de datos de una función:

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\ \)]

Tenga en cuenta que el Bash printfincorporado es diferente de la printfutilidad que viene incluida con la mayoría de los sistemas operativos tipo Unix. Si, por alguna razón, el printfcomando invoca la utilidad en lugar de la integrada, siempre puede ejecutarla builtin printf.

Dejay Clayton
fuente
No estoy seguro de cómo eso ayuda si lo que necesitaría impreso sería 'Ne'\''er do well', etc., es decir, citas incluidas en la salida.
Walf
1
@Walf Creo que no estás entendiendo que las dos formas son equivalentes, y ambas son tan seguras como la otra. Por ejemplo, [[ 'Ne'\''er do well' == Ne\'er\ do\ well ]] && echo 'equivalent!'se hará ecoequivalent!
Dejay Clayton
Extrañé eso: P, sin embargo, prefiero el formulario citado ya que es más fácil de leer en un visor / editor de resaltado de sintaxis.
Walf
@Walf parece que su enfoque es bastante peligroso, teniendo en cuenta que en su ejemplo Perl, pasar un valor similar 'hello'da como resultado un valor incorrecto ''\''hello'', que tiene una cadena vacía inicial innecesaria (las dos primeras comillas simples) y una comilla simple final inapropiada.
Dejay Clayton el
1
@Walf, para aclarar, $'escape-these-chars'es la característica de comillas ANSI-C de Bash que hace que se escapen todos los caracteres dentro de la cadena especificada. Por lo tanto, para crear fácilmente un literal de cadena que contenga una nueva línea dentro del nombre de archivo (por ejemplo $'first-line\nsecond-line'), usar \ndentro de esta construcción.)
Dejay Clayton
8

Supongo que no hice RTFM. Se puede hacer así:

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

Luego echo "$foo_esc"da lo esperado'bar'\''baz'


Cómo lo estoy usando realmente es con una función:

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

...

foo_esc="`esc_var "$foo"`"

Modificando esto para usar la printfsolución integrada de Dejay:

function esc_vars {
    printf '%q' "$@"
}
Walf
fuente
3
Usaría la solución de @ michael-homer si mi versión lo admitiera.
Walf
4

Hay varias soluciones para citar un valor var:

  1. alias
    En la mayoría de los shells (donde está disponible el alias) (excepto csh, tcsh y probablemente otros como csh):

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

    Sí, esto funciona en muchos shdepósitos similares a guiones o cenizas.

  2. establecer
    también en la mayoría de conchas (de nuevo, no CSH):

    $ qux=bar\'baz
    $ set | grep '^qux='
    qux='bar'\''baz'
  3. composición tipográfica
    en algunos shells (ksh, bash y zsh al menos):

    $ qux=bar\'baz
    $ typeset -p qux
    typeset qux='bar'\''baz'             # this is zsh, quoting style may
                                         # be different for other shells.
  4. exportar
    primero hacer:

    export qux=bar\'baz

    Luego use:
    export -p | grep 'qux=' export -p | grep 'qux='
    export -p qux

  5. se puede utilizar comillas
    echo "${qux@Q}"
    echo "${(qq)qux}" # de una a cuatro q.

Isaac
fuente
El enfoque de alias es inteligente y parece que está especificado por POSIX. Para una portabilidad máxima, creo que este es el camino a seguir. Creo que las sugerencias relacionadas grepcon exporto setpueden interrumpir variables que contienen nuevas líneas incrustadas.
jw013