Cómo escapar de comillas simples dentro de cadenas entre comillas simples


1018

Digamos que tienes un Bash aliascomo:

alias rxvt='urxvt'

que funciona bien

Sin embargo:

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

no funcionará, y tampoco lo hará:

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

Entonces, ¿cómo terminas haciendo coincidir las comillas de apertura y cierre dentro de una cadena una vez que has escapado de comillas?

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

parece desgarbado, aunque representaría la misma cadena si se le permite concatenarlos así.


16
¿Te das cuenta de que no necesitas usar comillas simples para el alias? Las comillas dobles son mucho más fáciles.
teknopaul


3
Las comillas dobles anidadas se pueden escapar, "\""por lo que deben usarse con preferencia a la respuesta de @ liori siempre que sea posible.
alan

77
Las comillas dobles se comportan de manera bastante diferente a las comillas simples en * nix (incluyendo Bash y herramientas relacionadas como Perl), por lo que sustituir las comillas dobles siempre que haya un problema con las comillas simples NO es una buena solución. Las comillas dobles especifican $ ... las variables deben sustituirse antes de la ejecución, mientras que las comillas simples especifican $ ... deben tratarse literalmente.
Chuck Kollars

Si estás pensando, usé comillas dobles pero aún no funciona , busca tu script nuevamente.
Samy Bencherif

Respuestas:


1456

Si realmente desea utilizar comillas simples en la capa más externa, recuerde que puede pegar ambos tipos de comillas. Ejemplo:

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

Explicación de cómo '"'"'se interpreta como justo ':

  1. ' Termine la primera cita que usa comillas simples.
  2. " Comience la segunda cita, usando comillas dobles.
  3. ' Personaje citado
  4. " Termine la segunda cita, usando comillas dobles.
  5. ' Comience la tercera cita, usando comillas simples.

Si no coloca espacios en blanco entre (1) y (2), o entre (4) y (5), el shell interpretará esa cadena como una palabra larga.


55
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"¡Funciona cuando hay comillas simples y comillas dobles en la cadena de alias!
Uphill_ What '1

17
Mi interpretación: bash concatena implícitamente expresiones de cadena entre comillas diferentes.
Benjamin Atkin

2
funcionó para mí, ejemplo de comillas simples con doble escape:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
JAMESSTONEco

2
Ciertamente no es la solución más legible. Utiliza en exceso las comillas simples donde realmente no son necesarias.
Oberlies

26
Sostengo que '\''es mucho más legible en la mayoría de los contextos que '"'"'. De hecho, el primero es casi siempre claramente distinto dentro de una cadena entre comillas simples, y por lo tanto, es solo una cuestión de mapearlo semánticamente al significado de "es una comilla escapada", como se hace con \"cadenas entre comillas dobles. Mientras que este último se combina en una línea de marcas de cotización y necesita una inspección cuidadosa en muchos casos para distinguir adecuadamente.
mtraceur

263

Siempre reemplazo cada comilla simple incrustada con la secuencia: '\''(es decir: comilla de comilla invertida) que cierra la cadena, agrega una comilla simple escapada y vuelve a abrir la cadena.


A menudo utilizo una función "cotizar" en mis scripts de Perl para hacer esto por mí. Los pasos serían:

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

Esto prácticamente se encarga de todos los casos.

La vida se vuelve más divertida cuando introduces evaltus scripts de shell. ¡Esencialmente tienes que volver a cotizar todo de nuevo!

Por ejemplo, cree un script de Perl llamado quotify que contenga las declaraciones anteriores:

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

luego úselo para generar una cadena citada correctamente:

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

resultado:

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

que luego se puede copiar / pegar en el comando alias:

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

(Si necesita insertar el comando en una evaluación, ejecute la cita nuevamente:

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

resultado:

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

que se puede copiar / pegar en una evaluación:

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

1
Pero esto no es perl. Y como Steve B señaló anteriormente, con su referencia al "manual de referencia de GNU", no se puede escapar de las citas en bash dentro del mismo tipo de cita. Y de hecho, no es necesario para escapar de ellos entre comillas alternativos, por ejemplo, "'" es una cadena de una sola cita válida y '"' es una cadena de comillas dobles válidas sin necesidad de escapar.
nicerobot

8
@nicerobot: agregué un ejemplo que muestra que: 1) no intento escapar de las comillas dentro del mismo tipo de comillas, 2) ni en comillas alternativas, y 3) Perl se usa para automatizar el proceso de generación de una Bash string con citas incrustadas
Adrian Pronk

