¿Qué requiere POSIX para los documentos citados aquí dentro de la sustitución de comandos?

20

En esta pregunta, alguien informa un problema al usar un documento aquí con una palabra delimitadora citada dentro de la $(...)sustitución de comando , donde una barra invertida \al final de una línea dentro del documento desencadena la continuación de la línea de unión de nueva línea , mientras que el mismo documento aquí fuera de la sustitución de comando funciona como se esperaba .

Aquí hay un documento de ejemplo simplificado:

cat <<'EOT'
abc ` def
ghi \
jkl
EOT

Esto incluye una barra de retroceso y una barra invertida al final de una línea. Se cita el delimitador, por lo que no se producen expansiones dentro del cuerpo. En todos los Bourne-alikes puedo encontrar esta salida de los contenidos textualmente. Si pongo el mismo documento dentro de una sustitución de comando de la siguiente manera:

x=$(cat <<'EOT'
abc ` def
ghi \
jkl
EOT
)
echo "$x"

entonces ya no se comportan de manera idéntica:

  • dash, ash, zsh, ksh93, BusyBox ash, mkshy SunOS 5.10 POSIX shtodos dan los contenidos literales del documento, como antes.
  • Bash 3.2 da un error de sintaxis para un backtick sin igual. Con backticks coincidentes, intenta ejecutar el contenido como un comando.
  • Bash 4.3 colapsa "ghi" y "jkl" en una sola línea, pero no tiene ningún error. La --posixopción no afecta esto. Kusalananda me dice (¡gracias!) Que se pdkshcomporta de la misma manera .

En la pregunta original, dije que esto era un error en el analizador de Bash. ¿Lo es? [Actualización: ] El texto relevante de POSIX (todo de la definición del lenguaje de comandos de Shell) que puedo encontrar es:

  • §2.6.3 Sustitución de comandos :

    Con el formulario $ (comando), todos los caracteres que siguen el paréntesis abierto al paréntesis de cierre coincidente constituyen el comando. Se puede usar cualquier script de shell válido para el comando , excepto un script que consista únicamente en redirecciones que produzcan resultados no especificados.

  • §2.7.4 Aquí-Documento :

    Si se cita una parte de la palabra , el delimitador se formará mediante la eliminación de la cita en la palabra , y las líneas del documento aquí no se expandirán.

  • §2.2.1 Carácter de escape (barra invertida) :

    Si un <newline> sigue al <backslash>, el shell interpretará esto como una continuación de línea. Las <backslash> y <newline> se eliminarán antes de dividir la entrada en tokens.

  • §2.3 Reconocimiento de tokens :

    Cuando la gramática ha reconocido un token io_here (ver Gramática de Shell ), una o más de las líneas posteriores que siguen inmediatamente al siguiente token NEWLINE forman el cuerpo de uno o más documentos aquí y se analizarán de acuerdo con las reglas de Aquí. Documento .

    Cuando no está procesando un io_here , el shell dividirá su entrada en tokens aplicando la primera regla aplicable a continuación al siguiente carácter en su entrada. ...

    ...

    1. Si el carácter actual es <backslash>, comillas simples o comillas dobles y no se cita, afectará la cita para los caracteres posteriores hasta el final del texto citado. Las reglas para las citas son como se describen en las citas . Durante el reconocimiento de tokens, no se realizarán sustituciones, y el token resultante contendrá exactamente los caracteres que aparecen en la entrada (excepto para la unión de <línea nueva>), sin modificar, incluidas las comillas o operadores de sustitución incluidos o incluidos, entre el final y el final. del texto citado.

Mi interpretación de esto es que todos los caracteres posteriores $(hasta la terminación )comprenden el script de shell, textualmente; aparece un documento aquí, por lo que el procesamiento del documento aquí ocurre en lugar de la tokenización ordinaria; el documento aquí tiene un delimitador citado, lo que significa que su contenido se procesa textualmente; y el personaje de escape nunca entra en juego. Sin embargo, puedo ver un argumento de que este caso simplemente no se aborda y que ambos comportamientos son permisibles. Es posible que también haya omitido algún texto relevante en alguna parte.


  • ¿Esta situación se aclara en otra parte?
  • ¿En qué debería poder confiar un script portátil (en teoría)?
  • ¿El tratamiento específico de alguno de estos shells (Bash 3.2 / Bash 4.3 / todos los demás) es obligatorio según el estándar? ¿Prohibido? ¿Permitido?
