Come assegnare un valore ereditario a una variabile in Bash?


374

Ho questa stringa multilinea (virgolette incluse):

abc'asdf"
$(dont-execute-this)
foo"bar"''

Come lo assegnerei a una variabile usando un heredoc in Bash?

Devo preservare le nuove righe.

Non voglio sfuggire ai personaggi nella stringa, sarebbe fastidioso ...


@JohnM - Ho appena provato un incarico ereditario 'EOF'con ` in the content: if the second line has virgolette singole , con interruzioni di riga con escape con comando cd`, torno indietro: " .sh: riga X: cd: comando non trovato "; ma se doppi virgolette "EOF"; quindi le variabili bash ${A}non vengono conservate come stringhe (vengono espanse); ma poi, le interruzioni di riga vengono mantenute - e, non ho problemi a eseguire un comando con cdin seconda riga ( e sia 'EOF' che "EOF" sembrano giocare bene anche con eval, per eseguire una serie di comandi memorizzati in una variabile stringa ). Saluti!
sdaau,

1
... e per aggiungere al mio commento precedente: bash commenti "#" nella "EOF"variabile double-qouted , se chiamato via eval $VAR, farà commentare tutto il resto dello script, poiché qui $ VAR verrà visto come una riga singola ; per poter usare i #commenti bash nello script multilinea, virgolette doppie anche variabili eval call: nell'eval "$ VAR" `.
sdaau,

@sdaau: ho avuto problemi con evalquesto metodo, ma non l'ho rintracciato poiché faceva parte di alcuni pacchetti che sono evalalcune variabili definite nel suo file di configurazione. Messaggio di errore era: /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. Sono appena passato a un'altra soluzione.
Golar Ramblar,

Risposte:


515

Puoi evitare un uso inutile cate gestire meglio le virgolette non corrispondenti con questo:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

Se non si cita la variabile quando la si fa eco, le nuove righe vengono perse. Citando, li conserva:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Se si desidera utilizzare il rientro per la leggibilità nel codice sorgente, utilizzare un trattino dopo i meno di. Il rientro deve essere eseguito utilizzando solo le schede (senza spazi).

$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
    EOF
$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Se, invece, desideri conservare le schede nei contenuti della variabile risultante, devi rimuovere la scheda IFS. Il marker terminale per here doc ( EOF) non deve essere rientrato.

$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF
$ echo "$VAR"
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''

Le schede possono essere inserite dalla riga di comando premendo Ctrl- V Tab. Se si utilizza un editor, a seconda di quale, potrebbe funzionare o potrebbe essere necessario disattivare la funzione che converte automaticamente le schede in spazi.


117
Penso che valga la pena ricordare che se hai set -o errexit(aka set -e) nel tuo script e lo usi, questo terminerà lo script perché readrestituisce un codice di ritorno diverso da zero quando raggiunge EOF.
Mark Byers,

14
@MarkByers: questo è uno dei motivi per cui non uso mai set -ee sconsiglio sempre di utilizzarlo. È preferibile utilizzare una corretta gestione degli errori. trapÈ tuo amico. Altri amici: elsee ||tra gli altri.
In pausa fino a nuovo avviso.

7
catIn tal caso, l'evitamento ne vale davvero la pena? Assegnare un'ereditarietà a una variabile con catè un linguaggio ben noto. In qualche modo usare le readcose offuscate per imho piccoli benefici.
Gregory Pakosz,

6
@ulidtko Questo perché non hai uno spazio tra de la stringa vuota; bashcrolla -rd''semplicemente -rdprima di readvedere i suoi argomenti, quindi VARviene trattato come argomento -d.
Chepner

6
In questo formato, readverrà restituito con un codice di uscita diverso da zero. Questo rende questo metodo tutt'altro che ideale in uno script con il controllo degli errori abilitato (ad es set -e.).
Svizzero

246

Usa $ () per assegnare l'output della cattua variabile in questo modo:

