Expansión de variables dentro de comillas simples en un comando en Bash

393

Quiero ejecutar un comando desde un script bash que tiene comillas simples y algunos otros comandos dentro de las comillas simples y una variable.

p.ej repo forall -c '....$variable'

En este formato, $se escapa y la variable no se expande.

Intenté las siguientes variaciones pero fueron rechazadas:

repo forall -c '...."$variable" '

repo forall -c " '....$variable' "

" repo forall -c '....$variable' "

repo forall -c "'" ....$variable "'"

Si sustituyo el valor en lugar de la variable, el comando se ejecuta muy bien.

Por favor dime dónde me estoy equivocando.

Rachit
fuente
33
repo forall -c ' ...before... '"$variable"' ...after...'
n. 'pronombres' m.
2
@nm las comillas simples son parte del comando repo. no creo que esto funcione
Rachit
1
bashCome comillas simples. O no estás dentro bash, o las comillas simples no son parte del repocomando.
n. 'pronombres' m.
No estoy seguro de que te entiendo, pero en caso de que las comillas simples necesiten estar allí, no volvería a reposar -c "'... antes ..." $ variable "... después de ...'" ¿funciona?
ecv
Es un script bash que se ejecuta en bash shell y el comando repo forall toma parámetros dentro de comillas simples. Si sustituyo el valor real, el comando funciona bien.
Rachit

Respuestas:

621

Dentro de las comillas simples todo se conserva literalmente, sin excepción.

Eso significa que debe cerrar las comillas, insertar algo y luego volver a ingresar.

'before'"$variable"'after'
'before'"'"'after'
'before'\''after'

La concatenación de palabras se realiza simplemente por yuxtaposición. Como puede verificar, cada una de las líneas anteriores es una sola palabra para el shell. Las comillas (comillas simples o dobles, según la situación) no aíslan las palabras. Solo se usan para deshabilitar la interpretación de varios caracteres especiales, como espacios en blanco $, ;... Para un buen tutorial sobre citas, vea la respuesta de Mark Reed. También relevante: ¿Qué caracteres se deben escapar en bash?

No concatene cadenas interpretadas por un shell

Debe evitar absolutamente la creación de comandos de shell concatenando variables. Esta es una mala idea similar a la concatenación de fragmentos SQL (inyección SQL).

Por lo general, es posible tener marcadores de posición en el comando y proporcionar el comando junto con variables para que el destinatario pueda recibirlos de la lista de argumentos de invocación.

Por ejemplo, lo siguiente es muy inseguro. NO HAGAS ESTO

script="echo \"Argument 1 is: $myvar\""
/bin/sh -c "$script"

Si el contenido de $myvarno es confiable, aquí hay un exploit:

myvar='foo"; echo "you were hacked'

En lugar de la invocación anterior, use argumentos posicionales. La siguiente invocación es mejor, no es explotable:

script='echo "arg 1 is: $1"'
/bin/sh -c "$script" -- "$myvar"

Tenga en cuenta el uso de marcas simples en la asignación de script, lo que significa que se toma literalmente, sin expansión variable o cualquier otra forma de interpretación.

Jo So
fuente
@ Jo.SO que no funcionará. porque el comando repo tomará solo los parámetros hasta que se cierren las primeras comillas. Cualquier cosa posterior será ignorada para el repositorio y generará otro error.
Rachit
8
@Rachit: el shell no funciona así. "some"thing' like'thises todo una palabra para la concha. Las comillas no terminan nada en lo que respecta al análisis, y no hay forma de que un comando llamado por el shell indique qué se citó cómo.
Mark Reed
3
Esa segunda oración ("ni siquiera puedes escapar de las comillas simples") hace que las comillas simples sean los agujeros negros del Shell.
@Evert: no sé cómo decirlo mejor. He eliminado la oración.
Jo So
@JoSo Es una lástima: no, la broma se cae.
97

Al repocomando no le importa qué tipo de citas obtiene. Si necesita expansión de parámetros, use comillas dobles. Si eso significa que terminas teniendo que hacer una barra diagonal inversa de muchas cosas, usa comillas simples para la mayor parte, y luego sal de ellas y entra en dobles para la parte donde necesitas que ocurra la expansión.

repo forall -c 'literal stuff goes here; '"stuff with $parameters here"' more literal stuff'

La explicación sigue, si está interesado.

Cuando ejecuta un comando desde el shell, lo que ese comando recibe como argumentos es una matriz de cadenas terminadas en cero. Esas cadenas pueden contener absolutamente cualquier carácter no nulo.

Pero cuando el shell construye ese conjunto de cadenas desde una línea de comando, interpreta algunos caracteres especialmente; Esto está diseñado para hacer que los comandos sean más fáciles (de hecho, posibles) de escribir. Por ejemplo, los espacios normalmente indican el límite entre cadenas en la matriz; por esa razón, los argumentos individuales a veces se llaman "palabras". Pero, sin embargo, un argumento puede tener espacios; solo necesita alguna forma de decirle al shell que es lo que quiere.

Puedes usar una barra invertida delante de cualquier personaje (incluido el espacio u otra barra invertida) para indicarle al shell que trate a ese personaje literalmente. Pero si bien puedes hacer algo como esto:

echo \"Thank\ you.\ \ That\'ll\ be\ \$4.96,\ please,\"\ said\ the\ cashier