Michael Homer
fuente
¿Puede mostrarnos cómo produce su salida en el segundo caso?
Julie Pelletier
@JuliePelletier echo "$x", pero cualquier forma de inspeccionar la variable funciona. He editado esa línea en la parte inferior.
Michael Homer el
2
Parece que es una solución fácil. Este parche parece funcionar al menos: ignore_quoted_newline_in_quoted_heredoc.patch
geirha
1
Creo que está interpretando esto correctamente e, en mi opinión, el estándar es bastante claro, ya que "El shell expandirá la sustitución de comandos ejecutando el comando en un entorno de subshell y reemplazando [...] la sustitución de comandos con la salida estándar de el comando "[...]" Entonces ejecuta el comando en una subshell y lo reemplaza $(...)por lo que sea esa salida ... Ahora, cuando ejecuta el comando en su ejemplo en una subshell (en bash) sí genera el resultado esperado. Es solo cuando lo convierte en sustitución de comando que colapsa "ghi" y "jkl". Así que esto es un error de la OMI
don_crissti
2
@geirha informé un error Bash ; No voy a preocuparme por pdksh ya que no parece tener ni una sombra del mantenimiento actual.
Michael Homer

Respuestas:

5

Esto fue preguntado en la lista de correo de Bash, y el responsable confirmó que era un error

También mencionaron que el texto en POSIX "no es necesariamente ambiguo, pero requiere una lectura atenta", por lo que solicité una aclaración al respecto. Su respuesta, incluida una descripción del problema y la interpretación de la norma, fue la siguiente:

La sustitución del comando es un arenque rojo; es relevante solo porque señaló dónde estaba el error.

Se cita el delimitador del documento aquí, por lo que las líneas no se expanden. En este caso, el shell lee las líneas de la entrada como si fueran citadas. Si aparece una barra diagonal inversa en un contexto en el que se cita, no actúa como un carácter de escape (ver más abajo), y no se realiza el manejo especial de la barra diagonal inversa-nueva línea. De hecho, si se cita cualquier parte del delimitador, las líneas del documento aquí se leen como si fueran comillas simples.

El texto en Posix 2.2.1 está escrito torpemente, pero significa que la barra invertida solo se trata especialmente cuando no se cita. Puede citar una barra invertida e inhibir toda expansión solo con comillas simples u otra barra invertida.

La parte de lectura cercana es el texto "no expandido" que implica las comillas simples. El estándar dice en 2.2 que aquí los documentos son "otra forma de cita", pero la única forma de cita en la que las palabras no se expanden en absoluto es comillas simples. Entonces, es una forma de cita que es casi exactamente como comillas simples, pero no comillas simples.

Kevin
fuente
@Scott (1) Creo que esto responde todas las preguntas y nada es superfluo. Mi comentario que comienza con la respuesta es sobre una eliminación realizada por un moderador que no entendió la situación. (2) No tengo suficiente reputación. (3) Hubiera apreciado un comportamiento similar al eliminar mis respuestas, pero ciertamente lo tendré en cuenta en el futuro. Gracias por los pensamientos
Kevin
Mi punto fue que la mayoría de su primer párrafo es una conversación con Michael Mrozek y no una respuesta a la pregunta. Me doy cuenta de que no tienes suficiente reputación para comentar en ninguna publicación, pero creo que tienes suficiente para meta y chat.
Scott
1
@Scott Entiendo y aprecio que estés tratando de simplificar la respuesta, pero publiqué esa respuesta simplificada anteriormente (solo la cita y un enlace), y dicho moderador la eliminó (¡sin ninguna discusión!) Y yo no vea enlaces en la publicación eliminada para chatear y disputar esa decisión. Esperaba que al responder a su crítica infundada, sobreviviera a la eliminación, fuera aceptado por el autor de la pregunta y luego modificaría la respuesta para eliminar el preámbulo.
Kevin