Jak przypisać wartość heredoc do zmiennej w Bash?

374

Mam ten ciąg wieloliniowy (w tym cytaty):

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

Jak przypisałbym ją do zmiennej za pomocą heredoc w Bash?

Muszę zachować nowe linie.

Nie chcę uciec od znaków w ciągu, co byłoby denerwujące ...

Neil
źródło
@JohnM - Właśnie próbowałem przypisania heredoc z pojedynczym cudzysłowem 'EOF', ze ` in the content: if the second line has znakami ucieczki z komendami cd`, zwracam: " .sh: linia X: cd: polecenie nie znaleziono "; ale jeśli podwójnie zacytuję "EOF"; wtedy zmienne bash ${A}nie są zachowywane jako ciągi znaków (są one rozwijane); ale potem zachowywane podziały wierszy - i nie mam problemu z uruchomieniem polecenia cdw drugim wierszu ( i zarówno „EOF”, jak i „EOF” wydają się dobrze grać z eval, do uruchamiania zestawu poleceń przechowywanych w zmienna łańcuchowa ). Twoje zdrowie!
sdaau
1
... i aby dodać do mojego poprzedniego komentarza: komentarze bash „#” w "EOF"zmiennej podwójnie qouted , jeśli zostanie wywołane przez eval $VAR, spowoduje skomentowanie całej reszty skryptu, ponieważ tutaj $ VAR będzie widoczny jako pojedynczy wiersz ; aby móc używać #komentarzy bash w skrypcie wielowierszowym, podwójny cudzysłów również zmienny w eval call: ewaluacji „$ VAR”.
sdaau
@ sdaau: Miałem problemy z evaltymi metodami, ale nie wyśledziłem go, ponieważ był on częścią jakiegoś pakietu, który zawiera evalpewne zmienne zdefiniowane w jego pliku konfiguracyjnym. Komunikat o błędzie: /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. Właśnie przeszedłem na inne rozwiązanie.
Golar Ramblar,

Odpowiedzi:

515

Można uniknąć niepotrzebnego użycia cati obsługi niedopasowane cytaty lepiej z tym:

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

Jeśli nie podasz zmiennej podczas echa, znaki nowej linii zostaną utracone. Cytując to zachowuje je:

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

Jeśli chcesz użyć wcięcia dla czytelności w kodzie źródłowym, użyj myślnika po mniej niż. Wcięcie należy wykonać tylko przy użyciu tabulatorów (bez spacji).

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

Jeśli zamiast tego chcesz zachować tabulatory w treści wynikowej zmiennej, musisz usunąć tabulatory z IFS. Znacznik końcowy tutaj doc ( EOF) nie może być wcięty.

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

Karty można wstawiać w wierszu polecenia, naciskając Ctrl- V Tab. Jeśli używasz edytora, w zależności od tego, który może również działać lub może być konieczne wyłączenie funkcji, która automatycznie konwertuje tabulatory na spacje.

Wstrzymano do odwołania.
źródło
117
Myślę, że warto wspomnieć, że jeśli masz set -o errexit(aka set -e) w swoim skrypcie i użyjesz go, to zakończy skrypt, ponieważ readzwraca niezerowy kod powrotu, gdy osiągnie EOF.
Mark Byers
14
@MarkByers: To jeden z powodów, dla których nigdy nie używam set -ei zawsze odradzam jego użycie. Zamiast tego lepiej jest użyć odpowiedniej obsługi błędów. trapjest twoim przyjacielem. Inni przyjaciele: elsei ||między innymi.
Wstrzymano do odwołania.
7
Czy catw takim przypadku unikanie naprawdę jest tego warte? Przypisywanie heredoc do zmiennej za pomocą catjest dobrze znanym idiomem. Jakoś użycie readzaciemnia rzeczy dla małych korzyści imho.
Gregory Pakosz
6
@ulidtko To dlatego, że nie ma spacji między di pusty ciąg; bashzapada -rd''się tak, że nigdy -rdprzedtem readnie widzi swoich argumentów, więc VARjest traktowany jak argument do -d.
chepner
6
W tym formacie readzwróci niezerowy kod wyjścia. To sprawia, że ​​ta metoda nie jest idealna w skrypcie z włączoną funkcją sprawdzania błędów (np set -e.).
Szwajcarski
245

Użyj $ (), aby przypisać wynik catswojej zmiennej w następujący sposób:

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

Pamiętaj, by oddzielić END_HEREDOC od pojedynczych cudzysłowów.

Zauważ, że ogranicznik heredoc kończący END_HEREDOCmusi znajdować się sam na linii (stąd nawias kończący znajduje się w następnej linii).

Dzięki @ephemientza odpowiedź.

Neil
źródło
1
Właściwie to w niektórych okolicznościach przepuszcza cytaty. Z łatwością udało mi się to zrobić w Perlu ...
Neil,
32
+1. To jest najbardziej czytelne rozwiązanie, przynajmniej dla moich oczu. Pozostawia nazwę zmiennej po lewej stronie, zamiast osadzać ją w poleceniu read.
Clayton Stanley,
12
PSA: pamiętaj, że zmienna musi być cytowana, aby zachować nowe wiersze. echo "$VAR"zamiast echo $VAR.
sevko
7
To jest miłe z ashOpenWRT, gdzie readnie obsługuje -d.
David Ehrmann
3
Z powodów, których nie mogę pojąć, nie powiedzie się to z powodu „nieoczekiwanego błędu EOF”, jeśli w heredoc masz niesparowany backstick.
Radon Rosborough,
79

