Bagaimana cara menetapkan nilai string ke variabel melalui beberapa baris saat indentasi?

54

Masalah:

  1. Saya perlu menetapkan nilai variabel yang panjang.
  2. Semua baris skrip saya harus di bawah sejumlah kolom.

Jadi, saya mencoba menetapkannya menggunakan lebih dari satu baris.

Mudah dilakukan tanpa indentasi:

VAR="This displays without \
any issues."
echo "${VAR}"

Hasil:

This displays without any issues.

Namun dengan indentasi:

    VAR="This displays with \
    extra spaces."
    echo "${VAR}"

Hasil:

This displays with      extra spaces.

Bagaimana saya bisa menetapkannya secara elegan tanpa spasi ini?

Sman865
sumber

Jawaban:

31

Di sini masalahnya adalah Anda mengelilingi variabel dengan tanda kutip ganda (""). Hapus itu dan semuanya akan berfungsi dengan baik.

    VAR="This displays with \
    extra spaces."
    echo ${VAR}

Keluaran

 This displays with extra spaces.

Di sini masalahnya adalah bahwa mengutip dua variabel mempertahankan semua karakter spasi putih. Ini dapat digunakan jika Anda membutuhkannya secara eksplisit.

Sebagai contoh,

$ echo "Hello     World    ........ ...            ...."

akan dicetak

Hello     World    ........ ...            ....

Dan pada menghapus kutipan, ini berbeda

$ echo Hello     World    ........ ...            ....
Hello World ........ ... ....

Di sini Bash menghapus spasi tambahan dalam teks karena dalam kasus pertama seluruh teks diambil sebagai argumen "tunggal" dan dengan demikian mempertahankan ruang ekstra. Tetapi dalam kasus kedua echoperintah menerima teks sebagai 5 argumen.

Mengutip variabel juga akan membantu saat menyampaikan argumen ke perintah.

Pada perintah di bawah ini, echohanya mendapat satu argumen sebagai"Hello World"

$ variable="Hello World"
$ echo "$variable"

Tetapi dalam kasus skenario di bawah ini echomendapat dua argumen sebagai HellodanWorld

$ variable="Hello World"
$ echo $variable
Kannan Mohan
sumber
Sebagian besar jawaban bekerja dengan baik, tetapi ini adalah yang paling sederhana. Terima kasih!
Sman865
12
@ Sman865 - tolong percayalah ketika saya memberi tahu Anda bahwa ini sebenarnya hampir pasti jawaban paling rumit yang ditawarkan di sini, dan itu juga salah dalam banyak hal - terutama dalam deklarasi pembukaannya. Masalah apa pun seputar penetapan nilai tidak dapat dengan cara apa pun terkait dengan ekspansi selanjutnya - itu hanya mundur. Maafkan aku Kannan, tetapi jawaban ini salah dan juga salah. Perluasan field-splitting $IFSadalah alat yang kuat dan universal - tetapi kode yang ditulis dengan cara ini tidak pernah dapat menghasilkan hasil yang dapat diandalkan dalam bentuk apa pun.
mikeserv
2
Gagal menggunakan tanda kutip ketika memperluas variabel menyebabkan masalah cepat atau lambat, dimulai dengan ekspansi nama file (salah satu dari * ? []).
mr.spuratic
Saya setuju bahwa variabel perlu dimasukkan di dalam tanda kutip ganda untuk mencegahnya dari ekspansi bash. Tetapi untuk kasus khusus kita dapat menghindarinya untuk mencegah komplikasi kode.
Kannan Mohan
1
Saya setuju dengan @mikeserv, ini adalah saran yang sangat buruk jika diambil pada nilai nominal. Ini akan rusak jika digunakan tanpa konsekuensi. Misalnya, jika variabel dilewatkan sebagai argumen ke perintah, Anda tidak ingin setiap kata dipecah sebagai argumen yang terpisah.
haridsv
28

Solusi yang diberikan oleh esuoxu dan Mickaël Bucas adalah cara umum dan lebih portabel untuk melakukan ini.

Berikut adalah beberapa bashsolusi (beberapa di antaranya juga dapat bekerja di shell lain, seperti zsh). Pertama dengan +=append operator (yang bekerja dengan cara yang sedikit berbeda untuk masing-masing variabel integer, variabel reguler, dan array).

