comando bash multi line con comentarios después del carácter de continuación

29

Considerar

echo \ # this is a comment
foo

Esto da:

$ sh foo.sh
 # this is a comment
foo.sh: line 2: foo: command not found

Después de buscar en la web, encontré una solución de DigitalRoss en el sitio hermano Stack Overflow. Entonces uno puede hacer

echo `: this is a comment` \
foo

o alternativamente

echo $(: this is a comment) \
foo

Sin embargo, DigitalRoss no explicó por qué funcionan estas soluciones. Agradecería una explicación. Él respondió con un comentario:

Solía ​​haber un gotocomando de shell que se bifurcaba a las etiquetas especificadas como :aquí. Se gotoha ido, pero aún puede usar la : whateversintaxis ... :es una especie de comentario analizado ahora.

Pero me gustaría obtener más detalles y contexto, incluida una discusión sobre la portabilidad.

Por supuesto, si alguien tiene otras soluciones, eso también sería bueno.

Consulte también la pregunta anterior ¿ Cómo comentar comandos de varias líneas en scripts de shell? .


Lleve a casa el mensaje de la discusión a continuación. El `: this is a comment`es solo una sustitución de comando. La salida de : this is a commentno es nada, y eso se pone en lugar de `: this is a comment`.

Una mejor opción es la siguiente:

echo `# this is a comment` \
foo
Faheem Mitha
fuente

Respuestas:

25

Los comentarios terminan en la primera línea nueva (consulte la regla 10 de reconocimiento de token de shell ), sin permitir líneas de continuación , por lo que este código tiene foouna línea de comando separada:

echo # this is a comment \
foo

En cuanto a su primera propuesta, la barra diagonal inversa no es seguida por una nueva línea, solo está citando el espacio: es equivalente a

echo ' # this is a comment'
foo

$(: this is a comment)sustituye la salida del comando : this is a comment. Si la salida de ese comando está vacía, esta es efectivamente una forma altamente confusa de insertar un comentario en el medio de una línea.

No está sucediendo magia: :es un comando ordinario, la utilidad de colon , que no hace nada. La utilidad de dos puntos es principalmente útil cuando la sintaxis de shell requiere un comando pero no tiene nada que hacer.

# Sample code to compress files that don't look compressed
case "$1" in
  *.gz|*.tgz|*.bz2|*.zip|*.jar|*.od?) :;; # the file is already compressed
  *) bzip2 -9 "$1";;
esac

Otro caso de uso es un modismo para establecer una variable si aún no está configurada.

: "${foo:=default value}"

El comentario sobre goto es histórico. La utilidad de colon se remonta incluso antes del shell Bourne , hasta el shell Thompson , que tenía una instrucción goto . El colon entonces significaba una etiqueta; un colon es una sintaxis bastante común para las etiquetas de goto (todavía está presente en sed ).

Gilles 'SO- deja de ser malvado'
fuente
OK veo. ¿Hay alguna forma mejor de insertar comentarios en este contexto que conozca?
Faheem Mitha
3
@FaheemMitha Como se recomienda en el hilo que cita: divida su comando en fragmentos manejables y comente cada fragmento. Si su comando es tan complicado que necesita un comentario en el medio, ¡es hora de simplificarlo!
Gilles 'SO- deja de ser malvado'
1
Bueno, el comando en cuestión tiene muchos argumentos ... Está convirtiendo un montón de archivos de video en un solo archivo. No veo una forma directa de simplificarlo. ¿Quizás crear una lista de algún tipo y pasarla como argumento? Supongo que esa podría ser otra pregunta.
Faheem Mitha
@FaheemMitha Ejemplo: make_FINDun script rápido que crea una larga lista de argumentos para find. Aquí, la motivación para construirlo fragmento por fragmento es que cada fragmento proviene del cuerpo de un bucle, pero el mismo estilo permite comentar cada fragmento.
Gilles 'SO- deja de ser malvado'
1
Gracias por el ejemplo Mi ejemplo es básicamente una larga lista de nombres dados como argumentos de un comando. Quiero comentarios adjuntos a cada nombre, porque esa es la forma más fácil para mí de mantener el contexto. No veo ninguna forma obvia de dividir el comando en pedazos, y si lo hiciera, podría hacerlo aún más largo, y ya es lo suficientemente largo.
Faheem Mitha
6

