Apakah ada sesuatu yang mirip dengan echo -n di heredoc (EOF)?

12

Saya sedang menulis di skrip pabrik skrip besar yang menghasilkan banyak skrip pemeliharaan untuk server saya.

Sampai sekarang saya menulis beberapa baris yang perlu ditulis dalam satu baris dengan echo -nemisalnya

echo -n "if (( " | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

# Generate exitCode check for each Server
IFS=" "
COUNT=0
while read -r name ipAddr
do
    if(($COUNT != 0))
    then
        echo -n " || " | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
    fi

    echo -n "(\$"$name"_E != 0 && \$"$name"_E != 1)" | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

    COUNT=$((COUNT+1))

    JOBSDONE=$((JOBSDONE+1))
    updateProgress $JOBCOUNT $JOBSDONE
done <<< "$(sudo cat /root/.virtualMachines)"

echo " ))" | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

ini menghasilkan saya (jika ada misalnya 2 server di file konfigurasi saya) seperti kode

if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))

Semua blok kode lain yang tidak memerlukan penulisan inline kode yang saya hasilkan dengan heredocs ini karena saya menemukan cara yang lebih baik untuk menulis dan memelihara. Misal setelah kode atas saya memiliki sisanya dihasilkan seperti

cat << EOF | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
then
    # Print out ExitCode legend
    echo " ExitCode 42 - upgrade failed"
    echo " ExitCode 43 - Upgrade failed"
    echo " ExitCode 44 - Dist-Upgrade failed"
    echo " ExitCode 45 - Autoremove failed"
    echo ""
    echo ""
fi
EOF

Jadi blok kode akhir terlihat seperti

if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))
then
    # Print out ExitCode legend
    echo " ExitCode 42 - upgrade failed"
    echo " ExitCode 43 - Upgrade failed"
    echo " ExitCode 44 - Dist-Upgrade failed"
    echo " ExitCode 45 - Autoremove failed"
    echo ""
    echo ""
fi

Pertanyaan saya:
Adakah cara heredoc berperilaku serupa echo -netanpa simbol end-of-line?

derHugo
sumber
1
Jika Anda membutuhkan sudoskrip yang salah: Jalankan keseluruhan skrip sebagai root!
hidangan penutup
1
Tidak karena itu juga melakukan hal-hal lain yang tidak ingin saya jalankan sebagai root
derHugo
Penggunaan sudo -u USERNAMEpada yang bukan, lihat Bagaimana cara menjalankan perintah 'sudo' dalam naskah? .
hidangan penutup
apa masalah melakukan itu seperti yang saya lakukan?
derHugo
4
Yah tentu saja ada masalah: Sebuah skrip sudotanpa nama pengguna meminta kata sandi, kecuali jika Anda mengetikkannya ada penundaan. Lebih buruk lagi jika Anda tidak menjalankan skrip di terminal, maka Anda bahkan tidak bisa melihat permintaan kata sandi dan itu tidak akan berfungsi. The sudo -uPendekatan tidak memiliki masalah ini.
hidangan penutup

Jawaban:

9

Tidak, heredoc selalu berakhir pada baris baru. Tetapi Anda masih dapat menghapus baris baru terakhir, misalnya dengan Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -pmembaca input baris demi baris, menjalankan kode yang ditentukan -e, dan mencetak baris (mungkin dimodifikasi)
  • chomp menghapus baris akhir terakhir jika ada, eof mengembalikan true pada akhir file.
choroba
sumber
1
@Yaron jika ditemui akhir file (dalam hal ini pipa tertutup) itu akan chomp baris baru char dari baris terakhir (yang adalah apa heredoc dan herestring tambahkan secara otomatis)
Sergiy Kolodyazhnyy
7

Apakah ada cara memiliki heredoc berperilaku mirip dengan echo -ne tanpa akhir-of-line-simbol?

Jawaban singkatnya adalah bahwa heredoc dan herestrings hanya dibangun dengan cara itu, jadi tidak ada - sini-doc dan herestring akan selalu menambahkan baris baru dan tidak ada pilihan atau cara asli untuk menonaktifkannya (mungkin akan ada rilis bash di masa mendatang?).

Namun, salah satu cara untuk mendekatinya melalui cara bash-only adalah membaca apa yang diberikan heredoc melalui while IFS= read -rmetode standar , tetapi menambahkan penundaan dan mencetak baris terakhir melalui printftanpa baris baru. Inilah yang bisa dilakukan seseorang dengan bash-only:

#!/usr/bin/env bash

while true
do
    IFS= read -r line || { printf "%s" "$delayed";break;}
    if [ -n "$delayed" ]; 
    then
        printf "%s\n" "$delayed"
    fi
    delayed=$line
done <<EOF
one
two
EOF

Apa yang Anda lihat di sini adalah loop sambil biasa dengan IFS= read -r linependekatan, namun setiap baris disimpan ke dalam variabel dan dengan demikian tertunda (jadi kami melewatkan pencetakan baris pertama, menyimpannya, dan mulai mencetak hanya ketika kami membaca baris kedua). Dengan begitu kita dapat menangkap baris terakhir, dan ketika pada iterasi berikutnya readmengembalikan status keluar 1, saat itulah kita mencetak baris terakhir tanpa baris baru via printf. Verbose? Iya. Tapi berhasil:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Perhatikan $karakter cepat didorong ke depan, karena tidak ada baris baru. Sebagai bonus, solusi ini adalah portable dan POSIX-ish, sehingga harus bekerja di kshdan dashjuga.