VAR=$(cat <<'END_HEREDOC'
abc'asdf"
$(dont-execute-this)
foo"bar"''
END_HEREDOC
)

# this will echo variable with new lines intact
echo "$VAR"
# this will echo variable without new lines (changed to space character)
echo $VAR

Assicurati di delimitare l'avvio di END_HEREDOC con virgolette singole.

Nota che il delimitatore ereditario finale END_HEREDOCdeve essere solo sulla linea (quindi la parentesi finale è sulla riga successiva).

Grazie @ephemientper la risposta.


1
In realtà, questo lascia passare citazioni in alcune circostanze. Sono riuscito a farlo facilmente in Perl ...
Neil,

32
+1. Questa è la soluzione più leggibile, almeno per i miei occhi. Lascia il nome della variabile all'estrema sinistra della pagina, invece di incorporarla nel comando read.
Clayton Stanley,

12
PSA: ricorda che la variabile deve essere quotata per preservare le nuove righe. echo "$VAR"invece di echo $VAR.
sevko,

7
Questo è bello con ashe OpenWRT dove readnon supporta -d.
David Ehrmann,

3
Per motivi che non riesco a capire, questo fallisce con un errore "EOF inatteso" se hai un backtick non accoppiato in eredità.
Radon Rosborough,

79

questa è la variazione del metodo Dennis, sembra più elegante negli script.

definizione della funzione:

define(){ IFS='\n' read -r -d '' ${1} || true; }

utilizzo:

define VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

echo "$VAR"

godere

ps ha creato una versione 'read loop' per shell che non supportano read -d. dovrebbe funzionare con set -eue apici inversi spaiati , ma non testato molto bene:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }

1
Questo sembra funzionare solo superficialmente. La funzione di definizione restituirà uno stato di 1 e non sono sicuro di cosa debba essere corretto.
fny

1
Questo è anche superiore alla risposta accettata, perché può essere modificato per supportare POSIX sh oltre a bash (un readloop nella funzione, per evitare il -d ''bashismo necessario per preservare le nuove righe).
ELLIOTTCABLE

A differenza dell'opzione cat-in-a-subshell, funziona con backtick non accoppiati nell'ereditarietà. Grazie!
Radon Rosborough,

Questa soluzione funziona con set -eset, mentre la risposta selezionata no. Sembra essere a causa dihttp://unix.stackexchange.com/a/265151/20650
sfogliando l'

2
Lo stato di ritorno di @fny ps è stato a lungo risolto
ttt

36
VAR=<<END
abc
END

non funziona perché stai reindirizzando lo stdin a qualcosa che non ti interessa, vale a dire il compito

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A

funziona, ma c'è un back-tic che potrebbe impedirti di usarlo. Inoltre, dovresti davvero evitare di usare i backtick, è meglio usare la notazione di sostituzione dei comandi $(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A

Ho aggiornato la mia domanda per includere $ (eseguibile). Inoltre, come si preservano le nuove linee?
Neil,