Puede lograr esto utilizando matrices Bash, por ejemplo

#!/bin/bash
CMD=(
  echo  # this is a comment
  foo
  )

"${CMD[@]}"

Esto define una matriz $CMDy luego la expande. Una vez expandida, se evalúa la línea resultante, por lo que en este caso echo foose ejecuta.

El texto entre (y )define la matriz y está sujeto a la sintaxis bash habitual, por lo que #se ignora todo en una línea posterior .

Nota sobre la preservación de espacios en blanco entre comillas

${CMD[@]}se expande a una sola cadena que es la concatenación de todos los elementos, separados por un espacio. Una vez expandido, Bash analizaría la cadena en tokens de la manera habitual (cf $ IFS ), que a menudo no es lo que queremos.

Por el contrario, si la expansión se envuelve entre comillas dobles, es decir "${CMD[@]}", cada elemento de la matriz se conserva. Considere la diferencia entre hello world second itemy "hello world" "second item".

Ejemplo ilustrativo:

# LIST=("hello world" "second item")

# for ITEM in ${LIST[@]}; do echo $ITEM; done
hello
world
second
item

# for ITEM in "${LIST[@]}"; do echo $ITEM; done
hello world
second item
RobM
fuente
Gracias por la respuesta, @RobM. Entonces, ¿su sugerencia es incluir los comentarios / metainformación sobre los elementos en la lista en la matriz bash? Esta pregunta probablemente sería más útil si hubiera incluido un ejemplo del tipo de comando que estaba tratando de usar. No sé por qué no lo hice.
Faheem Mitha
Bah, resulta que no leí tu pregunta con cuidado. Pensé que estabas haciendo la pregunta a la que te vinculaste. Ups :)
RobM
${CMD[@]}no se expande a una sola cadena. - Se expande a muchas cadenas: no solo se divide para cada elemento de la matriz, sino también en espacios en blanco en cada elemento. (Es decir: completamente inútil)
Robert Siemer
4

No lo haga $(: comment). Eso no es un comentario, es un subshell, otro proceso de shell completamente nuevo para la mayoría de los shells. Su objetivo es hacer menos con su aporte, no más, que es lo que eso haría, incluso si no tiene sentido.

En cambio, puedes hacer ...

printf '<%s>\n' some args here ${-##*"${--

                my long comment block

                }"}  and "more ${-##*"${--

                and another one in the
                middle of the quoted string
                there shouldn\'t b\e any special &
                (character) `echo issues here i hope >&2`
                basically anything that isn\'t a close \}
                $(echo the shell is looking for one >&2)
                }$(echo "}'"\" need backslash escaping >&2
                                )${-##*${--

                nesting is cool though

             }}"}here too"

}'" need backslash escaping
<some>
<args>
<here>
<and>
<more here too>

Básicamente lo que está sucediendo allí es que el shell está haciendo una sustitución. Sustituye el valor del parámetro de shell especial $-dos veces cada vez. De todos modos, es una cadena corta, pero siempre está configurada, por lo que la sustitución interna, que se interpreta como un patrón para quitar desde el exterior, no se expande al contenido entre paréntesis cuando uso la -forma de expansión.

Aquí:

bash -x <<""
printf %s\\n '${-##*"'${-- a set param doesn\'t expand to this optional text }'"}'

+ printf '%s\n' '${-##*"hxB"}'
${-##*"hxB"}

¿Ver? Entonces se expandió dos veces. Tan pronto como el shell encuentra que el parámetro se establece, todo en el campo de expansión opcional se descarta, más o menos, y se expande a su valor completo que se elimina de sí mismo y, por lo tanto, a nada en absoluto. En la mayoría de los shells ni siquiera necesita escapar de las comillas, sino que lo bashrequiere.

Mejor aún es:

COMMENT=
echo      ${COMMENT-"
           this will work the same way
           but the stripping isn\'t
           necessary because, while
           the variable is set, it is also
           empty, and so it will expand to
           its value - which is nothing
           "} this you\'ll see

this you'll see
mikeserv
fuente