Bagaimana cara menetapkan nilai heredoc ke variabel di Bash?

374

Saya memiliki string multi-baris ini (termasuk kutipan):

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

Bagaimana saya menetapkannya ke variabel menggunakan heredoc di Bash?

Saya perlu mempertahankan baris baru.

Saya tidak ingin lepas dari karakter dalam string, itu akan mengganggu ...

Neil
sumber
@JohnM - Saya baru saja mencoba tugas heredoc dengan tanda kutip tunggal 'EOF', dengan linebreak lolos dengan ` in the content: if the second line has perintah cd`, saya kembali: " .sh: baris X: cd: perintah tidak ditemukan "; tetapi jika saya double-quote "EOF"; kemudian variabel bash ${A}tidak bisa dipertahankan sebagai string (mereka bisa diperluas); tapi kemudian, line-break yang diawetkan - dan, saya tidak memiliki masalah menjalankan perintah dengan cddi baris kedua ( dan kedua 'EOF' dan "EOF" tampaknya bermain dengan baik juga dengan eval, untuk menjalankan sekumpulan perintah yang tersimpan di variabel string ). Bersulang!
sdaau
1
... dan untuk menambahkan komentar saya sebelumnya: komentar bash "#" dalam "EOF"variabel qouted ganda , jika dipanggil via eval $VAR, akan menyebabkan semua skrip lainnya dikomentari, karena di sini $ VAR akan dilihat sebagai satu baris ; untuk dapat menggunakan #komentar bash dalam skrip multiline, kutipan ganda juga variabel dalam eval call: eval "$ VAR" `.
sdaau
@ Sdaau: Saya punya masalah dengan evalith metode ini, tetapi tidak melacaknya karena itu adalah bagian dari beberapa paket yang evalbeberapa variabel didefinisikan dalam file konfigurasi itu. Pesan kesalahan adalah: /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. Saya baru saja beralih ke solusi lain.
Golar Ramblar

Jawaban:

515

Anda dapat menghindari penggunaan yang tidak berguna catdan menangani penawaran yang tidak cocok lebih baik dengan ini:

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

Jika Anda tidak mengutip variabel ketika Anda menggemakannya, baris baru hilang. Mengutip itu melindungi mereka:

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

Jika Anda ingin menggunakan indentasi untuk keterbacaan dalam kode sumber, gunakan tanda hubung setelah less-thans. Lekukan harus dilakukan hanya menggunakan tab (tanpa spasi).

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

Jika, sebagai gantinya, Anda ingin mempertahankan tab di isi dari variabel yang dihasilkan, Anda perlu menghapus tab dari IFS. Penanda terminal untuk doc di sini ( EOF) tidak boleh diindentasi.

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

Tab dapat dimasukkan pada baris perintah dengan menekan Ctrl- V Tab. Jika Anda menggunakan editor, tergantung yang mana, itu mungkin juga berfungsi atau Anda mungkin harus mematikan fitur yang secara otomatis mengubah tab menjadi spasi.

Dijeda sampai pemberitahuan lebih lanjut.
sumber
117
Saya pikir itu layak disebutkan bahwa jika Anda memiliki set -o errexit(alias set -e) dalam skrip Anda dan Anda menggunakan ini maka itu akan menghentikan skrip Anda karena readmengembalikan kode pengembalian tidak nol ketika mencapai EOF.
Mark Byers
14
@MarkByers: Itulah salah satu alasan saya tidak pernah menggunakan set -edan selalu merekomendasikan untuk tidak menggunakannya. Lebih baik menggunakan penanganan kesalahan yang tepat sebagai gantinya. trapadalah temanmu Teman-teman lain: elsedan di ||antara yang lainnya.
Dijeda sampai pemberitahuan lebih lanjut.
7
Apakah penghindaran catbenar-benar layak dalam kasus seperti itu? Menugaskan heredoc ke variabel dengan catadalah idiom yang terkenal. Entah bagaimana menggunakan readhal-hal mengaburkan untuk sedikit manfaat imho.
Gregory Pakosz
6
@ alidtko Itu karena Anda tidak memiliki spasi di antara ddan string kosong; bashruntuh -rd''untuk hanya -rdsebelum readmelihat argumennya, jadi VARdiperlakukan sebagai argumen untuk -d.
chepner
6
Dalam format ini, readakan kembali dengan kode keluar yang tidak nol. Ini membuat metode ini kurang ideal dalam skrip dengan pemeriksaan kesalahan diaktifkan (mis set -e.).
Swiss
246

Gunakan $ () untuk menetapkan output catke variabel Anda seperti ini:

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

Pastikan untuk membatasi mulai END_HEREDOC dengan tanda kutip tunggal.

Perhatikan bahwa mengakhiri pembatas heredoc END_HEREDOCharus sendirian di baris (maka tanda kurung berakhir di baris berikutnya).

Terima kasih @ephemientatas jawabannya.

Neil
sumber
1
Sebenarnya, yang ini memungkinkan untuk mengutip dalam beberapa keadaan. Saya berhasil melakukan ini di Perl mudah ...
Neil
32
+1. Ini adalah solusi yang paling mudah dibaca, setidaknya untuk mata saya. Itu meninggalkan nama variabel di paling kiri halaman, bukannya menanamkannya dalam perintah baca.
Clayton Stanley
12
PSA: ingat bahwa variabel harus dikutip untuk mempertahankan baris baru. echo "$VAR"bukannya echo $VAR.
sevko
7
Ini bagus ashdan OpenWRT readtidak mendukung -d.
David Ehrmann
3
Untuk alasan yang tidak dapat saya pahami, ini gagal dengan kesalahan "EOF tak terduga" jika Anda memiliki backtick yang tidak berpasangan di heredoc.
Radon Rosborough
79

