Y a-t-il quelque chose de similaire à echo -n dans heredoc (EOF)?

12

J'écris sur un énorme script de fabrique de scripts générant beaucoup de scripts de maintenance pour mes serveurs.

Jusqu'à présent, j'écris quelques lignes qui doivent être écrites sur une seule ligne avec echo -nepar exemple

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

cela me génère (s'il y a par exemple 2 serveurs dans mon fichier de configuration) du code comme

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

Tous les autres blocs de code qui n'ont pas besoin de cette écriture de code en ligne que je produis avec heredocs car je les trouve bien meilleurs à écrire et à maintenir. Par exemple, après le code supérieur, j'ai le reste généré comme

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

Ainsi, le bloc de code final ressemble

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

Ma question
Existe-t-il un moyen pour un hérédoc de se comporter comme echo -nesans le symbole de fin de ligne?

derHugo
la source
1
Si vous avez besoin sudod'un script, vous le faites mal: exécutez le script entier en tant que root à la place!
dessert
1
Non, car il fait également d'autres choses que je ne veux pas exécuter en tant que root
derHugo
L'utilisation sudo -u USERNAMEsur ce lieu, voir Comment puis-je exécuter une commande « sudo » dans un script? .
dessert
quel est le problème si je fais comme moi?
derHugo
4
Bien sûr, il y a un problème: un script sudosans nom d'utilisateur demande le mot de passe, sauf si vous l'avez tapé, il y a un délai. C'est encore pire si vous n'exécutez pas le script dans un terminal, vous ne pouvez même pas voir la requête de mot de passe et cela ne fonctionnera tout simplement pas. L' sudo -uapproche n'a aucun de ces problèmes.
dessert

Réponses:

9

Non, heredoc se termine toujours par une nouvelle ligne. Mais vous pouvez toujours supprimer la dernière nouvelle ligne, par exemple avec Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -plit l'entrée ligne par ligne, exécute le code spécifié dans -eet imprime la ligne (éventuellement modifiée)
  • chomp supprime la nouvelle ligne finale si elle existe, eof renvoie true à la fin du fichier.
choroba
la source
1
@Yaron si par fin de fichier rencontrée (dans ce cas, pipe fermée), il coupera le caractère de nouvelle ligne de la dernière ligne (ce que heredoc et herestring ajoutent automatiquement)
Sergiy Kolodyazhnyy
7

Existe-t-il un moyen pour un hérédoc de se comporter de la même manière que l'écho -ne sans le symbole de fin de ligne?

La réponse courte est que heredoc et herestrings sont juste construits de cette façon, donc non - ici-doc et herestring ajouteront toujours une nouvelle ligne de fin et il n'y a pas d'option ou de méthode native pour la désactiver (peut-être y aura-t-il dans les futures versions bash?).

Cependant, une façon de l'aborder via des méthodes bash uniquement serait de lire ce que heredoc donne via la while IFS= read -rméthode standard , mais d'ajouter un délai et d'imprimer la dernière ligne via printfsans la nouvelle ligne de fin. Voici ce que l'on pourrait faire avec bash uniquement:

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

Ce que vous voyez ici est la boucle while habituelle avec IFS= read -r lineapproche, mais chaque ligne est stockée dans une variable et donc retardée (nous sautons donc l'impression de la première ligne, la stockons et commençons l'impression uniquement lorsque nous avons lu la deuxième ligne). De cette façon, nous pouvons capturer la dernière ligne, et lorsque la prochaine itération readretourne le statut de sortie 1, c'est lorsque nous imprimons la dernière ligne sans nouvelle ligne via printf. Verbose? Oui. Mais ça marche:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Remarquez que le $caractère d'invite est poussé vers l'avant, car il n'y a pas de nouvelle ligne. En prime, cette solution est portable et ish Posix, il devrait donc travailler kshet dashaussi bien.

$ dash ./strip_heredoc_newline.sh                                 
one
two$
Sergiy Kolodyazhnyy
la source
6

Je vous recommande d'essayer une approche différente. Plutôt que de reconstituer votre script généré à partir de plusieurs déclarations d'écho et heredocs. Je vous suggère d'essayer d'utiliser un seul hérédoc.

Vous pouvez utiliser l'expansion variable et la substitution de commandes dans un hérédoc. Comme dans cet exemple:

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

Je trouve que cela conduit souvent à des résultats finaux beaucoup plus lisibles que la production de la pièce par pièce.

Il semble également que vous ayez oublié la #!ligne au début de vos scripts. La #!ligne est obligatoire dans tous les scripts correctement formatés. Tenter d'exécuter un script sans la #!ligne ne fonctionnera que si vous l'appelez à partir d'un shell qui fonctionne autour de scripts mal formatés, et même dans ce cas, cela pourrait finir par être interprété par un shell différent de celui que vous vouliez.

kasperd
la source
havent oublié le #!mais mon bloc de code n'est qu'un extrait d'un script de 2000 lignes;) Je ne vois pas en ce moment comment votre suggestion résout mon problème en générant une ligne de code dans une boucle while ...
derHugo
@derHugo La substitution de commandes pour la partie de la ligne où vous avez besoin d'une boucle est une façon de le faire. Une autre façon consiste à construire cette partie dans une variable avant l'hérédoc. Lequel des deux est le plus lisible dépend de la longueur du code nécessaire dans la boucle ainsi que du nombre de lignes de heredoc que vous avez avant la ligne en question.
kasperd
La substitution de commandes @derHugo invoquant une fonction shell définie précédemment dans le script est une troisième approche qui peut être plus lisible si vous avez besoin d'une quantité importante de code pour générer cette seule ligne.
kasperd
@derHugo: Notez que: (1) Vous pouvez avoir une boucle qui place toutes les commandes requises dans une variable; (2) Vous pouvez utiliser à l' $( ...command...)intérieur d'un hérédoc, pour insérer la sortie de ces commandes en ligne à ce point dans l'hérédoc; (3) Bash a des variables de tableau, que vous pouvez développer en ligne, et utilisez des substitutions pour ajouter des choses au début / à la fin si nécessaire. Ensemble, cela rend la suggestion de kasperd beaucoup plus puissante (et votre script potentiellement beaucoup plus lisible).
psmears
J'utilise les heredocs en raison de leur type de comportement de modèle (wysiwyg). Produire le modèle entier dans un autre endroit et le transmettre à l'hérédoc à mon avis ne facilite pas vraiment la lecture / l'entretien.
derHugo
2

Les Heredocs se terminent toujours par une nouvelle ligne, mais vous pouvez simplement supprimer cela. Le moyen le plus simple que je connaisse est avec le headprogramme:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF
David Foerster
la source
Cool je ne savais pas que ça -cprend aussi des valeurs négatives! Comme ça encore plus que la pearlsolution
derHugo
1

Vous pouvez supprimer les sauts de ligne comme vous le souhaitez, la tuyauterie trserait probablement la plus simple. Bien sûr, cela supprimerait toutes les nouvelles lignes.

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

Mais, l' instruction if que vous créez ne nécessite pas cela, l'expression arithmétique (( .. ))devrait fonctionner correctement même si elle contient des retours à la ligne.

Donc, vous pourriez faire

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

produire

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

Le construire en boucle rend toujours laide, cependant. Le || 0est là bien sûr pour que nous n'ayons pas besoin de cas particulier la première ou la dernière ligne.


De plus, vous en avez | sudo tee ...dans chaque sortie. Je pense que nous pourrions nous débarrasser de cela en ouvrant une redirection une seule fois (avec substitution de processus), et en l'utilisant pour l'impression ultérieure:

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

Et franchement, je pense toujours que construire des noms de variables à partir de variables dans le script externe (comme ça \$${name}_E) est un peu moche, et il devrait probablement être remplacé par un tableau associatif .

ilkkachu
la source