บังคับให้ bash ขยายตัวแปรในสตริงที่โหลดจากไฟล์


89

ฉันกำลังพยายามหาวิธีทำให้ bash (แรง?) ขยายตัวแปรในสตริง (ซึ่งโหลดจากไฟล์)

ฉันมีไฟล์ชื่อ "something.txt" ที่มีเนื้อหา:

hello $FOO world

จากนั้นฉันก็วิ่ง

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

ผลตอบแทนนี้:

   hello $FOO world

มันไม่ได้ขยาย $ FOO แม้ว่าจะตั้งค่าตัวแปรไว้ก็ตาม ฉันไม่สามารถประเมินหรือซอร์สไฟล์ได้เพราะมันจะพยายามและเรียกใช้งาน (มันไม่สามารถเรียกใช้งานได้อย่างที่เป็นอยู่ - ฉันแค่ต้องการสตริงที่มีการแทรกตัวแปร)

ความคิดใด ๆ ?



คุณหมายถึงความคิดเห็นสำหรับคำตอบด้านล่างนี้หรือไม่?
Michael Neale

ฉันหมายถึงความคิดเห็นสำหรับคุณ คำตอบมากกว่าหนึ่งข้อเสนอการใช้งานevalและสิ่งสำคัญคือต้องตระหนักถึงผลกระทบ
Dennis Williamson

@DennisWilliamson gotcha - ฉันตอบช้าไปหน่อย แต่เคล็ดลับที่ดี และแน่นอนว่าในช่วงหลายปีที่ผ่านมาเรามีสิ่งต่างๆเช่น "เชลล์ช็อต" ดังนั้นความคิดเห็นของคุณจึงเป็นบททดสอบของเวลา! tips hat
Michael Neale

สิ่งนี้ตอบคำถามของคุณหรือไม่? Bash ขยายตัวแปรในตัวแปร
LoganMzz

คำตอบ:


115

ฉันสะดุดกับสิ่งที่ฉันคิดว่าเป็นคำตอบสำหรับคำถามนี้: envsubstคำสั่ง

envsubst < something.txt

ในกรณีที่ยังไม่มีใน distro ของคุณจะอยู่ในไฟล์

gettextแพคเกจ GNU

@ Rockallite - ฉันเขียนสคริปต์กระดาษห่อเล็ก ๆ เพื่อดูแลปัญหา '\ $'

(BTW มี "คุณลักษณะ" ของ envsubst อธิบายไว้ที่ https://unix.stackexchange.com/a/294400/7088 สำหรับการขยายตัวแปรบางตัวในอินพุตเท่านั้น แต่ฉันยอมรับว่าการหลีกเลี่ยงข้อยกเว้นนั้นมีมากกว่านั้นมาก สะดวก)

นี่คือสคริปต์ของฉัน:

#! /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. นี่เป็นคำตอบเดียวที่รับประกันได้ว่าจะไม่ทำการแทนที่คำสั่งการลบใบเสนอราคาการประมวลผลอาร์กิวเมนต์ ฯลฯ
Josh Kelley

6
มันใช้งานได้กับเชลล์ที่ส่งออกอย่างสมบูรณ์แม้ว่า (ใน bash) เช่น S = '$ a $ b'; a = ชุด; ส่งออก b = ส่งออก; ก้อง $ S | envsubst; ผลตอบแทน: "ส่งออก"
gaoithe

1
ขอบคุณ - ฉันคิดว่าหลังจากผ่านไปหลายปีฉันควรยอมรับคำตอบนี้
Michael Neale

1
แต่ก็ไม่ได้จัดการกับเครื่องหมายดอลลาร์ ( $) หลบหนีเช่นecho '\$USER' | envsubstออกจะชื่อผู้ใช้ในปัจจุบันที่มีเครื่องหมายทับย้อนหลัง prefixing $USERไม่
Rockallite

3
ดูเหมือนว่าจะได้รับสิ่งนี้บน Mac ที่มีความต้องการ homebrewbrew link --force gettext
KeepCalmAndCarry ใน

25

หลายคำตอบโดยใช้evalและechoประเภทของงาน แต่แบ่งออกเป็นหลาย ๆ อย่างเช่นหลายบรรทัดพยายามที่จะหลบหนีอักขระเมตาดาต้าของเชลล์การหลบหนีภายในเทมเพลตไม่ได้ตั้งใจให้ขยายโดย bash เป็นต้น

ฉันมีปัญหาเดียวกันและเขียนฟังก์ชันเชลล์นี้ซึ่งเท่าที่ฉันสามารถบอกได้ว่าจัดการทุกอย่างได้อย่างถูกต้อง สิ่งนี้จะยังคงตัดเฉพาะบรรทัดใหม่ต่อท้ายจากเทมเพลตเนื่องจากกฎการทดแทนคำสั่งของ bash แต่ฉันไม่เคยพบว่าเป็นปัญหาตราบใดที่ทุกอย่างยังคงอยู่ครบถ้วน

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

ตัวอย่างเช่นคุณสามารถใช้แบบนี้กับไฟล์ parameters.cfgซึ่งเป็นเชลล์สคริปต์ที่ตั้งค่าตัวแปรเท่านั้นและtemplate.txtซึ่งเป็นเทมเพลตที่ใช้ตัวแปรเหล่านั้น:

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

ในทางปฏิบัติฉันใช้สิ่งนี้เป็นระบบเทมเพลตน้ำหนักเบา


4
นี่เป็นหนึ่งในเทคนิคที่ดีที่สุดที่ฉันพบ :) จากคำตอบของคุณมันง่ายมากที่จะสร้างฟังก์ชันที่ขยายตัวแปรด้วยสตริงที่มีการอ้างอิงถึงตัวแปรอื่น ๆ เพียงแค่แทนที่ $ data ด้วย $ 1 ในฟังก์ชันของคุณแล้วไปเลย! ฉันเชื่อว่านี่เป็นคำตอบที่ดีที่สุดสำหรับคำถามดั้งเดิม BTW
galaxy