$ dash ./strip_heredoc_newline.sh                                 
one
two$
Sergiy Kolodyazhnyy
sumber
6

Saya sarankan Anda mencoba pendekatan yang berbeda. Alih-alih menyatukan skrip yang dibuat dari beberapa pernyataan gema dan heredocs. Saya sarankan Anda mencoba menggunakan heredoc tunggal.

Anda dapat menggunakan ekspansi variabel dan mengganti perintah di dalam heredoc. Seperti dalam contoh ini:

#!/bin/bash
cat <<EOF
$USER $(uname)
EOF

Saya menemukan bahwa ini sering mengarah pada hasil akhir yang jauh lebih mudah dibaca daripada memproduksi sepotong demi sepotong.

Sepertinya Anda lupa #!baris di awal skrip Anda. The #!line adalah wajib di setiap script diformat dengan benar. Mencoba menjalankan skrip tanpa #!baris hanya akan berfungsi jika Anda memanggilnya dari shell yang bekerja di sekitar skrip yang diformat dengan buruk, dan bahkan dalam kasus itu mungkin akan ditafsirkan oleh shell yang berbeda dari yang Anda inginkan.

kasperd
sumber
havent lupa #!tapi blok kode saya hanya satu cuplikan dari skrip 2000 baris;) Saya tidak melihat sekarang bagaimana saran Anda memecahkan masalah saya menghasilkan satu baris kode dalam loop sementara ...
derHugo
Subtitle @derHugo Command untuk bagian dari baris di mana Anda memerlukan loop adalah salah satu cara untuk melakukannya. Cara lain adalah dengan membangun bagian itu dalam variabel sebelum heredoc. Manakah dari keduanya yang paling mudah dibaca tergantung pada panjang kode yang diperlukan dalam loop serta berapa banyak baris heredoc yang Anda miliki sebelum baris yang dimaksud.
kasperd
Subtitle @derHugo Command yang menjalankan fungsi shell yang didefinisikan sebelumnya dalam skrip adalah pendekatan ketiga yang mungkin lebih mudah dibaca jika Anda membutuhkan jumlah kode yang signifikan untuk menghasilkan satu baris.
kasperd
@derHugo: Perhatikan bahwa: (1) Anda dapat memiliki loop yang menempatkan semua perintah yang diperlukan ke dalam variabel; (2) Anda dapat menggunakan $( ...command...)di dalam heredoc, untuk memasukkan output dari perintah-perintah inline pada titik itu di heredoc; (3) Bash memiliki variabel array, yang Anda dapat memperluas inline, dan menggunakan substitusi untuk menambahkan hal-hal di awal / akhir jika perlu. Bersama-sama ini membuat saran kasperd jauh lebih kuat (dan skrip Anda berpotensi jauh lebih mudah dibaca).
psmears
Saya menggunakan heredocs karena jenis perilaku templatnya (wysiwyg). Memproduksi seluruh templat di tempat lain dan daripada menyerahkannya ke heredoc menurut saya tidak benar-benar membuat hal-hal lebih mudah dibaca / dipelihara.
derHugo
2

Heredocs selalu berakhir dengan karakter baris baru tetapi Anda dapat menghapusnya. Cara termudah yang saya tahu adalah dengan headprogram ini:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF
David Foerster
sumber
Keren, saya tidak tahu itu -cjuga mengambil nilai negatif! Seperti itu bahkan lebih dari pearlsolusinya
derHugo
1

Anda dapat menghapus baris baru dengan cara apa pun yang Anda suka, mem-piping ke trmungkin paling sederhana. Ini tentu saja akan menghapus semua baris baru.

$ tr -d '\n' <<EOF 
if ((
EOF

Tetapi, jika pernyataan yang Anda bangun tidak mengharuskan itu, ekspresi aritmatika (( .. ))harus berfungsi dengan baik meskipun berisi baris baru.

Jadi, Anda bisa melakukannya

cat <<EOF 
if ((
EOF
for name in x y; do 
    cat << EOF
    ( \$${name}_E != 0 && \$${name}_E != 1 ) ||
EOF
done
cat <<EOF
    0 )) ; then 
    echo do something
fi
EOF

memproduksi

if ((
    ( $x_E != 0 && $x_E != 1 ) ||
    ( $y_E != 0 && $y_E != 1 ) ||
    0 )) ; then 
    echo do something
fi

Membangunnya dalam satu lingkaran masih membuat jelek. Ada di || 0sana tentu saja sehingga kita tidak perlu kasus khusus baris pertama atau terakhir.


Juga, Anda memiliki itu | sudo tee ...di setiap output. Saya pikir kita bisa menyingkirkan itu dengan membuka pengalihan hanya sekali (dengan substitusi proses), dan menggunakannya untuk pencetakan nanti:

exec 3> >(sudo tee outputfile &>/dev/null)
echo output to the pipe like this >&3
echo etc. >&3
exec 3>&-         # close it in the end

Dan terus terang, saya masih berpikir bahwa membangun nama variabel dari variabel dalam skrip luar (seperti itu \$${name}_E) agak jelek, dan mungkin harus diganti dengan array asosiatif .

ilkkachu
sumber