Bash'i bir dosyadan yüklenen bir dizedeki değişkenleri genişletmeye zorlama


89

Bir dizede (bir dosyadan yüklenmiş) değişkenleri genişletmek için bash (force?) 'U nasıl yapacağımı çözmeye çalışıyorum.

İçeriği olan "bir şeyler.txt" adında bir dosyam var:

hello $FOO world

Sonra koşarım

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

bu şunu döndürür:

   hello $FOO world

Değişken ayarlanmış olmasına rağmen $ FOO'yu genişletmedi. Dosyayı değerlendiremiyorum veya kaynak alamıyorum - çünkü onu deneyecek ve çalıştıracak (olduğu gibi çalıştırılabilir değil - sadece enterpolasyonlu değişkenlerle dizeyi istiyorum).

Herhangi bir fikir?



Yorumun aşağıdaki cevap için olmasını mı istediniz?
Michael Neale

Yorumu senin için kastettim. Birden fazla yanıt, kullanımını önerir evalve sonuçlarının farkında olmak önemlidir.
Dennis Williamson

@DennisWilliamson gotcha - Biraz geç cevap verdim ama iyi bir ipucu. Ve tabii ki, aradan geçen yıllarda "kabuk şoku" gibi şeyler yaşadık, bu yüzden yorumunuz zamanın testinden geçti! ipuçları şapka
Michael Neale

Yanıtlar:


115

Bu sorunun cevabı olduğunu düşündüğüm şeye rastladım: envsubstemir.

envsubst < something.txt

Dağıtımınızda halihazırda mevcut değilse,

GNU paketi gettext.

@Rockallite - '\ $' sorununu halletmek için küçük bir sarmalayıcı komut dosyası yazdım.

(BTW, https://unix.stackexchange.com/a/294400/7088 adresinde girişteki değişkenlerin yalnızca bazılarını genişletmek için açıklanan envsubst "özelliği" vardır , ancak istisnalardan kaçmanın çok daha fazla olduğunu kabul ediyorum uygun.)

İşte benim senaryom:

#! /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  "$@"

5
+1. Bu, komut değiştirme, alıntı kaldırma, argüman işleme vb.
Josh Kelley

6
Sadece tamamen dışa aktarılan kabuk değişkenleri için çalışır (bash içinde). örneğin S = '$ a $ b'; a = set; dışa aktar b = dışa aktarıldı; echo $ S | envsubst; verim: "dışa
aktarıldı

1
teşekkürler - bunca yıldan sonra bu cevabı kabul etmem gerektiğini düşünüyorum.
Michael Neale

1
Ancak, dolar işareti ( $) kaçışını işlemez , örneğin echo '\$USER' | envsubstgeçerli kullanıcı adını geriye doğru eğik çizgi ile değil, önek olarak çıkarır $USER.
Rockallite

3
bunu homebrew ihtiyaçları olan bir Mac'te alıyor gibi görünüyorbrew link --force gettext
KeepCalmAndCarryOn

25

Yanıtların birçoğu işe yarıyor evalve echoişe yarıyor, ancak birden çok satır, kabuk meta karakterlerinden kaçmaya çalışmak, bash ile genişletilmesi amaçlanmayan şablonun içinden kaçmak gibi çeşitli şeylerden kopuyor.

Aynı sorunu yaşadım ve söyleyebildiğim kadarıyla her şeyi doğru bir şekilde ele alan bu kabuk işlevini yazdım. Bu hala, bash'ın komut ikame kuralları nedeniyle şablondan yalnızca son satırları çıkaracaktır, ancak diğer her şey bozulmadan kaldığı sürece bunun bir sorun olduğunu asla bulamadım.

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

Örneğin, bunu şu şekilde kullanabilirsiniz parameters.cfg: gerçekten sadece değişkenleri ayarlayan bir kabuk betiği ve template.txtbu değişkenleri kullanan bir şablon olan a:

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

Pratikte bunu bir tür hafif şablon sistemi olarak kullanıyorum.


4
Bu şimdiye kadar bulduğum en iyi numaralardan biri :). Cevabınıza göre, bir değişkeni diğer değişkenlere referanslar içeren bir dizeyle genişleten bir işlev oluşturmak çok kolaydır - işlevinizde $ veriyi $ 1 ile değiştirin ve işte başlıyoruz! Bunun orijinal soru olan BTW'ye en iyi cevap olduğuna inanıyorum.
galaksi

Bunun bile olağan evaltuzakları vardır. ; $(eval date)Girdi dosyası içindeki herhangi bir satırın sonuna eklemeyi deneyin ve datekomut çalıştıracaktır .
anubhava

1
@anubhava Ne demek istediğinden emin değilim; bu aslında bu durumda istenen genişleme davranışıdır. Başka bir deyişle, evet, bununla keyfi bir kabuk kodu çalıştırabilmeniz gerekiyor.
wjl

22

