Gibt es etwas Ähnliches wie echo -n im Heredoc (EOF)?

12

Ich schreibe über ein riesiges Skript-Factory-Skript, das viele Wartungsskripte für meine Server generiert.

Bis jetzt schreibe ich einige Zeilen, die in einer Zeile mit echo -nez

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

Dies erzeugt mir (wenn sich zB 2 Server in meiner Konfigurationsdatei befinden) Code wie

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

Alle anderen Codeblöcke, die dieses Inline-Schreiben von Code nicht benötigen, produziere ich mit Heredocs, da ich finde, dass sie viel besser zu schreiben und zu pflegen sind. ZB nach dem oberen Code habe ich den Rest wie generiert

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

Der letzte Codeblock sieht also so aus

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

Meine Frage
Gibt es eine Möglichkeit, wie sich ein Heredoc ähnlich verhält wie echo -neohne das Zeilenende-Symbol?

derHugo
quelle
1
Wenn Sie sudoin einem Skript etwas falsch machen müssen: Führen Sie stattdessen das gesamte Skript als root aus!
Dessert
1
Nein, weil es auch andere Sachen macht, die ich nicht als root
ausführen
Die Verwendung sudo -u USERNAMEauf , dass statt findet Wie kann ich einen ‚sudo‘ Befehl in einem Skript ausführen? .
Dessert
Was ist das Problem dabei?
derHugo
4
Natürlich gibt es ein Problem: Ein Skript sudoohne Benutzernamen fragt nach dem Passwort, es sei denn, Sie haben es eingegeben, es gibt eine Verzögerung. Es ist noch schlimmer, wenn Sie das Skript nicht in einem Terminal ausführen, dann können Sie nicht einmal die Kennwortabfrage sehen und es wird einfach nicht funktionieren. Der sudo -uAnsatz hat keines dieser Probleme.
Dessert

Antworten:

9

Nein, Heredoc endet immer in einer neuen Zeile. Sie können den letzten Zeilenumbruch jedoch weiterhin entfernen, z. B. mit Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -pliest die Eingabe zeilenweise, führt den in angegebenen Code aus -eund druckt die (möglicherweise geänderte) Zeile
  • chomp entfernt den letzten Zeilenumbruch, falls vorhanden, eof gibt am Ende der Datei true zurück.
Choroba
quelle
1
@Yaron, wenn pro angetroffenem Ende der Datei (in diesem Fall geschlossene Pipe) wird es Zeilenumbruch Zeichen aus der letzten Zeile chomp (was Heredoc und Herestring automatisch hinzufügen)
Sergiy Kolodyazhnyy
7

Gibt es eine Möglichkeit, dass sich ein Heredoc ähnlich wie echo -ne ohne das Zeilenende-Symbol verhält?

Die kurze Antwort lautet, dass Heredoc und Herestrings nur auf diese Weise erstellt wurden. Nein, Here-Doc und Herestring fügen immer eine nachgestellte Newline hinzu, und es gibt keine Option oder native Möglichkeit, sie zu deaktivieren (möglicherweise gibt es sie in zukünftigen Bash-Versionen?).

Eine Möglichkeit, dies nur über Bash zu erreichen, besteht darin, zu lesen, was Heredoc über die Standardmethode gibt while IFS= read -r, aber eine Verzögerung hinzuzufügen und die letzte Zeile printfohne die nachfolgende neue Zeile zu drucken. Folgendes könnte man nur mit Bash machen:

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

Was Sie hier sehen, ist die übliche while-Schleife mit IFS= read -r lineAnnäherung, jedoch wird jede Zeile in einer Variablen gespeichert und somit verzögert (daher überspringen wir das Drucken der ersten Zeile, speichern sie und beginnen erst dann mit dem Drucken, wenn wir die zweite Zeile gelesen haben). Auf diese Weise können wir die letzte Zeile erfassen. Wenn bei der nächsten Iteration readder Exit-Status 1 zurückgegeben wird, wird die letzte Zeile ohne Zeilenumbruch über gedruckt printf. Ausführlich? Ja. Aber es funktioniert:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Beachten Sie, dass das $Eingabeaufforderungszeichen nach vorne verschoben wird, da keine neue Zeile vorhanden ist. Als Bonus ist diese Lösung portabel und POSIX-artig, daher sollte sie auch in kshund funktionieren dash.

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

Ich empfehle Ihnen, einen anderen Ansatz zu versuchen. Anstatt Ihr generiertes Skript aus mehreren Echoanweisungen und Heredocs zusammenzusetzen. Ich schlage vor, Sie versuchen, einen einzelnen Heredoc zu verwenden.

