Buộc bash mở rộng các biến trong một chuỗi được tải từ một tệp


88

Tôi đang cố gắng tìm ra cách tạo biến mở rộng bash (force?) Trong một chuỗi (được tải từ một tệp).

Tôi có một tệp có tên "something.txt" với nội dung:

hello $FOO world

Sau đó tôi chạy

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

điều này trả về:

   hello $FOO world

Nó không mở rộng $ FOO mặc dù biến đã được đặt. Tôi không thể đánh giá hoặc nguồn tệp - vì nó sẽ thử và thực thi nó (nó không thực thi được như hiện tại - tôi chỉ muốn chuỗi với các biến được nội suy).

Có ý kiến ​​gì không?


3
Nhận thức được các tác động bảo mật củaeval .
Tạm dừng cho đến khi có thông báo mới.

Ý của bạn là nhận xét cho câu trả lời bên dưới?
Michael Neale

Tôi có nghĩa là nhận xét cho bạn. Nhiều câu trả lời đề xuất việc sử dụng evalvà điều quan trọng là phải nhận thức được các hàm ý.
Tạm dừng cho đến khi có thông báo mới.

@DennisWilliamson gotcha - Tôi trả lời hơi muộn nhưng mẹo hay. Và tất nhiên trong những năm tháng chúng ta đã có những điều như "sốc vỏ" vì vậy nhận xét của bạn đã đứng trước thử thách của thời gian! tip hat
Michael Neale

Điều này có trả lời câu hỏi của bạn không? Biến mở rộng Bash trong một biến
LoganMzz

Câu trả lời:


111

Tôi đã vấp phải câu trả lời mà tôi nghĩ là THEO câu hỏi này: câu envsubstlệnh.

envsubst < something.txt

Trong trường hợp nó chưa có sẵn trong bản phân phối của bạn, nó sẽ nằm trong

Gói GNU gettext.

@Rockallite - Tôi đã viết một đoạn script nhỏ để giải quyết vấn đề '\ ​​$'.

(BTW, có một "tính năng" của envsubst, được giải thích tại https://unix.stackexchange.com/a/294400/7088 để chỉ mở rộng một số biến trong đầu vào, nhưng tôi đồng ý rằng thoát khỏi các ngoại lệ còn nhiều hơn thế nữa tiện lợi.)

Đây là kịch bản của tôi:

#! /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. Đây là câu trả lời duy nhất đó là đảm bảo để không làm thay lệnh, loại bỏ báo giá, xử lý tranh luận vv
Josh Kelley

6
Nó chỉ hoạt động cho các shell vars được xuất hoàn toàn (trong bash). ví dụ: S = '$ a $ b'; a = bộ; export b = xuất khẩu; echo $ S | envsubst; sản lượng: "xuất"
gaoithe

1
cảm ơn - Tôi nghĩ sau ngần ấy năm, tôi nên chấp nhận câu trả lời này.
Michael Neale

1
Tuy nhiên, nó không xử lý $thoát khỏi ký hiệu đô la ( ), ví dụ: echo '\$USER' | envsubstsẽ xuất tên người dùng hiện tại với dấu gạch chéo ngược ở tiền tố, không phải $USER.
Rockallite

3
dường như nhận được điều này trên máy Mac có nhu cầu về homebrewbrew link --force gettext
KeepCalmAndCarryOn

25

Nhiều câu trả lời bằng cách sử dụng evalecholoại công việc, nhưng phá vỡ những thứ khác nhau, chẳng hạn như nhiều dòng, cố gắng thoát khỏi các ký tự meta shell, thoát bên trong mẫu không nhằm mục đích mở rộng bằng cách bash, v.v.

Tôi đã gặp vấn đề tương tự và đã viết hàm shell này, theo như tôi có thể nói, xử lý mọi thứ một cách chính xác. Điều này sẽ vẫn chỉ loại bỏ các dòng mới theo sau khỏi mẫu, vì các quy tắc thay thế lệnh của bash, nhưng tôi chưa bao giờ thấy đó là một vấn đề miễn là mọi thứ khác vẫn còn nguyên vẹn.

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

Ví dụ: bạn có thể sử dụng nó như thế này với một parameters.cfgtập lệnh shell thực sự chỉ đặt các biến và một template.txtlà mẫu sử dụng các biến đó:

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

Trong thực tế, tôi sử dụng nó như một loại hệ thống mẫu nhẹ.


4
Đây là một trong những thủ thuật tốt nhất mà tôi tìm thấy cho đến nay :). Dựa trên câu trả lời của bạn, thật dễ dàng để xây dựng một hàm mở rộng một biến với một chuỗi chứa tham chiếu đến các biến khác - chỉ cần thay thế $ data bằng $ 1 trong hàm của bạn và chúng ta bắt đầu! Tôi tin rằng đây là câu trả lời tốt nhất cho câu hỏi ban đầu, BTW.
galaxy

Ngay cả điều này cũng có evalnhững cạm bẫy thông thường . Hãy thử thêm ; $(eval date)vào cuối bất kỳ dòng nào bên trong tệp đầu vào và nó sẽ chạy datelệnh.
anubhava