... puede ser agotador. Entonces el shell ofrece una alternativa: comillas. Estos vienen en dos variedades principales.

Las comillas dobles se denominan "comillas de agrupación". Evitan que se expandan los comodines y los alias, pero principalmente son para incluir espacios en una palabra. Otras cosas como la expansión de parámetros y comandos (el tipo de cosas señaladas por a $) aún suceden. Y, por supuesto, si desea una comilla doble literal dentro de comillas dobles, debe hacer una barra diagonal inversa:

echo "\"Thank you. That'll be \$4.96, please,\" said the cashier"

Las comillas simples son más draconianas. Todo entre ellos se toma completamente literalmente, incluidas las barras invertidas. No hay absolutamente ninguna manera de obtener una comilla simple literal dentro de comillas simples.

Afortunadamente, las comillas en el shell no son delimitadores de palabras ; por sí mismos, no terminan una palabra. Puede entrar y salir de comillas, incluso entre diferentes tipos de comillas, dentro de la misma palabra para obtener el resultado deseado:

echo '"Thank you. That'\''ll be $4.96, please," said the cashier'

Así que eso es más fácil: muchas menos barras invertidas, aunque la secuencia de comillas cerradas, comillas simples literales, comillas simples, comillas abiertas y simples requiere algo de tiempo para acostumbrarse.

Los shells modernos han agregado otro estilo de comillas no especificado por el estándar POSIX, en el cual la comilla simple principal está precedida por un signo de dólar. Las cadenas así citadas siguen convenciones similares a los literales de cadena en el lenguaje de programación ANSI C y, por lo tanto, a veces se llaman "cadenas ANSI" y el $'... 'par "citas ANSI". Dentro de tales cadenas, el consejo anterior sobre las barras invertidas literalmente ya no se aplica. En cambio, se vuelven especiales de nuevo: no solo puede incluir una comilla simple o una barra invertida literal al anteponer una barra invertida, sino que el shell también expande los escapes de caracteres ANSI C (como \npara una nueva línea, \tpara tabulación y \xHHpara el personaje con código hexadecimalHH) Sin embargo, de lo contrario, se comportan como cadenas entre comillas simples: no se realiza ninguna sustitución de parámetros o comandos:

echo $'"Thank you.  That\'ll be $4.96, please," said the cashier'

Lo importante a tener en cuenta es que la cadena única recibida como argumento del echocomando es exactamente la misma en todos estos ejemplos. Después de que el shell termine de analizar una línea de comando, no hay forma de que el comando que se ejecuta diga cómo se citó. Incluso si quisiera.

Mark Reed
fuente
2
Si. Entiendo ahora. Funciona perfectamente ¡Muchas gracias por tu ayuda!
Rachit
66
Esta respuesta fue asombrosa.
Vinícius Ferrão
1
¿Es $'string'compatible el formato POSIX? Además, ¿hay un nombre para eso?
Comodín el
2
@Wildcard $'string'es una extensión que no es POSIX, a la que he visto referido como "cadenas ANSI"; He incorporado esos hechos en la respuesta. Tales cadenas funcionan en la mayoría de los shells compatibles con Bourne modernos: bash, dash, ksh (tanto AT&T como PD), y zsh todos los admiten.
Mark Reed
1
Enlace de regreso: ¿qué caracteres se requieren para escapar en los argumentos de la línea de comandos? en el intercambio de pila de Unix y Linux.
Comodín
3

A continuación se muestra lo que funcionó para mí:

QUOTE="'"
hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"
Manmohan
fuente
2
Espero TBL_HDFS_DIR_PATH no se proporcionó información de usuario por cierto, esto permitiría una inyección sql agradable ...
domenukk
1
Poner QUOTEen una variable separada es completamente superfluo; Las comillas simples dentro de las comillas dobles son solo un carácter regular. (Además, no use mayúsculas para sus variables privadas).
tripleee
2

EDITAR: (Según los comentarios en cuestión :)

He estado investigando esto desde entonces. Tuve la suerte de tener un repositorio por ahí. Aún no me queda claro si necesita encerrar sus comandos entre comillas simples por la fuerza. Investigué la sintaxis del repositorio y no creo que sea necesario. Podrías usar comillas dobles alrededor de tu comando, y luego usar las comillas simples y dobles que necesites dentro, siempre que escapes las dobles.

ecv
fuente
Gracias Kevin por tu ayuda.
Rachit
2

solo usa printf

en vez de

repo forall -c '....$variable'

use printf para reemplazar el token de la variable con la variable expandida.

Por ejemplo:

template='.... %s'

repo forall -c $(printf "${template}" "${variable}")
AndrewD
fuente
Esto está roto; printfen realidad no le compra nada aquí, y no citar la sustitución del comando causa nuevos problemas de cotización.
tripleee
0

Las variables pueden contener comillas simples.

myvar=\'....$variable\'

repo forall -c $myvar
usuario3734617
fuente
Pueden hacerlo, pero esta no es la forma correcta de hacerlo: aún debe poner "$myvar"comillas dobles.
tripleee
-2

¿Esto funciona para tí?

eval repo forall -c '....$variable'
drgnfr
fuente
8
¿Lo hace por ti? Esta pregunta tiene cinco años y ya tiene algunas respuestas, incluso una aceptada ...
Nico Haase