¿Hay algo similar a echo -n en heredoc (EOF)?

12

Estoy escribiendo en un gran script de fábrica de scripts que genera muchos scripts de mantenimiento para mis servidores.

Hasta ahora escribo algunas líneas que deben escribirse en una línea con, echo -nepor ejemplo,

echo -n "if (( " | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

# Generate exitCode check for each Server
IFS=" "
COUNT=0
while read -r name ipAddr
do
    if(($COUNT != 0))
    then
        echo -n " || " | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
    fi

    echo -n "(\$"$name"_E != 0 && \$"$name"_E != 1)" | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

    COUNT=$((COUNT+1))

    JOBSDONE=$((JOBSDONE+1))
    updateProgress $JOBCOUNT $JOBSDONE
done <<< "$(sudo cat /root/.virtualMachines)"

echo " ))" | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

esto me genera un código (si hay, por ejemplo, 2 servidores en mi archivo de configuración) como

if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))

Todos los demás bloques de código que no necesitan esta escritura en línea del código que produzco con heredocs ya que los encuentro mucho mejores para escribir y mantener. Por ejemplo, después del código superior tengo el resto generado como

cat << EOF | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
then
    # Print out ExitCode legend
    echo " ExitCode 42 - upgrade failed"
    echo " ExitCode 43 - Upgrade failed"
    echo " ExitCode 44 - Dist-Upgrade failed"
    echo " ExitCode 45 - Autoremove failed"
    echo ""
    echo ""
fi
EOF

Entonces el bloque de código final se ve así

if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))
then
    # Print out ExitCode legend
    echo " ExitCode 42 - upgrade failed"
    echo " ExitCode 43 - Upgrade failed"
    echo " ExitCode 44 - Dist-Upgrade failed"
    echo " ExitCode 45 - Autoremove failed"
    echo ""
    echo ""
fi

Mi pregunta
¿Hay alguna manera de que un heredoc se comporte de manera similar echo -nesin el símbolo de fin de línea?

derHugo
fuente
1
Si necesita sudoun script, lo está haciendo mal: en su lugar, ejecute todo el script como root.
postre
1
No porque también está haciendo otras cosas que no quiero ejecutar como root
derHugo
El uso sudo -u USERNAMEde eso en su lugar, ver ¿Cómo ejecuto un comando 'sudo' dentro de un script? .
postre
¿Cuál es el problema al hacerlo como yo?
derHugo
44
Bueno, por supuesto, hay un problema: un script sudosin nombre de usuario solicita la contraseña, a menos que la haya ingresado, hay un retraso. Es aún peor si no ejecuta el script en una terminal, entonces ni siquiera puede ver la consulta de contraseña y simplemente no funcionará. El sudo -uenfoque no tiene ninguno de estos problemas.
postre

Respuestas:

9

No, heredoc siempre termina en una nueva línea. Pero aún puede eliminar la última línea nueva, por ejemplo con Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -plee la entrada línea por línea, ejecuta el código especificado -ee imprime la línea (posiblemente modificada)
  • chomp elimina la nueva línea final si existe, eof devuelve verdadero al final del archivo.
choroba
fuente
1
@Yaron si se encuentra al final del archivo (en este caso, tubería cerrada), tragará char de nueva línea desde la última línea (que es lo que heredoc y herestring agregan automáticamente)
Sergiy Kolodyazhnyy
7

¿Hay alguna manera de que un heredoc se comporte de manera similar a echo -ne sin el símbolo de fin de línea?

La respuesta corta es que heredoc y herestrings se construyen de esa manera, por lo que no, here-doc y herestring agregarán siempre una nueva línea final y no hay una opción o una forma nativa para deshabilitarla (¿tal vez habrá versiones futuras de bash?).

Sin embargo, una forma de abordarlo a través de solo bash sería leer lo que heredoc brinda a través del while IFS= read -rmétodo estándar , pero agregar un retraso e imprimir la última línea printfsin la nueva línea final. Esto es lo que se podría hacer con bash-only:

#!/usr/bin/env bash

while true
do
    IFS= read -r line || { printf "%s" "$delayed";break;}
    if [ -n "$delayed" ]; 
    then
        printf "%s\n" "$delayed"
    fi
    delayed=$line
done <<EOF
one
two
EOF

Lo que ve aquí es el ciclo while habitual con IFS= read -r lineenfoque, sin embargo, cada línea se almacena en una variable y, por lo tanto, se retrasa (por lo que omitimos la impresión de la primera línea, la almacenamos y comenzamos a imprimir solo cuando hemos leído la segunda línea). De esa forma podemos capturar la última línea, y cuando en la próxima iteración readregrese el estado de salida 1, es cuando imprimimos la última línea sin nueva línea vía printf. Detallado? si. Pero funciona:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Observe que el $carácter de solicitud se empuja hacia adelante, ya que no hay nueva línea. Como beneficio adicional, esta solución es portátil y POSIX-ish, por lo que debería funcionar en kshy dashtambién.

