Memaksa bash untuk memperluas variabel dalam string yang dimuat dari file

89

Saya mencoba mencari cara untuk membuat bash (force?) Memperluas variabel dalam string (yang dimuat dari file).

Saya memiliki file bernama "sesuatu.txt" dengan konten:

hello $FOO world

Saya kemudian lari

export FOO=42
echo $(cat something.txt)

ini mengembalikan:

   hello $FOO world

Itu tidak memperluas $ FOO meskipun variabel telah ditetapkan. Saya tidak dapat mengevaluasi atau mencari file - karena akan mencoba dan mengeksekusinya (tidak dapat dieksekusi apa adanya - saya hanya ingin string dengan variabel diinterpolasi).

Ada ide?

Michael Neale
sumber
3
Waspadai implikasi keamananeval .
Dennis Williamson
Apakah maksud Anda komentar tersebut untuk jawaban di bawah ini?
Michael Neale
Saya bermaksud memberi komentar untuk Anda. Lebih dari satu jawaban mengusulkan penggunaan evaldan penting untuk menyadari implikasinya.
Dennis Williamson
@DennisWilliamson gotcha - Saya agak terlambat membalas tapi tip bagus. Dan tentu saja di tahun-tahun berikutnya kami mengalami hal-hal seperti "kejutan besar" sehingga komentar Anda bertahan dalam ujian waktu! tips topi
Michael Neale
Apakah ini menjawab pertanyaan Anda? Bash memperluas variabel dalam variabel
LoganMzz

Jawaban:

115

Saya tersandung pada apa yang menurut saya adalah jawaban ATAS pertanyaan ini: envsubstperintah.

envsubst < something.txt

Jika itu belum tersedia di distro Anda, itu ada di

Paket GNU gettext.

@Rockallite - Saya menulis skrip pembungkus kecil untuk mengatasi masalah '\ $'.

(BTW, ada "fitur" dari envsubst, dijelaskan di https://unix.stackexchange.com/a/294400/7088 untuk memperluas hanya beberapa variabel dalam masukan, tetapi saya setuju bahwa keluar dari pengecualian jauh lebih banyak mudah.)

Ini skrip saya:

#! /bin/bash
      ## -*-Shell-Script-*-
CmdName=${0##*/}
Usage="usage: $CmdName runs envsubst, but allows '\$' to  keep variables from
    being expanded.
  With option   -sl   '\$' keeps the back-slash.
  Default is to replace  '\$' with '$'
"

if [[ $1 = -h ]]  ;then echo -e >&2  "$Usage" ; exit 1 ;fi
if [[ $1 = -sl ]] ;then  sl='\'  ; shift ;fi

sed 's/\\\$/\${EnVsUbDolR}/g' |  EnVsUbDolR=$sl\$  envsubst  "$@"
LenW
sumber
5
+1. Ini adalah satu-satunya jawaban yang dijamin tidak melakukan pergantian perintah, penghapusan kutipan, pemrosesan argumen, dll.
Josh Kelley
6
Ini hanya berfungsi untuk shell vars yang sepenuhnya diekspor (dalam bash). misal S = '$ a $ b'; a = set; ekspor b = diekspor; echo $ S | envsubst; hasil: "diekspor"
gaoithe
1
terima kasih - Saya pikir setelah bertahun-tahun, saya harus menerima jawaban ini.
Michael Neale
1
Namun, itu tidak menangani $pelolosan tanda dolar ( ), misalnya echo '\$USER' | envsubstakan menampilkan nama pengguna saat ini dengan garis miring ke belakang, bukan $USER.
Rockallite
3
tampaknya mendapatkan ini di Mac dengan kebutuhan homebrewbrew link --force gettext
KeepCalmAndCarry Pada
25

Banyak jawaban yang menggunakan evaldan echojenis pekerjaan, tetapi merusak berbagai hal, seperti beberapa baris, mencoba untuk keluar dari meta-karakter shell, melarikan diri di dalam template yang tidak dimaksudkan untuk diperluas oleh bash, dll.

Saya memiliki masalah yang sama, dan menulis fungsi shell ini, yang sejauh yang saya tahu, menangani semuanya dengan benar. Ini masih akan menghapus hanya baris baru dari template, karena aturan substitusi perintah bash, tetapi saya tidak pernah menemukan itu menjadi masalah selama yang lainnya tetap utuh.

apply_shell_expansion() {
    declare file="$1"
    declare data=$(< "$file")
    declare delimiter="__apply_shell_expansion_delimiter__"
    declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter"
    eval "$command"
}

Misalnya, Anda dapat menggunakannya seperti ini dengan parameters.cfgyang merupakan skrip shell yang hanya menetapkan variabel, dan template.txttemplate yang menggunakan variabel tersebut:

. parameters.cfg
printf "%s\n" "$(apply_shell_expansion template.txt)" > result.txt

Dalam praktiknya, saya menggunakan ini sebagai semacam sistem template ringan.

wjl
sumber
4
Ini adalah salah satu trik terbaik yang saya temukan sejauh ini :). Berdasarkan jawaban Anda, sangat mudah membuat fungsi yang memperluas variabel dengan string yang berisi referensi ke variabel lain - cukup ganti $ data dengan $ 1 di fungsi Anda dan ini dia! Saya yakin ini adalah jawaban terbaik untuk pertanyaan asli, BTW.
galaksi
Bahkan ini memiliki evaljebakan yang biasa . Coba tambahkan ; $(eval date)di akhir baris mana pun di dalam file input dan itu akan menjalankan dateperintah.
anubhava
1
@anubhava Saya tidak yakin apa yang Anda maksud; yang sebenarnya adalah perilaku perluasan yang diinginkan dalam kasus ini. Dengan kata lain, ya, Anda diharapkan dapat mengeksekusi kode shell arbitrer dengan ini.
wjl
22

Anda dapat mencoba

echo $(eval echo $(cat something.txt))
Pizza
sumber
9
Gunakan echo -e "$(eval "echo -e \"`<something.txt`\"")"jika Anda membutuhkan baris baru.
pevik
Bekerja seperti pesona
kyb
1
Tetapi hanya jika Anda template.txtadalah teks. Operasi ini berbahaya karena menjalankan (mengevaluasi) perintah jika ada.
kyb
2
tapi ini menghapus kutipan ganda
Thomas Decaux
Salahkan pada subkulitnya.
ingyhere
8