18
El primer párrafo en sí mismo es la respuesta que estaba buscando.
Dave Causey

99
Esto es lo que hace bash también, escribe set -xy echo "here's a string"verá que se ejecuta bash echo 'here'\''s a string'. ( set +xpara devolver el comportamiento normal)
arekolek

196

Dado que la sintaxis Bash 2.04$'string' (en lugar de solo 'string'; advertencia: no confundir con $('string')) es otro mecanismo de comillas que permite secuencias de escape tipo ANSI C y se expande a la versión con comillas simples.

Ejemplo simple:

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

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

En tu caso:

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

Las secuencias de escape comunes funcionan según lo esperado:

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

A continuación se muestra la documentación relacionada copia + pegada man bash (versión 4.4):

Las palabras de la forma $ 'string' se tratan especialmente. La palabra se expande a cadena, con los caracteres con barra invertida reemplazados según lo especificado por el estándar ANSI C. Las secuencias de escape de barra invertida, si están presentes, se decodifican de la siguiente manera:

    \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

El resultado ampliado está entre comillas simples, como si el signo del dólar no hubiera estado presente.


Vea Citas y escapes: ANSI C como cadenas en el wiki de bash-hackers.org para más detalles. También tenga en cuenta que el archivo "Bash Changes" ( descripción general aquí ) menciona mucho los cambios y las correcciones de errores relacionados con el $'string'mecanismo de cotización.

De acuerdo con unix.stackexchange.com ¿Cómo usar un carácter especial como normal? debería funcionar (con algunas variaciones) en bash, zsh, mksh, ksh93 y FreeBSD y busybox sh.


podría usarse pero la cadena entre comillas simples aquí no es una echo $'foo\'b!ar'!ar': event not found
comilla

2
En mi máquina > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41

1
Sí, esa es la razón de "mayo", lo tenía en un Red Hat 6.4, ciertamente una versión anterior de bash.
regilero

Bash ChangeLog contiene muchas correcciones de errores relacionadas, por $'lo que probablemente la forma más fácil es probarlo usted mismo en sistemas más antiguos.
mj41

tenga en cuenta: e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.Tomado de tiswww.case.edu/php/chet/bash/CHANGES . Todavía funciona en 4.3.42 pero no en 4.3.48.
stiller_leser

49

No veo la entrada en su blog (¿enlace pls?) Pero según el manual de referencia de GNU :