แม้จะมีevalข้อผิดพลาดตามปกติ ลองเพิ่ม; $(eval date)ที่ส่วนท้ายของบรรทัดใดก็ได้ภายในไฟล์อินพุตและจะเรียกใช้dateคำสั่ง
anubhava

1
@anubhava ฉันไม่แน่ใจว่าคุณหมายถึงอะไร นั่นคือพฤติกรรมการขยายที่ต้องการในกรณีนี้ กล่าวอีกนัยหนึ่งใช่คุณควรจะสามารถรันโค้ดเชลล์ได้ตามอำเภอใจด้วยสิ่งนี้
wjl

22

คุณสามารถลอง

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

9
ใช้echo -e "$(eval "echo -e \"`<something.txt`\"")"ถ้าคุณต้องการเส้นใหม่เก็บไว้
pevik

ทำงานเหมือนมีเสน่ห์
kyb

1
แต่ถ้าtemplate.txtเป็นข้อความของคุณ การดำเนินการนี้เป็นอันตรายเนื่องจากจะดำเนินการ (ประเมิน) คำสั่งหากเป็นเช่นนั้น
kyb

2
แต่สิ่งนี้ลบคำพูดคู่
Thomas Decaux

ตำหนิบน subshell
นี่

8

คุณไม่ต้องการพิมพ์แต่ละบรรทัดคุณต้องการประเมินเพื่อให้ Bash ทำการแทนตัวแปรได้

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

ดูhelp evalหรือคู่มือ Bash สำหรับข้อมูลเพิ่มเติม


2
evalเป็นอันตราย ; คุณไม่เพียง แต่$varnameขยาย (ตามที่คุณต้องการenvsubst) แต่ยัง$(rm -rf ~)รวมถึง
Charles Duffy

4

อีกวิธีหนึ่ง (ซึ่งดูน่าเบื่อ แต่ฉันก็วางไว้ที่นี่):

เขียนเนื้อหาของ something.txt ไปยังไฟล์ temp โดยมีคำสั่ง echo ล้อมรอบ:

something=$(cat something.txt)

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

จากนั้นส่งกลับไปยังตัวแปร:

RESULT=$(source temp.out)

และ $ RESULT จะขยายทั้งหมด แต่ดูเหมือนผิดมาก!


1
น่าเบื่อ แต่ตรงไปตรงมาเรียบง่ายและใช้งานได้ดีที่สุด
kyb

3

หากคุณต้องการขยายการอ้างอิงตัวแปร (วัตถุประสงค์ที่ฉันมีเพื่อตัวฉันเอง) คุณสามารถทำสิ่งต่อไปนี้ได้

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