Anda tidak ingin mencetak setiap baris, Anda ingin mengevaluasinya sehingga Bash dapat melakukan substitusi variabel.

FOO=42
while read; do
    eval echo "$REPLY"
done < something.txt

Lihat help evalatau manual Bash untuk informasi lebih lanjut.

Todd A. Jacobs
sumber
2
evalberbahaya ; Anda tidak hanya $varnameberkembang (seperti yang Anda lakukan dengan envsubst), tetapi $(rm -rf ~)juga.
Charles Duffy
4

Pendekatan lain (yang tampaknya menjijikkan, tetapi saya tetap meletakkannya di sini):

Tulis konten sesuatu.txt ke file temp, dengan pernyataan echo yang membungkusnya:

something=$(cat something.txt)

echo "echo \"" > temp.out
echo "$something" >> temp.out
echo "\"" >> temp.out

lalu masukkan kembali ke variabel:

RESULT=$(source temp.out)

dan $ RESULT akan membuat semuanya diperluas. Tapi sepertinya salah!

Michael Neale
sumber
1
menjijikkan tetapi paling lugas, sederhana, dan berhasil.
kyb
3

Jika Anda hanya ingin referensi variabel diperluas (tujuan yang saya miliki untuk saya sendiri), Anda dapat melakukan hal di bawah ini.