ini adalah variasi dari metode Dennis, terlihat lebih elegan dalam skrip.

definisi fungsi:

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

pemakaian:

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

echo "$VAR"

Nikmati

ps membuat versi 'read loop' untuk shell yang tidak mendukung read -d. harus bekerja dengan set -eudan backticks tidak berpasangan , tetapi tidak diuji dengan sangat baik:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }
ttt
sumber
1
Ini tampaknya hanya berfungsi secara dangkal. Fungsi define akan mengembalikan status 1, dan saya tidak yakin apa yang perlu diperbaiki.
fny
1
Ini juga lebih unggul daripada jawaban yang diterima, karena dapat dimodifikasi untuk mendukung POSIX sh selain bash ( readloop dalam fungsi, untuk menghindari -d ''bashism yang diperlukan untuk mempertahankan baris baru).
ELLIOTTCABLE
Tidak seperti opsi cat-in-a-subshell, ini bekerja dengan backticks tidak berpasangan di heredoc. Terima kasih!
Radon Rosborough
Solusi ini berfungsi dengan set -eset, sedangkan jawaban yang dipilih tidak. Tampaknya karenahttp://unix.stackexchange.com/a/265151/20650
mencari
2
@fny ps status pengembalian telah lama diperbaiki
ttt
36
VAR=<<END
abc
END

tidak berfungsi karena Anda mengarahkan stdin ke sesuatu yang tidak mempedulikannya, yaitu tugas

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

berfungsi, tetapi ada back-tic di sana yang dapat menghentikan Anda dari menggunakan ini. Juga, Anda harus benar-benar menghindari menggunakan backticks, lebih baik menggunakan notasi substitusi perintah $(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A
l0st3d
sumber
Saya telah memperbarui pertanyaan saya untuk memasukkan $ (dapat dieksekusi). Juga, bagaimana Anda mempertahankan baris baru?
Neil
2
@ l0st3d: Sangat dekat ... Gunakan $(cat <<'END'saja. @ Neil: Baris baru terakhir tidak akan menjadi bagian dari variabel, tetapi sisanya akan dipertahankan.
ephemient
1
Sepertinya tidak ada baris baru yang dipertahankan. Menjalankan contoh di atas saya melihat: "sdfsdf sdfsdf sdfsfds" ... ah! Tapi menulis echo "$A"(yaitu menempatkan $ A dalam tanda kutip ganda) dan Anda memang melihat baris baru!
Darren Cook
1
@ Darren: aha! Saya telah memperhatikan masalah baris baru, dan menggunakan tanda kutip di sekitar variabel output tidak memperbaiki masalah. Terima kasih!
javadba
1
Menariknya, karena permainan kata-kata dari contoh pertama, dalam keadaan darurat Anda dapat menggunakannya untuk blok komentar darurat seperti ini: REM=<< 'REM' ... comment block goes here ... REM. Atau lebih kompak : << 'REM' ...,. Di mana "REM" dapat berupa "CATATAN" atau "SCRATCHPAD", dll.
Beejor
34

Masih belum ada solusi yang melindungi baris baru.

Ini tidak benar - Anda mungkin hanya disesatkan oleh perilaku gema:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

tepuk
sumber
5
Sungguh ini adalah perilaku bagaimana mengutip variabel bekerja. Tanpa tanda kutip, itu akan menyisipkan mereka sebagai parameter yang berbeda, spasi dibatasi, sementara dengan tanda kutip seluruh variabel konten akan diperlakukan sebagai satu argumen
Czipperz
11

Bercabang dari jawaban Neil , Anda sering tidak membutuhkan var sama sekali, Anda dapat menggunakan fungsi dengan cara yang sama seperti variabel dan itu jauh lebih mudah dibaca daripada solusi inline atau read-berbasis.

$ 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
sumber
9

Array adalah variabel, jadi dalam hal ini mapfile akan berfungsi

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

Maka Anda bisa mencetak seperti ini

printf %s "${y[@]}"
Steven Penny
sumber
3

menetapkan nilai heredoc ke variabel

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

digunakan sebagai argumen dari suatu perintah

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"
pria perunggu
sumber
Ketika saya mencoba metode pertama, sepertinya tidak ada garis terminator di antara garis. Harus ada semacam konfigurasi di mesin linux saya?
Kemin Zhou
Ini mungkin berarti ketika Anda menggemakan variabel Anda, Anda tidak memberikan tanda kutip di sekitarnya ... Cobalah seperti itu:echo "$VAR"
Brad Parks
1

Saya menemukan diri saya harus membaca string dengan NULL di dalamnya, jadi di sini adalah solusi yang akan membaca apa pun yang Anda lemparkan padanya. Meskipun jika Anda benar-benar berurusan dengan NULL, Anda harus menghadapinya pada level hex.

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

Bukti:

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

Contoh HEREDOC (dengan ^ 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
sumber
0

Berkat jawaban dimo414 , ini menunjukkan bagaimana solusi hebatnya bekerja, dan menunjukkan bahwa Anda dapat dengan mudah mengutip dan variabel dalam teks:

contoh 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
Taman Brad
sumber
Akan menarik untuk melihat kutipan yang tak tertandingi dalam teks untuk melihat bagaimana itu menanganinya. Mungkin `Jangan panik, ada file" $ COUNT "` sehingga tanda kutip / kutipan tunggal dapat membuat hal-hal menarik.
dragon788
-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
Guy Kastenbaum
sumber