2
@ l0st3d: So close ... Usa $(cat <<'END'invece. @Neil: l'ultima riga non farà parte della variabile, ma il resto verrà preservato.
effimero

1
Non sembra che nessuna nuova riga sia stata preservata. Eseguendo l'esempio sopra vedo: "sdfsdf sdfsdf sdfsfds" ... ah! Ma scrivendo echo "$A"(ovvero mettendo $ A tra virgolette doppie) e vedi le nuove righe!
Darren Cook,

1
@Darren: aha! Avevo notato il problema delle newline e l'uso delle virgolette intorno alla variabile di output risolve il problema. grazie!
javadba,

1
È interessante notare che, a causa del capriccio del primo esempio, in un pizzico si può utilizzare per i blocchi di commento di fortuna come questo: REM=<< 'REM' ... comment block goes here ... REM. O più compatto, : << 'REM' .... Dove "REM" potrebbe essere qualcosa come "NOTE" o "SCRATCHPAD", ecc.
Beejor,

34

Non esiste ancora una soluzione che preservi le nuove linee.

Questo non è vero - probabilmente stai solo venendo fuorviato dal comportamento dell'eco:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines


5
In realtà questo è il comportamento di come funziona la quotazione di una variabile. Senza virgolette, le inserirà come parametri diversi, spazio delimitato, mentre con le virgolette l'intero contenuto della variabile verrà trattato come un argomento
Czipperz

11

Diramando la risposta di Neil , spesso non hai bisogno di una var, puoi usare una funzione più o meno allo stesso modo di una variabile ed è molto più facile da leggere rispetto alle readsoluzioni in linea o basate.

$ complex_message() {
  cat <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
}

$ echo "This is a $(complex_message)"
This is a abc'asdf"
$(dont-execute-this)
foo"bar"''

9

Un array è una variabile, quindi in questo caso mapfile funzionerà

mapfile y <<z
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

Quindi puoi stampare in questo modo

printf %s "${y[@]}"

3

assegnare un valore ereditario a una variabile

VAR="$(cat <<'VAREOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
VAREOF
)"

usato come argomento di un comando

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"

Quando ho provato il primo metodo, sembra che non ci siano terminatori di riga tra le righe. Deve essere una specie di configurazione sulla mia macchina linux?
Kemin Zhou,

Questo probabilmente significa che quando facevi eco alla tua variabile, non hai inserito virgolette intorno ... Provalo così:echo "$VAR"
Brad Parks

1

Mi sono ritrovato a dover leggere una stringa con NULL, quindi ecco una soluzione che leggerà tutto ciò che ci lancerai. Anche se in realtà hai a che fare con NULL, dovrai affrontarlo a livello esadecimale.

$ cat> read.dd.sh

read.dd() {
     buf= 
     while read; do
        buf+=$REPLY
     done < <( dd bs=1 2>/dev/null | xxd -p )

     printf -v REPLY '%b' $( sed 's/../ \\\x&/g' <<< $buf )
}

Prova:

$ . read.dd.sh
$ read.dd < read.dd.sh
$ echo -n "$REPLY" > read.dd.sh.copy
$ diff read.dd.sh read.dd.sh.copy || echo "File are different"
$ 

Esempio HEREDOC (con ^ J, ^ M, ^ I):

$ read.dd <<'HEREDOC'
>       (TAB)
>       (SPACES)
(^J)^M(^M)
> DONE
>
> HEREDOC

$ declare -p REPLY
declare -- REPLY="  (TAB)
      (SPACES)
(^M)
DONE

"

$ declare -p REPLY | xxd
0000000: 6465 636c 6172 6520 2d2d 2052 4550 4c59  declare -- REPLY
0000010: 3d22 0928 5441 4229 0a20 2020 2020 2028  =".(TAB).      (
0000020: 5350 4143 4553 290a 285e 4a29 0d28 5e4d  SPACES).(^J).(^M
0000030: 290a 444f 4e45 0a0a 220a                 ).DONE

0

Grazie alla risposta di dimo414 , questo mostra come funziona la sua ottima soluzione e mostra che puoi avere facilmente citazioni e variabili nel testo:

esempio di output

$ ./test.sh

The text from the example function is:
  Welcome dev: Would you "like" to know how many 'files' there are in /tmp?

  There are "      38" files in /tmp, according to the "wc" command

test.sh

#!/bin/bash

function text1()
{
  COUNT=$(\ls /tmp | wc -l)
cat <<EOF

  $1 Would you "like" to know how many 'files' there are in /tmp?

  There are "$COUNT" files in /tmp, according to the "wc" command

EOF
}

function main()
{
  OUT=$(text1 "Welcome dev:")
  echo "The text from the example function is: $OUT"
}

main

Sarebbe interessante vedere una citazione senza eguali nel testo per vedere come la gestisce. Forse `Non impazzire, ci sono file" $ COUNT ", quindi l'apostrofo / virgoletta singola può rendere le cose interessanti.
dragon788,

-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
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.