text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
text+="tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
text+="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." 

Jika Anda ingin baris baru (atau spasi / lolos lainnya) dalam teks, gunakan $''kutipan:

text=$'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n'
text+=$'...'

Selanjutnya, gunakan printf -vuntuk menetapkan nilai yang diformat ke variabel

printf -v text "%s" "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed " \
                    "do eiusmod empor incididunt ut labore et dolore magna aliqua. "\
                    "Ut enim ad minim veniam ..."

Kuncinya di sini adalah bahwa ada lebih banyak argumen daripada penentu format, jadi tidak seperti kebanyakan printffungsi, yang bash menggunakan kembali format string sampai habis. Anda dapat menempatkan \ndalam string format, atau menggunakan $ '', (atau keduanya) untuk menangani spasi putih.

Selanjutnya, menggunakan array:

text=("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
      "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." )

Anda juga dapat menggunakan +=untuk membangun teks baris demi baris (perhatikan ()):

text+=("post script")

Namun di sini, Anda harus ingat untuk "meratakan" array jika Anda ingin seluruh konten teks sekaligus

echo "$text"      # only outputs index [0], the first line
echo "${text[*]}" # output complete text (joined by first character of IFS)

(array integer yang diindeks secara implisit diurutkan, tidak seperti array asosiatif) Ini memberi Anda sedikit lebih banyak fleksibilitas karena Anda dapat memanipulasi garis dan bahkan mengiris dan memotong jika diperlukan.

Akhirnya, menggunakan readatau readarraydan "dokumen-sini":

read -r -d '' text <<-"EOT"
        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ...
EOT

readarray -t textarray <<-"EOT"
        Lorem [...]
EOT  

Bentuk di sini dokumen <<-berarti semua tab keras utama dihapus dari input, jadi Anda harus menggunakan tab untuk membuat indentasi teks Anda. Mengutip sekitar "EOT"mencegah fitur ekspansi shell, sehingga input digunakan kata demi kata. Dengan readitu menggunakan input dibatasi NUL byte, sehingga akan membaca teks yang dibatasi baris baru dalam sekali jalan. Dengan readarray(alias mapfile, tersedia sejak bash-4.0) ia membaca ke dalam sebuah array, dan -tmenghapus baris baru di setiap baris.

mr.spuratic
sumber
1
The readpilihan dengan here documentsangat bagus! Berguna untuk menanamkan skrip python dan mengeksekusi dengan python -c. Saya pernah melakukannya script=$(cat <here document>)sebelumnya, tetapi read -r -d '' script <here document>jauh lebih baik.
haridsv
9

Ada sintaks heredoc khusus yang menghapus tab di awal semua baris: "<< -" (perhatikan tanda hubung ditambahkan)

http://tldp.org/LDP/abs/html/here-docs.html

Contoh 19-4. Pesan multi-line, dengan tab ditekan

Anda bisa menggunakannya seperti ini:

v="$(cat <<-EOF
    A
        B
    C
EOF
)"
echo "$v"

Hasil:

A
B
C

Ini hanya berfungsi dengan tab, bukan spasi.

Mickaël Bucas
sumber
Di sini di ubuntu, kebalikannya benar - spasi berfungsi, bukan tab. \tbekerja sangat baik jika Anda membutuhkan tab. Cukup gunakan echo -e "$v"daripada echo "$v"untuk mengaktifkan karakter backslash
will-ob
5

Biarkan cangkang memakan biji-bijian yang tidak diinginkan dan spasi berikut:

$ cat weird.sh 
#!/bin/sh

        var1="A weird(?) $(
             )multi line $(
             )text idea. $(
             )PID=$$"

        var2='You can '$(
            )'avoid expansion '$(
            )'too: PID=$$'

        var3='Or mix it: '$(
            )'To insert the PID use $$. '$(
            )"It expands to e.g. $$."

        echo "$var1"
        echo "$var2"
        echo "$var3"
$ sh weird.sh 
A weird(?) multi line text idea. PID=13960
You can avoid expansion too: PID=$$
Or mix it: To insert the PID use $$. It expands to e.g. 13960.

Jadi itu mungkin ... tapi yakin itu masalah selera untuk suka atau tidak suka solusi ini ...