1
@anubhava Tôi không chắc ý của bạn; đó thực sự là hành vi mở rộng mong muốn trong trường hợp này. Nói cách khác, có, bạn có thể thực thi mã shell tùy ý với điều này.
wjl

22

bạn co thể thử

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

9
Sử dụng echo -e "$(eval "echo -e \"`<something.txt`\"")"nếu bạn cần giữ dòng mới.
pevik

Hoạt động như một sự quyến rũ
kyb

1
Nhưng chỉ khi của bạn template.txtlà văn bản. Thao tác này nguy hiểm vì nó thực thi (đánh giá) các lệnh nếu chúng có.
kyb

2
nhưng dấu ngoặc kép này đã bị xóa
Thomas Decaux

Đổ lỗi cho nó trên vỏ con.
ingyhere

8

Bạn không muốn in từng dòng, bạn muốn đánh giá nó để Bash có thể thực hiện các thay thế khác nhau.

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

Xem help evalhoặc hướng dẫn sử dụng Bash để biết thêm thông tin.


2
evallà nguy hiểm ; bạn không chỉ được $varnamemở rộng (như bạn làm với envsubst), mà còn $(rm -rf ~).
Charles Duffy

4

Một cách tiếp cận khác (có vẻ khó, nhưng dù sao thì tôi cũng đang đặt nó ở đây):

Ghi nội dung của something.txt vào một tệp tạm thời, với một câu lệnh echo bao quanh nó:

something=$(cat something.txt)

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

sau đó chuyển nguồn nó trở lại một biến:

RESULT=$(source temp.out)

và $ RESULT sẽ mở rộng tất cả. Nhưng nó có vẻ rất sai lầm!


1
khó hiểu nhưng nó thẳng thắn, đơn giản và hiệu quả nhất.
kyb

3

Nếu bạn chỉ muốn các tham chiếu biến được mở rộng (một mục tiêu mà tôi đã có cho bản thân mình), bạn có thể làm như dưới đây.

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

(Các dấu ngoặc kép xung quanh $ content là chìa khóa ở đây)


Tôi đã thử cái này, tuy nhiên $ () loại bỏ các kết thúc dòng trong trường hợp của tôi, điều này làm hỏng chuỗi kết quả
moz

Tôi đã thay đổi dòng eval thành eval "echo \"$$contents\""và nó giữ nguyên khoảng trắng
moz

1

Giải pháp sau:

  • cho phép thay thế các biến đã được xác định

  • để lại chỗ dành sẵn cho các biến không thay đổi không được xác định. Điều này đặc biệt hữu ích trong quá trình triển khai tự động.

  • hỗ trợ thay thế các biến ở các định dạng sau:

    ${var_NAME}

    $var_NAME

  • báo cáo biến nào không được xác định trong môi trường và trả về mã lỗi cho các trường hợp đó



    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

1
  1. Nếu something.txt chỉ có một dòng, một bashphương thức, (phiên bản ngắn hơn của câu trả lời "icky" của Michael Neale ), sử dụng quy trìnhthay thế lệnh :

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

    Đầu ra:

    hello 42 world
    

    Lưu ý rằng exportkhông cần thiết.

  2. Nếu something.txt có một hoặc nhiều dòng, phương pháp định giá GNU :sed e

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

1
Tôi thấy sedđánh giá phù hợp nhất với mục đích của tôi. Tôi cần tạo tập lệnh từ các mẫu, sẽ có các giá trị được điền từ các biến được xuất trong một tệp cấu hình khác. Nó xử lý thoát đúng cách để mở rộng lệnh, ví dụ như đưa \$(date)vào mẫu để nhận được $(date)đầu ra. Cảm ơn!
gadamiak

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
Cảm ơn bạn về đoạn mã này, đoạn mã này có thể cung cấp một số trợ giúp hạn chế, tức thì. Một lời giải thích phù hợp sẽ cải thiện đáng kể giá trị lâu dài của nó bằng cách chỉ ra lý do tại sao đây là một giải pháp tốt cho vấn đề và sẽ hữu ích hơn cho những người đọc trong tương lai với những câu hỏi tương tự khác. Vui lòng chỉnh sửa câu trả lời của bạn để thêm một số giải thích, bao gồm cả những giả định bạn đã đưa ra.
Ma Kết

-1

envsubst là một giải pháp tuyệt vời (xem câu trả lời của LenW) nếu nội dung bạn đang thay thế có độ dài "hợp lý".

Trong trường hợp của tôi, tôi cần thay thế trong nội dung của tệp để thay thế tên biến. envsubstyêu cầu nội dung được xuất dưới dạng các biến môi trường và bash gặp sự cố khi xuất các biến môi trường lớn hơn một megabyte hoặc hơn.

giải pháp awk

Sử dụng giải pháp của cuonglm cho một câu hỏi khác:

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

Giải pháp này hoạt động cho cả các tệp lớn.


-3

Các hoạt động sau: bash -c "echo \"$(cat something.txt)"\"


Điều này không chỉ không hiệu quả mà còn cực kỳ nguy hiểm; nó không chỉ chạy các bản mở rộng an toàn mà còn chạy các bản mở rộng $fookhông an toàn như $(rm -rf ~).
Charles Duffy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.