El encerrar caracteres entre comillas simples ('' ') conserva el valor literal de cada carácter dentro de las comillas. Una comilla simple no puede aparecer entre comillas simples, incluso cuando está precedida por una barra invertida.

entonces bash no entenderá:

alias x='y \'z '

sin embargo, puede hacer esto si lo rodea con comillas dobles:

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


Los contenidos encerrados con comillas dobles se están evaluando, por lo que incluir solo comillas simples en comillas dobles como sugiere liori parece ser la solución adecuada.
Piotr Dobrogost

3
Esta es la respuesta real a la pregunta. Si bien la respuesta aceptada puede proporcionar una solución, técnicamente está respondiendo una pregunta que no se hizo.
Matthew G

3
Matthew, la pregunta era sobre escapar de comillas simples dentro de comillas simples. Esta respuesta le pide al usuario que cambie su comportamiento, y si tiene un impedimento para usar comillas dobles (como sugiere el título de la pregunta), esta respuesta no ayudaría. Sin embargo, es bastante útil (aunque obvio), y como tal merece un voto positivo, pero la respuesta aceptada aborda el problema preciso sobre el que Op preguntó.
Fernando Cordeiro

No es necesario citar una comilla simple en una cadena de comillas dobles.
Matthew D. Scholefield

32

Puedo confirmar que el uso '\''de una comilla simple dentro de una cadena entre comillas sí funciona en Bash, y se puede explicar de la misma manera que el argumento "pegar" de la secuencia anterior. Supongamos que tenemos una cadena entre comillas:'A '\''B'\'' C' (todas las comillas aquí son comillas simples). Si se pasa a eco, imprime el siguiente: A 'B' C. En cada una, '\''la primera cita cierra la cadena de comillas simples actual, lo siguiente \'pega una comilla simple a la cadena anterior ( \'es una forma de especificar una comilla simple sin comenzar una cadena de comillas), y la última comilla abre otra cadena de comillas simples.


2
Esto es engañoso, esta sintaxis '\' 'no va "dentro" de una sola cadena entre comillas. En esta declaración 'A' \ '' B '\' 'C' estás concatenando 5 \ escape y cadenas de comillas simples
teknopaul

1
@teknopaul La asignación alias something='A '\''B'\'' C'resulta en somethinguna sola cadena, por lo que, aunque el lado derecho de la tarea no es técnicamente una sola cadena, no creo que importe mucho.
Teemu Leisti

Si bien esto funciona en su ejemplo, técnicamente no proporciona una solución sobre cómo insertar una comilla simple dentro de una sola cadena entre comillas. Ya lo has explicado, pero sí, lo está haciendo 'A ' + ' + 'B' + ' + ' C'. En otras palabras, una solución para insertar caracteres de comillas simples dentro de una cadena entre comillas simples debería permitirme crear dicha cadena por sí misma e imprimirla. Sin embargo, esta solución no funcionará en este caso. STR='\''; echo $STR. Tal como se diseñó, BASH realmente no permite esto.
krb686

@mikhail_b, sí, '\''funciona para bash. ¿Podría señalar qué secciones de gnu.org/software/bash/manual/bashref.html especifican tal comportamiento?
Jingguo Yao

20

Ambas versiones funcionan, ya sea con concatenación usando el carácter de comilla simple escapado (\ '), o con concatenación encerrando el carácter de comilla simple entre comillas dobles ("'").

El autor de la pregunta no se dio cuenta de que había una cita simple adicional (') al final de su último intento de fuga:

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

Como puede ver en la bonita pieza anterior de arte ASCII / Unicode, a la última comilla simple escapada (\ ') le sigue una comilla simple innecesaria ('). Usar un resaltador de sintaxis como el presente en Notepad ++ puede resultar muy útil.

Lo mismo es cierto para otro ejemplo como el siguiente:

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

Estas dos hermosas instancias de alias muestran de una manera muy intrincada y ofuscada cómo se puede alinear un archivo. Es decir, de un archivo con muchas líneas, solo se obtiene una línea con comas y espacios entre el contenido de las líneas anteriores. Para dar sentido al comentario anterior, el siguiente es un ejemplo:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214

3
Upwoted para la ilustración de la tabla ASCII :)
php-dev

16

Ejemplo simple de comillas de escape en shell:

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

Se realiza al terminar uno abierto ( '), colocando uno escapado ( \') y luego abriendo otro ( '). Esta sintaxis funciona para todos los comandos. Es un enfoque muy similar a la primera respuesta.


15

No estoy abordando específicamente el problema de las citas porque, bueno, a veces, es razonable considerar un enfoque alternativo.

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

que luego puedes llamar como:

rxvt 123456 654321

La idea es que ahora puede alias esto sin preocuparse por las comillas:

alias rxvt='rxvt 123456 654321'

o, si necesita incluir el #en todas las llamadas por algún motivo:

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

que luego puedes llamar como:

rxvt '#123456' '#654321'

entonces, por supuesto, un alias es:

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

(Uy, supongo que me dirigí a la cita :)


1
Estaba tratando de poner algo entre comillas simples que estaba entre comillas dobles que, a su vez, estaban entre comillas simples. Yikes Gracias por su respuesta de "pruebe un enfoque diferente". Eso hizo la diferencia.
Clinton Blackmore

1
Llego 5 años tarde, pero ¿no te estás perdiendo una sola cita en tu último alias?
Julien

1
@Julien No veo ningún problema ;-)
nicerobot

11

Dado que no se pueden poner comillas simples dentro de cadenas entre comillas simples, la opción más simple y fácil de leer es usar una cadena HEREDOC

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

alias rxvt=$command

En el código anterior, el HEREDOC se envía al catcomando y su salida se asigna a una variable mediante la notación de sustitución del comando$(..)

Es necesario poner una cita simple alrededor del HEREDOC ya que está dentro de un $()


Ojalá me hubiera desplazado hacia abajo hasta aquí, ¡reinventé este enfoque y vine a publicarlo! Esto es mucho más limpio y más legible que todos los otros enfoques de escape. No, no funcionará en algunos shells que no sean bash, como dashcuál es el shell predeterminado en los scripts de inicio de Ubuntu y en otros lugares.
Korny

¡Gracias! que lo que buscaba, la forma de definir un comando como es a través de heredoc y pasar el comando de escape automático a ssh. BTW cat << COMMAND sin comillas permite interpolar vatiables dentro del comando y funciona también para este enfoque.
Igor Tverdovskiy

10

Solo uso códigos de shell ... por ejemplo, \x27o \\x22según corresponda. Sin problemas, nunca realmente.


¿Podría mostrar un ejemplo de esto en funcionamiento? Para mí solo imprime un literal x27(en Centos 6.6)
Will Sheppard

6

La mayoría de estas respuestas se refieren al caso específico que está preguntando. Hay un enfoque general que un amigo y yo hemos desarrollado que permite arbitraria citar en caso de que necesite cita fiesta de comandos a través de múltiples capas de desarrollo del forro, por ejemplo, a través de ssh, su -c, bash -c, etc. Hay un núcleo primitivo que necesita, aquí en bash nativo:

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
}