Sie können die Variablenerweiterung und Befehlssubstitution in einem Heredoc verwenden. Wie in diesem Beispiel:

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

Ich finde, dass dies oft zu viel besser lesbaren Endergebnissen führt, als die Ausgabe Stück für Stück zu produzieren.

Es sieht auch so aus, als hätten Sie die #!Zeile am Anfang Ihrer Skripte vergessen . Die #!Zeile ist in korrekt formatierten Skripten obligatorisch. Der Versuch, ein Skript ohne die #!Zeile auszuführen , funktioniert nur, wenn Sie es von einer Shell aus aufrufen, die schlecht formatierte Skripte umgeht, und selbst in diesem Fall wird es möglicherweise von einer anderen Shell interpretiert, als Sie beabsichtigt haben.

Kasperd
quelle
Ich habe das nicht vergessen, #!aber mein Codeblock ist nur ein Ausschnitt aus einem 2000-Zeilen-Skript. Ich sehe derzeit nicht, wie Ihr Vorschlag mein Problem löst, eine Codezeile in einer while-Schleife zu
generieren
@derHugo Befehlsersetzung für den Teil der Zeile, in dem Sie eine Schleife benötigen, ist eine Möglichkeit, dies zu tun. Eine andere Möglichkeit besteht darin, diesen Teil in einer Variablen vor dem Heredoc zu erstellen. Welche der beiden am besten lesbar ist, hängt von der Länge des in der Schleife benötigten Codes sowie von der Anzahl der Heredoc-Zeilen ab, die Sie vor der betreffenden Zeile haben.
Kasperd
@derHugo Die Befehlssubstitution, die eine zuvor im Skript definierte Shell-Funktion aufruft, ist ein dritter Ansatz, der möglicherweise besser lesbar ist, wenn Sie eine erhebliche Menge Code benötigen, um diese eine Zeile zu generieren.
Kasperd
@derHugo: Beachten Sie Folgendes: (1) Sie können eine Schleife haben, die alle erforderlichen Befehle in eine Variable einfügt. (2) Sie können $( ...command...)innerhalb eines Heredocs verwenden, um die Ausgabe dieser Befehle an dieser Stelle im Heredoc inline einzufügen. (3) Bash verfügt über Array-Variablen, die Sie inline erweitern und bei Bedarf durch Ersetzungen am Anfang / Ende hinzufügen können. Zusammen machen diese den Vorschlag von Kasperd viel leistungsfähiger (und Ihr Skript möglicherweise viel lesbarer).
Psmears
Ich benutze die Heredocs wegen ihres Vorlagenverhaltens (wysiwyg). Die gesamte Vorlage an einem anderen Ort zu erstellen und sie meiner Meinung nach an den Heredoc weiterzugeben, macht das Lesen / Verwalten nicht wirklich einfacher.
derHugo
2

Heredocs enden immer in neuen Zeilen, aber Sie können diese einfach entfernen. Der einfachste Weg, den ich kenne, ist mit dem headProgramm:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF
David Foerster
quelle
Cool Ich wusste nicht, dass das -cauch negative Werte annimmt! So noch mehr als die pearlLösung
derHugo
1

Sie können die Zeilenumbrüche nach Belieben entfernen. Die Weiterleitung ist trwahrscheinlich am einfachsten. Dies würde natürlich alle Zeilenumbrüche entfernen.

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

Für die if- Anweisung, die Sie erstellen, ist dies jedoch nicht erforderlich. Der arithmetische Ausdruck (( .. ))sollte auch dann einwandfrei funktionieren, wenn er Zeilenumbrüche enthält.

Also könntest du es tun

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

produzieren

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

Es ist jedoch immer noch hässlich, es in einer Schleife zu bauen. Das || 0ist natürlich da, damit wir die erste oder letzte Zeile nicht als Sonderfall verwenden müssen.


Das haben Sie auch | sudo tee ...in jeder Ausgabe. Ich denke, wir könnten das beseitigen, indem wir eine Umleitung nur einmal öffnen (mit Prozessersetzung) und diese für den späteren Druck verwenden:

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

Und ehrlich gesagt denke ich immer noch, dass das Erstellen von Variablennamen aus Variablen im äußeren Skript (so \$${name}_E) etwas hässlich ist und wahrscheinlich durch ein assoziatives Array ersetzt werden sollte .

ilkkachu
quelle