deneyebilirsin

echo $(eval echo $(cat something.txt))

9
echo -e "$(eval "echo -e \"`<something.txt`\"")"Yeni hatların tutulmasına ihtiyacınız varsa kullanın .
pevik

Cazibe gibi çalışıyor
kyb

1
Ama sadece senin template.txtmetin ise. Bu işlem tehlikelidir çünkü eğer varsa komutları yürütür (değerlendirir).
kyb

2
ancak bu çift alıntı kaldırıldı
Thomas Decaux

Alt kabuğunu suçla.
ingyhere

8

Her satırı yazdırmak istemezsiniz , Bash'in değişken ikameleri gerçekleştirebilmesi için değerlendirmek istersiniz .

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

Daha help evalfazla bilgi için veya Bash kılavuzuna bakın .


2
evaltehlikelidir ; sadece $varname(yapacağınız gibi envsubst) değil, aynı $(rm -rf ~)zamanda genişlemiş olursunuz .
Charles Duffy

4

Başka bir yaklaşım (ki bu iğrenç görünüyor, ama yine de buraya koyuyorum):

Bir şey.txt'nin içeriğini, etrafına bir echo ifadesi ile bir geçici dosyaya yazın:

something=$(cat something.txt)

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

sonra onu bir değişkene geri kaynakla:

RESULT=$(source temp.out)

ve $ RESULT her şeyi genişletecek. Ama çok yanlış görünüyor!


1
icky ama çok açık, basit ve işe yarıyor.
kyb

3

Yalnızca değişken referansların genişletilmesini istiyorsanız (benim için sahip olduğum bir hedef), aşağıdakileri yapabilirsiniz.

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

($ Content etrafındaki kaçak alıntılar burada anahtardır)


Bunu denedim, ancak $ () benim durumumda ortaya çıkan dizeyi bozan satır sonlarını kaldırıyor
moz

Eval satırını olarak değiştirdim eval "echo \"$$contents\""ve boşlukları korudu
moz

2
  1. Bir şey.txt dosyasının yalnızca bir satırı, bir bashyöntemi varsa ( Michael Neale'in "icky" yanıtının daha kısa bir versiyonu ), işlem ve komut ikamesi kullanılarak :

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

    Çıktı:

    hello 42 world
    

    Bunun exportgerekli olmadığını unutmayın .

  2. Eğer birşey.txt bir veya daha fazla çizgi, bir yer alır GNU olarak değerlendirir yöntemi:sed e

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    

1
Bulduğum seden iyi şekilde değerlendirmek amacımı uygun. Başka bir yapılandırma dosyasında dışa aktarılan değişkenlerden doldurulan değerlere sahip şablonlardan komut dosyaları üretmem gerekiyordu. Komut genişletme için doğru şekilde kaçmayı yönetir, örneğin çıktıya \$(date)girmek için şablona koymak $(date). Teşekkürler!
gadamiak

1

Aşağıdaki çözüm:

  • tanımlanan değişkenlerin değiştirilmesine izin verir

  • tanımlanmamış değişmeyen değişken yer tutucuları bırakır. Bu, özellikle otomatik dağıtımlar sırasında kullanışlıdır.

  • aşağıdaki formatlarda değişkenlerin değiştirilmesini destekler:

    ${var_NAME}

    $var_NAME

  • hangi değişkenlerin ortamda tanımlanmadığını raporlar ve bu gibi durumlar için hata kodu döndürür



    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

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.

0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)

1
Sınırlı ve anında yardım sağlayabilecek bu kod parçacığı için teşekkür ederiz. Bir Doğru bir açıklama ölçüde bu soruna iyi bir çözüm olmasının nedeni göstererek uzun vadeli değerini artıracak ve diğer benzer sorularla gelecek okuyucularına daha kullanışlı bir hale getirecektir. Yaptığınız varsayımlar da dahil olmak üzere bazı açıklamalar eklemek için lütfen yanıtınızı düzenleyin .
Oğlak

-1

envsubst yerine koyduğunuz içerik "makul" uzunluktaysa harika bir çözümdür (LenW'nin cevabına bakın).

Benim durumumda, değişken adını değiştirmek için bir dosyanın içeriğini değiştirmem gerekiyordu. envsubstiçeriğin ortam değişkenleri olarak dışa aktarılmasını gerektirir ve bash, bir megabayttan daha büyük ortam değişkenlerini dışa aktarırken bir sorun yaşar.

awk çözümü

Cuonglm çözümünü farklı bir sorudan kullanmak:

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

Bu çözüm, büyük dosyalar için bile işe yarar.


-3

Aşağıdaki işler: bash -c "echo \"$(cat something.txt)"\"


Bu sadece verimsiz değil, aynı zamanda son derece tehlikelidir; sadece güvenli genişletmeler $foodeğil, güvenli olmayanlar gibi $(rm -rf ~).
Charles Duffy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.