Esto hace exactamente lo que dice: cita cada argumento individualmente (después de la expansión bash, por supuesto):

$ 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'

Hace lo obvio para una capa de expansión:

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

(Tenga en cuenta que las comillas dobles $(quote_args ...)son necesarias para convertir el resultado en un argumento único para bash -c). Y se puede usar de manera más general para citar correctamente a través de múltiples capas de expansión:

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

El ejemplo anterior:

  1. shell-cita cada argumento al interior quote_args individualmente y luego combina el resultado resultante en un solo argumento con las comillas dobles internas.
  2. cotizaciones bash , -cy el resultado ya citado una vez del paso 1, y luego combina el resultado en un solo argumento con las comillas dobles externas.
  3. envía ese desorden como argumento al exterior bash -c.

Esa es la idea en pocas palabras. Puede hacer algunas cosas bastante complicadas con esto, pero debe tener cuidado con el orden de evaluación y las subcadenas citadas. Por ejemplo, lo siguiente hace las cosas mal (para alguna definición de "mal"):

$ (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

En el primer ejemplo, bash se expande inmediatamente quote_args cd /; pwd 1>&2en dos comandos separados quote_args cd /y pwd 1>&2, por lo tanto, el CWD aún está en ejecución /tmpcuando pwdse ejecuta el comando. El segundo ejemplo ilustra un problema similar para el globbing. De hecho, el mismo problema básico ocurre con todas las expansiones de bash. El problema aquí es que una sustitución de comando no es una llamada a una función: literalmente está evaluando un script bash y usando su salida como parte de otro script bash.

Si intenta simplemente escapar de los operadores de shell, fallará porque la cadena resultante que se pasa bash -ces solo una secuencia de cadenas entre comillas individuales que no se interpretan como operadores, lo que es fácil de ver si repite la cadena que han pasado a 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'

El problema aquí es que estás sobre citando. Lo que necesita es que los operadores no estén entre comillas como entrada para el cerramiento bash -c, lo que significa que deben estar fuera de la $(quote_args ...)sustitución del comando.

En consecuencia, lo que debe hacer en el sentido más general es citar shell cada palabra del comando que no se pretende expandir en el momento de la sustitución del comando por separado, y no aplicar ninguna cita adicional a los operadores de 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

Una vez que haya hecho esto, toda la cadena es un juego justo para seguir citando a niveles arbitrarios de evaluación:

$ 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.

Estos ejemplos pueden parecer excesivos dado que las palabras como success, sbiny pwdno necesitan ser citadas, pero el punto clave a recordar cuando se escribe un script que toma una entrada arbitraria es que desea citar todo lo que no está absolutamente seguro . No es necesario citar, porque nunca se sabe cuándo un usuario arrojará a Robert'; rm -rf /.

Para comprender mejor lo que sucede debajo de las cubiertas, puedes jugar con dos pequeñas funciones auxiliares:

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

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

que enumerará cada argumento a un comando antes de ejecutarlo:

$ 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

Hola kyle Su solución funcionó muy bien para un caso que tuve, cuando tenía que pasar a un grupo de argumentos como un solo argumento: vagrant ssh -c {single-arg} guest. Las {single-arg}necesidades a ser tratados como un solo arg porque vagabundo toma el siguiente arg después de que el nombre del huésped. El orden no se puede cambiar. Pero necesitaba pasar un comando y sus argumentos dentro {single-arg}. Por lo que utiliza su quote_args()citar el comando y sus argumentos, y poner entre comillas el resultado, y funcionó como un encanto: vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. ¡¡¡Gracias!!!
Andreas Maier

6

En mi humilde opinión, la respuesta real es que no puede escapar de comillas simples dentro de cadenas de comillas simples.

Es imposible.

Si suponemos que estamos usando bash.

Del manual 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.

Debe usar uno de los otros mecanismos de escape de cadena "o \

No hay nada mágico alias que requiera el uso de comillas simples.

Tanto el siguiente trabajo en bash.

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

Este último está usando \ para escapar del carácter de espacio.

Tampoco hay nada mágico en # 111111 que requiera comillas simples.

Las siguientes opciones logran el mismo resultado que las otras dos opciones, ya que el alias rxvt funciona como se esperaba.

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

También puedes escapar del problemático # directamente

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

"La verdadera respuesta es que no puedes escapar de las comillas simples dentro de las cadenas de comillas simples". Eso es técnicamente cierto. Pero puede tener una solución que comienza con una comilla simple, termina con una comilla simple y solo contiene comillas simples en el medio. stackoverflow.com/a/49063038
wisbucky

No escapando, solo por concatenación.
teknopaul

4

En el ejemplo dado, simplemente usó comillas dobles en lugar de comillas simples como mecanismo de escape externo:

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

Este enfoque es adecuado para muchos casos en los que solo desea pasar una cadena fija a un comando: solo verifique cómo el shell interpretará la cadena entre comillas dobles a través de un echo , y si es necesario, escape los caracteres con barra invertida.

En el ejemplo, vería que las comillas dobles son suficientes para proteger la cadena:

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

4

Obviamente, sería más fácil simplemente rodearlo con comillas dobles, pero ¿dónde está el desafío en eso? Aquí está la respuesta usando solo comillas simples. Estoy usando una variable en lugar de, aliaspor lo que es más fácil imprimir como prueba, pero es lo mismo que usar alias.

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

Explicación

La clave es que puede cerrar la cotización simple y volver a abrirla tantas veces como desee. Por ejemplo foo='a''b'es lo mismo que foo='ab'. Por lo tanto, puede cerrar la comilla simple, incluir una comilla simple literal \'y luego volver a abrir la siguiente.

Diagrama de desglose

Este diagrama lo deja claro usando corchetes para mostrar dónde se abren y cierran las comillas simples. Las citas no están "anidadas" como pueden estar entre paréntesis. También puede prestar atención al resaltado de color, que se aplica correctamente. Las cadenas citadas son de color granate, mientras que el \'es de color negro.

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

(Esta es esencialmente la misma respuesta que la de Adrian, pero creo que esto lo explica mejor. También su respuesta tiene 2 comillas simples superfluas al final).


+1 por usar el '\''método que recomiendo sobre el '"'"'método que a menudo es más difícil de leer para los humanos.
mtraceur

3

Aquí hay una elaboración sobre La única respuesta verdadera mencionada anteriormente:

¡A veces descargaré usando rsync sobre ssh y tendré que escapar de un nombre de archivo con un 'dos ​​veces! (OMG!) Una vez para bash y otra para ssh. Aquí funciona el mismo principio de delimitadores de cotizaciones alternas.

Por ejemplo, digamos que queremos obtener: Historias de LA de Louis Theroux ...

  1. Primero encierra Louis Theroux entre comillas simples para bash y comillas dobles para ssh: '"Louis Theroux"'
  2. Luego usa comillas simples para escapar de una comilla doble '"'
  3. El uso de comillas dobles para escapar del apóstrofe "'"
  4. Luego repita # 2, usando comillas simples para escapar de una comilla doble '"'
  5. Luego, encierre LA Stories entre comillas simples para bash y comillas dobles para ssh: '"LA Stories"'

Y he aquí! Terminas con esto:

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

que es mucho trabajo para un pequeño ', pero ahí lo tienes


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

Explicación de implementación:

  • comillas dobles para que podamos generar fácilmente comillas simples envolventes y usar la ${...}sintaxis

  • La búsqueda y reemplazo de bash se ve así: ${varname//search/replacement}

  • estamos reemplazando 'con'\''

  • '\''codifica un solo 'así:

    1. ' termina la cita simple

    2. \'codifica a '(la barra invertida es necesaria porque no estamos entre comillas)

    3. ' comienza a cotizar de nuevo

    4. bash concatena automáticamente cadenas sin espacios en blanco entre

  • hay un \antes de cada \y 'porque esas son las reglas de escape ${...//.../...}.

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

PS Siempre codifique para cadenas de comillas simples porque son mucho más simples que las cadenas de comillas dobles.


2

Otra forma de solucionar el problema de demasiadas capas de citas anidadas:

Estás tratando de meter demasiado en un espacio demasiado pequeño, así que usa una función bash.

El problema es que está intentando tener demasiados niveles de anidamiento, y la tecnología de alias básica no es lo suficientemente potente como para adaptarse. Utilice una función bash como esta para que las marcas simples, dobles y los parámetros pasados ​​se manejen normalmente como se esperaría:

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

Luego puede usar sus variables de $ 1 y $ 2 y comillas simples, dobles y ticks sin preocuparse de que la función de alias destruya su integridad.

Este programa 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 tiene instalado GNU Parallel, puede usar su cita interna:

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

Desde la versión 20190222 puedes incluso --shellquotevarias veces:

$ 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

Citará la cadena en todos los shells compatibles (no solo bash).


1

Esta función:

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

Permite cotizar el 'interior '. Úselo así:

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

Si la línea para citar se vuelve más compleja, como las comillas dobles mezcladas con comillas simples, puede ser bastante complicado hacer que la cadena cite dentro de una variable. Cuando aparezcan estos casos, escriba la línea exacta que necesita citar dentro de un script (similar a esto).

#!/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_

Saldrá:

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

Todas las cadenas correctamente citadas dentro de comillas simples.


1

Si está generando la cadena de shell dentro de Python 2 o Python 3, lo siguiente puede ayudar a citar los argumentos:

#!/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()]))

Esto generará:

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

1

Aquí están mis dos centavos, en el caso de que uno quiera ser shportátil, no solo bashespecífico (sin embargo, la solución no es demasiado eficiente, ya que inicia un programa externo sed):

  • pon esto quote.sh(o simplemente quote) en algún lugar de tu PATH:
# esto funciona con entrada estándar (stdin)
cita () {
  echo -n "'";
  sed 's / \ ([' "'"'] ['"'" '] * \) /' "'"' "\ 1" '"'" '/ g';
  echo -n "'"
}

caso "$ 1" en
 -) cita ;;
 *) echo "uso: cat ... | quote - # entrada de comillas simples para Bourne shell" 2> & 1 ;;
