Czy jest coś podobnego do echa -n w heredoc (EOF)?

12

Piszę na ogromnym skrypcie fabrycznym, generującym wiele skryptów konserwacyjnych dla moich serwerów.

Do tej pory piszę kilka wierszy, które należy zapisać w jednym wierszu echo -nenp

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

generuje to (jeśli w moim pliku konfiguracyjnym są np. 2 serwery) kod podobny do

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

Wszystkie inne bloki kodu, które nie wymagają tego wbudowanego pisania kodu, tworzę z heredocami, ponieważ uważam, że są one o wiele lepsze do pisania i zarządzania. Np. Po górnym kodzie mam resztę wygenerowaną jak

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

Tak wygląda ostatni blok kodu

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

Moje pytanie
Czy istnieje sposób, aby heredok zachowywał się podobnie jak echo -nebez symbolu końca linii?

derHugo
źródło
1
Jeśli potrzebujesz sudoskryptu, robisz to źle: Zamiast tego uruchom cały skrypt jako root!
deser
1
Nie, ponieważ robi też inne rzeczy, których nie chcę uruchamiać jako root
derHugo,
Zastosowanie sudo -u USERNAMEna który zamiast zobacz Jak uruchomić polecenie „sudo” wewnątrz skryptu? .
deser
na czym polega problem robienia tego tak jak ja?
derHugo
4
No cóż, oczywiście jest problem: skrypt sudobez nazwy użytkownika prosi o hasło, chyba że je wpiszesz. Gorzej, jeśli nie uruchomisz skryptu w terminalu, wtedy nawet nie zobaczysz zapytania o hasło i po prostu nie zadziała. sudo -uPodejście nie ma żadnego z tych problemów.
deser

Odpowiedzi:

9

Nie, heredoc zawsze kończy się nową linią. Ale nadal możesz usunąć ostatnią nową linię, na przykład za pomocą Perla:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -podczytuje wejściowy wiersz po wierszu, uruchamia kod określony w -ei drukuje (ewentualnie zmodyfikowaną) linię
  • chomp usuwa ostatnią nową linię, jeśli istnieje, eof zwraca true na końcu pliku.
choroba
źródło
1
@Yaron, jeśli na napotkany koniec pliku (w tym przypadku zamknięty potok), będzie chomp znak nowego wiersza z ostatniej linii (co heredoc i herestring dodają automatycznie)
Sergiy Kolodyazhnyy
7

Czy istnieje sposób, aby heredok zachowywał się podobnie do echa -ne bez symbolu końca linii?

Krótka odpowiedź jest taka, że ​​heredoc i tustrings są właśnie budowane w ten sposób, więc nie - tutaj-doc i tustring będą zawsze dodawać końcowy znak nowej linii i nie ma żadnej opcji ani natywnego sposobu, aby go wyłączyć (być może będą w przyszłych wydaniach bash?).

Jednak jednym ze sposobów, aby podejść do tego tylko za pomocą bash, byłoby odczytanie tego, co heredoc daje standardową while IFS= read -rmetodą, ale dodanie opóźnienia i wydrukowanie ostatniej linii printfbez końca nowej linii. Oto, co można zrobić z 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

To, co tu widzisz, jest zwykłą pętlą while z IFS= read -r linepodejściem, jednak każda linia jest przechowywana w zmiennej, a zatem opóźniona (więc pomijamy drukowanie pierwszej linii, przechowujemy ją i rozpoczynamy drukowanie dopiero po przeczytaniu drugiej linii). W ten sposób możemy przechwycić ostatnią linię, a kiedy przy następnej iteracji readzwraca status wyjścia 1, wtedy drukujemy ostatnią linię bez nowej linii przez printf. Pełne? tak. Ale to działa:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Zauważ, że $znak zachęty jest przesuwany do przodu, ponieważ nie ma nowego wiersza. Jako bonus, to rozwiązanie jest przenośny i POSIX-owski, więc powinien działać na kshi dashtak dobrze.

$ dash ./strip_heredoc_newline.sh                                 
one
two$
Sergiy Kolodyazhnyy
źródło
6