$ dash ./strip_heredoc_newline.sh                                 
one
two$
Sergiy Kolodyazhnyy
fuente
6

Te recomiendo que pruebes un enfoque diferente. En lugar de juntar su script generado a partir de múltiples declaraciones de eco y heredocs. Le sugiero que intente usar un solo heredoc.

Puede usar la expansión variable y la sustitución de comandos dentro de un heredoc. Como en este ejemplo:

#!/bin/bash
cat <<EOF
$USER $(uname)
EOF

Creo que esto a menudo conduce a resultados finales mucho más legibles que producir el resultado pieza por pieza.

También parece que has estado olvidando la #!línea al comienzo de tus scripts. La #!línea es obligatoria en cualquier script con formato correcto. Intentar ejecutar un script sin la #!línea solo funcionará si lo llama desde un shell que funciona con scripts mal formateados, e incluso en ese caso podría terminar siendo interpretado por un shell diferente de lo que pretendía.

kasperd
fuente
no #!he olvidado el pero mi bloque de código es solo un fragmento de un script de 2000 líneas;) No veo en este momento cómo su sugerencia resuelve mi problema generando una línea de código en un bucle while ...
derHugo
@derHugo La sustitución de comandos para la parte de la línea donde necesita un bucle es una forma de hacerlo. Otra forma es construir esa parte en una variable antes del heredoc. Cuál de los dos es más legible depende de la longitud del código necesario en el bucle, así como de cuántas líneas de heredoc tiene antes de la línea en cuestión.
Kasperd
@derHugo La sustitución de comandos que invoca una función de shell definida anteriormente en el script es un tercer enfoque que puede ser más legible si necesita una cantidad significativa de código para generar esa línea.
kasperd
@derHugo: Tenga en cuenta que: (1) Puede tener un bucle que ponga todos los comandos necesarios en una variable; (2) Puede usar $( ...command...)dentro de un heredoc para insertar la salida de esos comandos en línea en ese punto del heredoc; (3) Bash tiene variables de matriz, que puede expandir en línea, y usar sustituciones para agregar elementos al inicio / final si es necesario. Juntos, esto hace que la sugerencia de kasperd sea mucho más poderosa (y su script potencialmente mucho más legible).
Salidas del
Estoy usando los heredocs debido a su tipo de comportamiento de plantilla (wysiwyg). Producir la plantilla completa en otro lugar y, en mi opinión, pasarla al heredoc en realidad no hace que las cosas sean más fáciles de leer / mantener.
derHugo
2

Los heredocs siempre terminan en una nueva línea de caracteres, pero simplemente puede eliminar eso. La forma más fácil que conozco es con el headprograma:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF
David Foerster
fuente
¡Genial, no sabía que eso -ctambién toma valores negativos! Como eso, incluso más que la pearlsolución
derHugo
1

Puede eliminar las nuevas líneas de la forma que desee, la canalización trprobablemente sería la más simple. Esto eliminaría todas las nuevas líneas, por supuesto.

$ tr -d '\n' <<EOF 
if ((
EOF

Pero, la declaración if que está creando no requiere eso, la expresión aritmética (( .. ))debería funcionar bien incluso si contiene nuevas líneas.

Entonces, podrías hacer

cat <<EOF 
if ((
EOF
for name in x y; do 
    cat << EOF
    ( \$${name}_E != 0 && \$${name}_E != 1 ) ||
EOF
done
cat <<EOF
    0 )) ; then 
    echo do something
fi
EOF

productor

if ((
    ( $x_E != 0 && $x_E != 1 ) ||
    ( $y_E != 0 && $y_E != 1 ) ||
    0 )) ; then 
    echo do something
fi

Sin embargo, construirlo en un bucle todavía es feo. El || 0está ahí, por supuesto, para que no necesitemos un caso especial en la primera o la última línea.


Además, tienes eso | sudo tee ...en cada salida. Creo que podríamos deshacernos de eso abriendo una redirección solo una vez (con sustitución de proceso) y usándola para la impresión posterior:

exec 3> >(sudo tee outputfile &>/dev/null)
echo output to the pipe like this >&3
echo etc. >&3
exec 3>&-         # close it in the end

Y, francamente, sigo pensando que construir nombres de variables a partir de variables en el script externo (como ese \$${name}_E) es un poco feo, y probablemente debería reemplazarse con una matriz asociativa .

ilkkachu
fuente