contents="$(cat something.txt)"
echo $(eval echo \"$contents\")

(Kutipan yang lolos di sekitar $ konten adalah kuncinya di sini)

SS781
sumber
Saya telah mencoba yang ini, namun $ () menghapus akhir baris dalam kasus saya yang memutuskan string yang dihasilkan
moz
Saya mengubah baris eval menjadi eval "echo \"$$contents\""dan itu mempertahankan spasi
moz
2
  1. Jika sesuatu.txt hanya memiliki satu baris, bashmetode, (versi yang lebih pendek dari jawaban "icky" Michael Neale ), menggunakan substitusi proses & perintah :

    FOO=42 . <(echo -e echo $(<something.txt))
    

    Keluaran:

    hello 42 world
    

    Perhatikan bahwa exportitu tidak diperlukan.

  2. Jika something.txt memiliki satu atau lebih garis, GNU valuate metode:sed e

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    
agc
sumber
1
Saya menemukan sedevaluasi yang paling sesuai dengan tujuan saya. Saya perlu membuat skrip dari template, yang nilainya diisi dari variabel yang diekspor ke file konfigurasi lain. Ini menangani pelolosan dengan benar untuk perluasan perintah misalnya dimasukkan ke \$(date)dalam template untuk mendapatkan $(date)output. Terima kasih!
gadamiak
1

Solusi berikut:

  • memungkinkan penggantian variabel yang ditentukan

  • meninggalkan placeholder variabel yang tidak berubah yang tidak ditentukan. Ini sangat berguna selama penerapan otomatis.

  • mendukung penggantian variabel dalam format berikut:

    ${var_NAME}

    $var_NAME

  • melaporkan variabel mana yang tidak didefinisikan dalam lingkungan dan mengembalikan kode kesalahan untuk kasus seperti itu



    TARGET_FILE=someFile.txt;
    ERR_CNT=0;

    for VARNAME in $(grep -P -o -e '\$[\{]?(\w+)*[\}]?' ${TARGET_FILE} | sort -u); do     
      VAR_VALUE=${!VARNAME};
      VARNAME2=$(echo $VARNAME| sed -e 's|^\${||g' -e 's|}$||g' -e 's|^\$||g' );
      VAR_VALUE2=${!VARNAME2};

      if [ "xxx" = "xxx$VAR_VALUE2" ]; then
         echo "$VARNAME is undefined ";
         ERR_CNT=$((ERR_CNT+1));
      else
         echo "replacing $VARNAME with $VAR_VALUE2" ;
         sed -i "s|$VARNAME|$VAR_VALUE2|g" ${TARGET_FILE}; 
      fi      
    done

    if [ ${ERR_CNT} -gt 0 ]; then
        echo "Found $ERR_CNT undefined environment variables";
        exit 1 
    fi
aprodan
sumber
0
$ eval echo $(cat something.txt)
hello 42 world
$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.
ClearCrescendo
sumber
0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)
Božský Boch
sumber
1
Terima kasih atas cuplikan kode ini, yang mungkin memberikan bantuan terbatas dan langsung. Sebuah penjelasan yang tepat akan sangat meningkatkan nilai jangka panjang dengan menunjukkan mengapa ini adalah solusi yang baik untuk masalah ini dan akan membuatnya lebih bermanfaat untuk pembaca masa depan dengan lainnya, pertanyaan-pertanyaan serupa. Mohon edit jawaban Anda untuk menambahkan penjelasan, termasuk asumsi yang Anda buat.
Capricorn
-1

envsubst adalah solusi yang bagus (lihat jawaban LenW) jika konten yang Anda gantikan memiliki panjang yang "masuk akal".

Dalam kasus saya, saya perlu mengganti konten file untuk mengganti nama variabel. envsubstmengharuskan konten diekspor sebagai variabel lingkungan dan bash mengalami masalah saat mengekspor variabel lingkungan yang lebih dari satu megabyte atau lebih.

solusi awk

Menggunakan solusi cuonglm dari pertanyaan berbeda:

needle="doc1_base64" # The "variable name" in the file. (A $ is not needed.)
needle_file="doc1_base64.txt" # Will be substituted for the needle 
haystack=$requestfile1 # File containing the needle
out=$requestfile2
awk "BEGIN{getline l < \"${needle_file}\"}/${needle}/{gsub(\"${needle}\",l)}1" $haystack > $out

Solusi ini berfungsi bahkan untuk file besar.

Larry K
sumber
-3

Karya-karya berikut: bash -c "echo \"$(cat something.txt)"\"

Eddy
sumber
Ini bukan hanya tidak efisien, tetapi juga sangat berbahaya; itu tidak hanya menjalankan ekspansi yang aman seperti $foo, tetapi yang tidak aman seperti $(rm -rf ~).
Charles Duffy