esac

Un ejemplo:

$ echo -n "G'day, amigo!" El | ./quote.sh -
'¡Buen dia amigo!'

Y, por supuesto, eso convierte de nuevo:

$ echo 'G' "'"' día, amigo! '
¡Buen dia amigo!

Explicación: básicamente tenemos que encerrar la entrada con comillas ', y luego reemplazar cualquier comilla simple con este micro-monstruo: '"'"'(finalice la comilla de apertura con un emparejamiento ', escape de la comilla simple encontrada envolviéndola con comillas dobles "'", y finalmente, emitir una nueva apertura comilla simple ', o en seudo-notación: ' + "'" + ' == '"'"')

Una forma estándar de hacerlo sería utilizar sedel siguiente comando de sustitución:

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

Sin embargo, un pequeño problema es que para usar eso en shell uno debe escapar de todos estos caracteres de comillas simples en la propia expresión sed, lo que lleva a algo así como

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

(y una buena manera de construir este resultado es alimentar la expresión original s/\(['][']*\)/'"\1"'/ga los guiones de Kyle Rose o George V. Reilly).

Finalmente, tiene sentido esperar que la entrada provenga stdin, ya que pasarla por los argumentos de la línea de comandos ya podría ser un problema.

(Ah, y es posible que queramos agregar un pequeño mensaje de ayuda para que el script no se cuelgue cuando alguien simplemente lo ejecuta y se ./quote.sh --helppregunta qué hace).


0

Aquí hay otra solución. Esta función tomará un argumento simple y lo citará apropiadamente usando el carácter de comillas simples, tal como lo explica la respuesta votada arriba:

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'"
}

Entonces, puedes usarlo de esta manera:

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

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.