jest to odmiana metody Dennisa, wygląda bardziej elegancko w skryptach.

definicja funkcji:

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

stosowanie:

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

echo "$VAR"

cieszyć się

ps utworzył wersję „pętli odczytu” dla powłok, które nie obsługują read -d. powinny pracować z set -eui niesparowanych backticks , ale nie badanych bardzo dobrze:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }
ttt
źródło
1
To wydaje się działać tylko powierzchownie. Funkcja definiująca zwróci status 1 i nie jestem pewien, co należy poprawić.
fny
1
Jest to również lepsze niż zaakceptowana odpowiedź, ponieważ można ją zmodyfikować tak, aby obsługiwała POSIX sh oprócz bash ( readpętla w funkcji, aby uniknąć -d ''bashizmu niezbędnego do zachowania nowych linii).
ELLIOTTCABLE,
W przeciwieństwie do opcji cat-in-a-shell, działa to z niesparowanymi backtickami w heredoc. Dziękuję Ci!
Radon Rosborough,
To rozwiązanie działa z set -eustawionym zestawem, a wybrana odpowiedź nie. Wydaje się, że http://unix.stackexchange.com/a/265151/20650
dzieje
2
@fny ps status powrotu został już naprawiony
ttt
36
VAR=<<END
abc
END

nie działa, ponieważ przekierowujesz stdin do czegoś, co go nie obchodzi, a mianowicie do zadania

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

działa, ale jest tam back-tic, który może powstrzymać cię od korzystania z tego. Ponadto naprawdę powinieneś unikać używania odwrotnych wskazówek, lepiej jest użyć notacji zastępowania poleceń $(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A
l0st3d
źródło
Zaktualizowałem moje pytanie, aby zawierało $ (plik wykonywalny). Jak zachować nowe wiersze?
Neil
2
@ l0st3d: Tak blisko ... Użyj $(cat <<'END'zamiast tego. @Neil: Ostatnia nowa linia nie będzie częścią zmiennej, ale reszta zostanie zachowana.
ephemient
1
Wygląda na to, że nie zachowano żadnych nowych linii. W powyższym przykładzie widzę: „sdfsdf sdfsdf sdfsfds” ... ah! Ale pisząc echo "$A"(tj. Umieszczając $ A w podwójnych cudzysłowach) i widzisz nowe linie!
Darren Cook
1
@Darren: aha! Zauważyłem problem nowego wiersza i użycie cudzysłowu wokół zmiennej wyjściowej naprawia problem. dzięki!
javadba
1
Co ciekawe, ze względu na dziwactwo pierwszym przykładzie, w celowniku można użyć go do prowizorycznych bloków komentarzy jak ten: REM=<< 'REM' ... comment block goes here ... REM. Lub bardziej zwięźle, : << 'REM' .... Gdzie „REM” może być czymś w rodzaju „UWAGI” lub „SCRATCHPAD” itp.
Beejor,
34

Nadal nie ma rozwiązania, które zachowuje nowe linie.

To nieprawda - prawdopodobnie echo wprowadza Cię w błąd:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

patspam
źródło
5
Naprawdę jest to sposób działania cytowania zmiennej. Bez cudzysłowów wstawi je jako różne parametry, z ograniczeniem miejsca, natomiast w cudzysłowach cała zawartość zmiennej będzie traktowana jako jeden argument
Czipperz
11

Opierając się na odpowiedzi Neila , często nie potrzebujesz w ogóle var, możesz użyć funkcji w taki sam sposób jak zmienną i jest znacznie łatwiejszy do odczytania niż rozwiązania wbudowane lub readoparte na bazie .

$ 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"''
dimo414
źródło
9

Tablica jest zmienną, więc w takim przypadku plik map będzie działał

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

Następnie możesz wydrukować w ten sposób

printf %s "${y[@]}"
Steven Penny
źródło
3

przypisz wartość heredoc do zmiennej

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

używany jako argument polecenia

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"
człowiek z brązu
źródło
Kiedy wypróbowałem pierwszą metodę, wydaje się, że między liniami nie ma terminatorów linii. Czy musi to być jakaś konfiguracja na moim komputerze z systemem Linux?
Kemin Zhou
Prawdopodobnie oznacza to, że podczas echa zmiennej nie wstawiłeś cudzysłowów ... Spróbuj tak:echo "$VAR"
Brad Parks
1

Przekonałem się, że muszę przeczytać ciąg znaków z wartością NULL, więc oto rozwiązanie, które przeczyta wszystko, co na niego rzucisz. Chociaż jeśli faktycznie masz do czynienia z NULL, będziesz musiał sobie z tym poradzić na poziomie heksów.

$ 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 )
}

Dowód:

$ . 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"
$ 

Przykład HEREDOC (z ^ 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
Orwellophile
źródło
0

Dzięki odpowiedzi dimo414 pokazuje to, jak działa jego świetne rozwiązanie, a także pokazuje, że w tekście można łatwo umieszczać cytaty i zmienne:

przykładowe wyjście

$ ./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
Brad Parks
źródło
Byłoby interesujące zobaczyć niedopasowany cytat w tekście, aby zobaczyć, jak sobie z tym radzi. Może „Nie wystrasz się, są pliki„ $ COUNT ”, więc apostrof / pojedynczy cudzysłów może sprawić, że wszystko będzie interesujące.
dragon788
-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
Guy Kastenbaum
źródło