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


@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.


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ź.


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"; }

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

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


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"''

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[@]}"

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
)"

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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.