(เครื่องหมายคำพูดที่หลีกเลี่ยงเนื้อหา $ เป็นกุญแจสำคัญที่นี่)


ฉันได้ลองสิ่งนี้แล้ว แต่ $ () จะลบการสิ้นสุดบรรทัดในกรณีของฉันซึ่งทำให้สตริงที่แตกออก
moz

ฉันเปลี่ยนเส้น eval เป็นeval "echo \"$$contents\""และรักษาช่องว่างไว้
moz

2
  1. หากsomething.txtมีเพียงหนึ่งเส้นเป็นbashวิธีการ (รุ่นสั้นของไมเคิลเนลของ "เหนอะ" คำตอบ ) โดยใช้กระบวนการและแทนคำสั่ง :

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

    เอาท์พุต:

    hello 42 world
    

    โปรดทราบว่าexportไม่จำเป็น

  2. หากsomething.txtมีอย่างน้อยหนึ่งบรรทัดวิธีการประเมินค่าGNU :sed e

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

1
ฉันพบว่าการsedประเมินนั้นเหมาะสมกับวัตถุประสงค์ของฉันมากที่สุด ฉันต้องการสร้างสคริปต์จากเทมเพลตซึ่งจะมีค่าที่เติมจากตัวแปรที่ส่งออกในไฟล์กำหนดค่าอื่น มันจัดการกับการหลีกเลี่ยงการขยายคำสั่งอย่างถูกต้องเช่นใส่\$(date)เทมเพลตเพื่อรับ$(date)ในเอาต์พุต ขอบคุณ!
gadamiak

1

วิธีแก้ไขต่อไปนี้:

  • อนุญาตให้แทนที่ตัวแปรที่กำหนดไว้

  • ทิ้งตัวยึดตำแหน่งตัวแปรที่ไม่เปลี่ยนแปลงซึ่งไม่ได้กำหนดไว้ สิ่งนี้มีประโยชน์อย่างยิ่งในระหว่างการปรับใช้อัตโนมัติ

  • รองรับการแทนที่ตัวแปรในรูปแบบต่อไปนี้:

    ${var_NAME}

    $var_NAME

  • รายงานว่าตัวแปรใดไม่ได้กำหนดไว้ในสภาพแวดล้อมและส่งคืนรหัสข้อผิดพลาดสำหรับกรณีดังกล่าว



    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
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)

1
ขอบคุณสำหรับข้อมูลโค้ดนี้ซึ่งอาจให้ความช่วยเหลือเฉพาะบางอย่างได้ทันที คำอธิบายที่เหมาะสมมากจะเพิ่มมูลค่าในระยะยาวด้วยการแสดงเหตุผลนี้เป็นทางออกที่ดีในการแก้ไขปัญหาและจะทำให้มันมีประโยชน์มากขึ้นให้กับผู้อ่านในอนาคตกับอื่น ๆ คำถามที่คล้ายกัน โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายรวมถึงสมมติฐานที่คุณตั้งขึ้น
ราศีมังกร

-1

envsubst เป็นวิธีแก้ปัญหาที่ยอดเยี่ยม (ดูคำตอบของ LenW) หากเนื้อหาที่คุณนำมาทดแทนมีความยาว "เหมาะสม"

ในกรณีของฉันฉันต้องแทนที่เนื้อหาของไฟล์เพื่อแทนที่ชื่อตัวแปร envsubstกำหนดให้ส่งออกเนื้อหาเป็นตัวแปรสภาพแวดล้อมและ bash มีปัญหาเมื่อส่งออกตัวแปรสภาพแวดล้อมที่มีขนาดมากกว่าหนึ่งเมกะไบต์

โซลูชัน awk

ใช้วิธีแก้ปัญหาของ cuonglm จากคำถามอื่น:

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

โซลูชันนี้ใช้ได้กับไฟล์ขนาดใหญ่


-3

ผลงานดังต่อไปนี้: bash -c "echo \"$(cat something.txt)"\"


สิ่งนี้ไม่เพียง แต่ไม่มีประสิทธิภาพ แต่ยังเป็นอันตรายอย่างยิ่ง มันไม่เพียงแค่เรียกใช้การขยายที่ปลอดภัยเช่น$foo$(rm -rf ~)แต่คนที่ไม่ปลอดภัยเช่น
Charles Duffy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.