yeti
sumber
3
setiap satu dari ini adalah garpu.
mikeserv
5

Mungkin Anda bisa mencoba ini.

          echo "Test" \
               "Test2" \
               "Test3"
esuoxu
sumber
5

Begini saran saya, Anda harus melakukannya, dan saya akan menjelaskan alasannya, tetapi pertama-tama saya ingin berbicara tentang sesuatu yang lain ...

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"

Banyak solusi disodorkan lain di sini tampaknya menyarankan bahwa Anda entah bagaimana dapat mempengaruhi konten variabel shell dengan mengubah metode Anda memperluasnya. Saya dapat meyakinkan Anda bahwa ini bukan masalahnya.

    string="some stuff here \
            some more stuff here."
    echo $string ${#string} 
    echo "$string" "${#string}"

KELUARAN

some stuff here some more stuff here. 53
some stuff here                 some more stuff here. 53

Apa yang Anda lihat di atas adalah pertama ekspansi field-split, kemudian laporan tentang byte-count untuk variabel sumber ekspansi, kemudian ekspansi yang dibatasi-kuotasi, dan byte-count yang sama. Sementara output mungkin berbeda, isi variabel shell $stringtidak pernah berubah sama sekali kecuali pada penugasan.

Terlebih lagi, jika Anda tidak mengerti mengapa ini terjadi, Anda pasti akan menghadapi beberapa kejutan yang sangat buruk lebih cepat daripada nanti. Mari kita coba lagi, tetapi dalam kondisi yang sedikit berbeda.

    IFS=sf
    echo $string ${#string} 
    echo "$string" "${#string}"

Sama $string- lingkungan berbeda.

KELUARAN

 ome  tu   here                  ome more  tu   here. 53
some stuff here                 some more stuff here. 53

Pemecahan bidang terjadi berdasarkan pembatas bidang yang didefinisikan dalam $IFS. Ada dua jenis pembatas - $IFSspasi putih dan yang $IFSlainnya. Secara default $IFSditugaskan baris ruang nilai tab - yang merupakan tiga kemungkinan $IFSnilai ruang kosong. Namun, ini mudah diubah, seperti yang Anda lihat di atas, dan dapat memiliki efek drastis pada ekspansi bidang-split.

$IFSspasi putih akan berurutan dengan urutan ke bidang tunggal - dan inilah sebabnya echoekspansi yang berisi urutan ruang mana pun ketika $IFSberisi ruang akan mengevaluasi hanya ruang tunggal - karena echomenyatukan argumennya pada ruang. Tetapi nilai-nilai non-spasi putih tidak akan hilang dengan cara yang sama, dan setiap pembatas yang terjadi selalu mendapatkan bidang untuk dirinya sendiri - seperti yang dapat dilihat pada perluasan barang di atas.

Ini bukan yang terburuk. Pertimbangkan yang lain ini $string.

IFS=$space$tab$newline
cd emptydir
    string=" * * * \
             * * * "
    echo $string ${#string}
    echo "$string" "${#string}"    

KELUARAN

* * * * * * 30
 * * *                  * * *  30

Terlihat oke, bukan? Baiklah, mari kita ubah lingkungan lagi.

    touch file1 file2 file3 file4 file5
    echo $string ${#string}
    echo "$string" "${#string}"    

KELUARAN

file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 30
 * * *                  * * *  30

Wow.

Secara default shell akan memperluas nama file jika itu bisa cocok dengan mereka. Ini terjadi setelah ekspansi parameter dan pemisahan bidang dalam urutan parse-nya dan sehingga string yang tidak dikutip dikutip rentan dengan cara ini. Anda dapat menonaktifkan perilaku ini set -fjika Anda suka, tetapi setiap shell yang kompatibel dengan POSIX akan selalu menggumpal secara default.

Ini adalah jenis hal yang Anda hadapi ketika Anda memberikan tanda kutip pada ekspansi yang sesuai dengan preferensi indentasi Anda. Dan meskipun demikian, dalam setiap kasus, terlepas dari perilaku ekspansi, nilai aktual untuk $stringselalu tetap apa pun itu ketika Anda terakhir kali menetapkannya. Jadi mari kita kembali ke hal pertama.

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"
echo "$var" "${#var}"

KELUARAN

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like. 70

Saya percaya ini adalah cara yang jauh lebih waras untuk mengadaptasi sintaksis shell dengan preferensi indentasi Anda. Apa yang saya lakukan di atas adalah menugaskan setiap string individu ke parameter posisi - yang masing-masing dapat direferensikan dengan angka seperti $1atau ${33}- dan kemudian menetapkan nilai gabungan mereka untuk $varmenggunakan parameter shell khusus $*.

Pendekatan ini tidak kebal terhadap $IFShal itu. Namun, saya menganggap hubungannya dengan $IFSmanfaat tambahan dalam hal ini. Mempertimbangkan:

IFS=\ ;space_split="$*"
IFS=/; slash_split="$*";IFS='
';new_line_split="$*"

echo "$space_split"
echo "$slash_split"
echo "$new_line_split"

KELUARAN

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like.
Arg 1: Line 1./Arg 2: Line 2./and so on for/as long as you might like.
Arg 1: Line 1.
Arg 2: Line 2.
and so on for
as long as you might like.

Seperti yang Anda lihat, $*gabungkan masing-masing arg "$@"pada byte pertama di $IFS. Jadi menyimpan nilainya saat $IFSditugaskan secara berbeda akan mendapat pembatas bidang yang berbeda untuk setiap nilai yang disimpan. Apa yang Anda lihat di atas adalah nilai literal untuk setiap variabel. Jika Anda tidak menginginkan pembatas sama sekali, Anda akan melakukannya:

IFS=;delimitless="$*"
echo "$delimitless" "${#delimitless}"

KELUARAN

Arg 1: Line 1.Arg 2: Line 2.and so on foras long as you might like. 67
mikeserv
sumber
2

Anda mungkin ingin mencoba:

echo $VAR | tr -s " "

atau

myArr=($VAL)
VAL=${myArr[@]}
echo "$VAL"

dan juga Anda dapat memeriksa ini keluar.

Vivian Maya
sumber
2

Gunakan Ekspansi Substitusi Bash

Jika Anda menggunakan Bash, Anda dapat menggunakan ekspansi substitusi . Sebagai contoh:

$ echo "${VAR//  /}"
This displays with extra spaces.
CodeGnome
sumber
1

Ini adalah varian untuk menyetel variabel path-like:

set -- "${MYDIR}/usr/local/lib" \
      :"${MYDIR}/usr/lib" \
      :"${MYDIR}/lib" \
       "${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
    export LD_LIBRARY_PATH="$*"
    LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)

Menggunakan setoverwrites $@, yang dapat disimpan dan digunakan kemudian sebagai berikut:

ARGV=("$@")
exec foo "${ARGV[@]}"

The LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)eliminasi baris spasi sebelum titik dua serta mungkin spasi trailing. Jika hanya menghilangkan spasi tambahan, gunakan LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }saja.

Seluruh pendekatan ini merupakan varian dari jawaban mikeserv yang luar biasa.

Randen
sumber
0

Jangan takut dengan karakter spasi putih. Hapus saja sebelum Anda mencetak teks multiline Anda.

$ cat ./t.sh
#!/bin/bash

NEED_HELP=1
if [[ $NEED_HELP -eq 1 ]]; then

    lucky_number=$((1 + RANDOM % 10 + 10))

    read -r -d '' text <<'    EOF'
    NAME says hello

    Look at this helpful text:

                                                 * -**
                                 Eyjafjallajokull        Eyjafjallajokull
                            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

    Don't go away, please rate your experience... It'll just take two minutes.

    EOF
    text=$(echo "$text" | sed -r 's!^\s{4}!!')
    text=$(echo "$text" | sed -r "s!\bNAME\b!$0!") # Bash: text=${text//NAME/$0}
    text=$(echo "$text" | sed -r "s!\btwo\b!$lucky_number!")

    echo "$text"

fi

Keluaran:

$ ./t.sh
./t.sh says hello

Look at this helpful text:

                                             * -**
                             Eyjafjallajokull        Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

Don't go away, please rate your experience... It'll just take 16 minutes.

Tidak perlu menggunakan <<-heredoc dan hancurkan indentasi 4-ruang Anda dengan karakter tab.

c0xc
sumber