Polecam spróbować innego podejścia. Zamiast składać wygenerowany skrypt z wielu instrukcji echa i heredoków. Sugeruję, aby spróbować użyć jednego heredoc.

Możesz użyć rozszerzania zmiennych i podstawiania poleceń w heredoc. Jak w tym przykładzie:

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

Uważam, że często prowadzi to do znacznie bardziej czytelnych rezultatów końcowych niż wytwarzanie wyników kawałek po kawałku.

Wygląda na to, że zapomniałeś #!linii na początku swoich skryptów. #!Linia jest obowiązkowe w każdym poprawnie sformatowanych skryptów. Próba uruchomienia skryptu bez #!wiersza będzie działać tylko wtedy, gdy wywołasz go z powłoki, która działa wokół źle sformatowanych skryptów, a nawet w takim przypadku może zostać zinterpretowany przez inną powłokę niż zamierzałeś.

kasperd
źródło
nie zapomniałem, #!ale mój blok kodu to tylko jeden fragment skryptu z 2000 wierszy;) Nie rozumiem teraz, w jaki sposób twoja sugestia rozwiązuje mój problem z generowaniem jednego wiersza kodu w pętli while ...
derHugo
@derHugo Podstawienie polecenia dla części linii, w której potrzebujesz pętli, jest jednym ze sposobów. Innym sposobem jest skonstruowanie tej części zmiennej przed heredoc. To, który z nich jest najbardziej czytelny, zależy od długości kodu potrzebnego w pętli, a także od liczby linii heredoc, które masz przed tą linią.
kasperd
@derHugo Podstawienie polecenia wywołujące funkcję powłoki zdefiniowaną wcześniej w skrypcie jest trzecim podejściem, które może być bardziej czytelne, jeśli potrzebujesz znacznej ilości kodu do wygenerowania tej jednej linii.
kasperd
@derHugo: Uwaga: (1) Możesz mieć pętlę, która umieszcza wszystkie wymagane polecenia w zmiennej; (2) Możesz użyć $( ...command...)wewnątrz heredoc, aby wstawić wynik tych poleceń inline w tym punkcie heredoc; (3) Bash ma zmienne tablicowe, które można rozwinąć w linii i użyć podstawień, aby dodać rzeczy na początku / końcu, jeśli to konieczne. Razem sprawiają, że sugestia kasperda jest o wiele silniejsza (a twój skrypt potencjalnie o wiele bardziej czytelny).
psmears
Używam heredoków ze względu na ich zachowanie szablonu (wysiwyg). Stworzenie całego szablonu w innym miejscu i przekazanie go heredocowi, moim zdaniem, tak naprawdę nie ułatwia czytania / utrzymania.
derHugo,
2

Heredoki zawsze kończą się znakami nowej linii, ale możesz to po prostu usunąć. Najłatwiejszy sposób, jaki znam, to headprogram:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF
David Foerster
źródło
Fajnie, nie wiedziałem, że to -ctakże przyjmuje wartości ujemne! Tak to nawet bardziej niż pearlrozwiązanie
derHugo,
1

Możesz usunąć nowe wiersze w dowolny sposób, przesyłanie potoków do trbyłoby prawdopodobnie najprostsze. Oczywiście usunęłoby to wszystkie nowe wiersze.

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

Ale budowana instrukcja if nie wymaga tego, wyrażenie arytmetyczne (( .. ))powinno działać dobrze, nawet jeśli zawiera znaki nowej linii.

Więc możesz zrobić

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

produkować

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

Jednak budowanie go w pętli wciąż jest brzydkie. || 0Tam jest oczywiście tak, że nie trzeba specjalnego przypadku pierwszej lub ostatniej linii.


Masz to również | sudo tee ...na każdym wyjściu. Myślę, że moglibyśmy się tego pozbyć, otwierając przekierowanie tylko raz (z podstawieniem procesu) i używając go do późniejszego drukowania:

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

I szczerze mówiąc, nadal uważam, że budowanie nazw zmiennych ze zmiennych w zewnętrznym skrypcie (jak ten \$${name}_E) jest nieco brzydkie i prawdopodobnie powinno zostać zastąpione tablicą asocjacyjną .

ilkkachu
źródło