C'è qualcosa di simile a echo -n in heredoc (EOF)?


12

Sto scrivendo un enorme script di script-factory che genera molti script di manutenzione per i miei server.

Fino ad ora scrivo alcune righe che devono essere scritte in una riga con echo -nees

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

questo mi genera (se ci sono ad esempio 2 server nel mio file di configurazione) come

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

Tutti gli altri blocchi di codice che non necessitano di questa scrittura in linea del codice che produco con heredocs poiché li trovo molto meglio da scrivere e mantenere. Ad esempio dopo il codice superiore ho il resto generato come

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

Quindi appare il blocco di codice finale

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

La mia domanda
Esiste un modo in cui un heredoc si comporta in modo simile echo -nesenza il simbolo di fine riga?


1
Se hai bisogno sudodi uno script, stai sbagliando: esegui invece l'intero script come root!
dessert

1
No perché sta facendo anche altre cose che non voglio eseguire come root
derHugo,

L'uso sudo -u USERNAMEsu quello invece, vedi Come posso eseguire un comando 'sudo' all'interno di uno script? .
dessert

qual è il problema farlo come faccio io?
derHugo,

4
Bene, ovviamente c'è un problema: uno script sudosenza nome utente richiede la password, a meno che tu non l'abbia digitata c'è un ritardo. È ancora peggio se non si esegue lo script in un terminale, quindi non è nemmeno possibile visualizzare la query della password e non funzionerà. L' sudo -uapproccio non ha nessuno di questi problemi.
dessert

Risposte:


9

No, l'eredità finisce sempre con una nuova riga. Ma puoi ancora rimuovere l'ultima nuova riga, ad esempio con Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -plegge l'input riga per riga, esegue il codice specificato in -ee stampa la riga (eventualmente modificata)
  • chomp rimuove la nuova riga finale se esiste, eof restituisce true alla fine del file.

1
@Yaron se per fine file incontrata (in questo caso pipe chiusa) troncerà il carattere newline dall'ultima riga (che è ciò che ereditano automaticamente eredità e eredità)
Sergiy Kolodyazhnyy,

7

Esiste un modo in cui un heredoc si comporta in modo simile all'eco -ne senza il simbolo di fine riga?

La risposta breve è che l'ereditarietà e le eredità sono state costruite in quel modo, quindi no - qui-doc e l'estrazione aggiungeranno sempre una nuova riga finale e non c'è alcuna opzione o modo nativo per disabilitarla (forse ci saranno in future versioni di bash?).

Tuttavia, un modo per affrontarlo tramite solo bash sarebbe quello di leggere ciò che Heredoc fornisce tramite il while IFS= read -rmetodo standard , ma aggiungere un ritardo e stampare l'ultima riga via printfsenza la nuova riga finale. Ecco cosa si potrebbe fare con solo bash:

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

Quello che vedi qui è il solito ciclo while con IFS= read -r lineapproccio, tuttavia ogni riga è memorizzata in una variabile e quindi ritardata (quindi saltiamo la stampa della prima riga, la memorizziamo e iniziamo a stampare solo quando abbiamo letto la seconda riga). In questo modo possiamo catturare l'ultima riga e quando alla successiva iterazione readrestituisce lo stato di uscita 1, è quando stampiamo l'ultima riga senza newline tramite printf. Verbose? sì. Ma funziona:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Notare che il $carattere del prompt viene spinto in avanti, poiché non esiste una nuova riga. Come bonus, questa soluzione è portatile e POSIX-ish, così dovrebbe funzionare in kshe dashpure.

$ dash ./strip_heredoc_newline.sh                                 
one
two$

6

Ti consiglio di provare un approccio diverso. Invece di mettere insieme il tuo script generato da più istruzioni eche ed eredità. Ti suggerisco di provare a usare una sola eredità.

Puoi usare l'espansione variabile e la sostituzione dei comandi all'interno di un ereditario. Come in questo esempio:

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

Trovo che ciò porti spesso a risultati finali molto più leggibili rispetto alla produzione dell'output pezzo per pezzo.

Inoltre sembra che tu abbia dimenticato la #!riga all'inizio dei tuoi script. La #!riga è obbligatoria in tutti gli script correttamente formattati. Il tentativo di eseguire uno script senza la #!linea funzionerà solo se lo si chiama da una shell che funziona con script mal formattati e anche in quel caso potrebbe finire per essere interpretato da una shell diversa da quella che si intendeva.


non #!ho mai dimenticato ma il mio blocco di codice è solo uno snippet di uno script di 2000 righe;) Non vedo adesso come il tuo suggerimento risolva il mio problema generando una riga di codice in un ciclo while ...
derHugo

@derHugo La sostituzione del comando per la parte della riga in cui è necessario un loop è un modo per farlo. Un altro modo è quello di costruire quella parte in una variabile prima dell'ereditarietà. Quale dei due è più leggibile dipende dalla lunghezza del codice necessario nel loop e da quante righe di eredità hai prima della riga in questione.
Kasperd,

La sostituzione del comando @derHugo che richiama una funzione shell definita precedentemente nello script è un terzo approccio che può essere più leggibile se è necessaria una quantità significativa di codice per generare quella riga.
Kasperd,

@derHugo: Nota che: (1) Puoi avere un ciclo che mette tutti i comandi richiesti in una variabile; (2) Puoi usare $( ...command...)all'interno di una eredità, per inserire l'output di quei comandi in linea in quel punto della eredità; (3) Bash ha variabili di array, che è possibile espandere in linea e utilizzare le sostituzioni per aggiungere elementi all'inizio / alla fine, se necessario. Insieme, questi rendono il suggerimento di Kasperd molto più potente (e la tua sceneggiatura potenzialmente molto più leggibile).
psmears,

Sto usando gli heredocs a causa del loro tipo di comportamento del modello (wysiwyg). Produrre l'intero modello in un altro posto e poi passarlo all'eredità secondo me non rende le cose più facili da leggere / mantenere.
derHugo,

2

Gli heredocs finiscono sempre con caratteri di nuova riga, ma puoi semplicemente rimuoverli. Il modo più semplice che conosco è con il headprogramma:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF

Fantastico, non sapevo che -cassumesse anche valori negativi! Piace anche di più della pearlsoluzione
derHugo,

1

Potresti rimuovere le nuove righe come preferisci, trprobabilmente le tubazioni sarebbero le più semplici. Ciò eliminerebbe tutte le nuove linee, ovviamente.

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

Ma l' istruzione if che stai costruendo non lo richiede, l'espressione aritmetica (( .. ))dovrebbe funzionare bene anche se contiene nuove righe.

Quindi, potresti farlo

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

produzione

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

Costruirlo in un ciclo rende comunque brutto. Il || 0c'è, naturalmente, in modo che non abbiamo bisogno di caso speciale la prima o l'ultima riga.


Inoltre, ce l'hai | sudo tee ...in ogni output. Penso che potremmo liberarcene aprendo un reindirizzamento una sola volta (con sostituzione del processo) e utilizzandolo per la stampa successiva:

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

E francamente, penso ancora che costruire nomi di variabili da variabili nello script esterno (come quello \$${name}_E) sia un po 'brutto, e probabilmente dovrebbe essere sostituito con un array associativo .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.