Làm thế nào để gán giá trị di truyền cho một biến trong Bash?


374

Tôi có chuỗi nhiều dòng này (bao gồm cả trích dẫn):

abc'asdf"
$(dont-execute-this)
foo"bar"''

Làm thế nào tôi có thể gán nó cho một biến bằng cách sử dụng một di truyền trong Bash?

Tôi cần phải bảo tồn các dòng mới.

Tôi không muốn thoát khỏi các ký tự trong chuỗi, điều đó sẽ gây phiền nhiễu ...


@JohnM - Tôi vừa thử một nhiệm vụ heredoc với trích dẫn đơn 'EOF', với các ngắt dòng đã thoát với ` in the content: if the second line has lệnh cd`, tôi nhận lại: " .sh: line X: cd: lệnh không tìm thấy "; nhưng nếu tôi trích dẫn đôi "EOF"; sau đó các biến bash ${A}không được bảo toàn dưới dạng chuỗi (chúng được mở rộng); nhưng sau đó, ngắt dòng được giữ nguyên - và, tôi không gặp vấn đề gì khi chạy lệnh với cddòng thứ hai ( và cả 'EOF' và "EOF" dường như cũng hoạt động tốt với evalviệc chạy một tập lệnh được lưu trữ trong một biến chuỗi ). Chúc mừng!
sdaau

1
... và để thêm vào nhận xét trước đây của tôi: bash bình luận "#" trong biến hai lần "EOF", nếu được gọi qua eval $VAR, sẽ khiến tất cả phần còn lại của tập lệnh được nhận xét, vì ở đây $ VAR sẽ được xem là một dòng duy nhất ; để có thể sử dụng các #bình luận bash trong tập lệnh multiline, trích dẫn kép cũng biến trong eval call: eval "$ VAR" `.
sdaau

@sdaau: Tôi gặp vấn đề với evalphương pháp này, nhưng không theo dõi được vì đây là một phần của gói có evalmột số biến được định nghĩa trong tệp cấu hình của nó. Thông báo lỗi là : /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. Tôi chỉ chuyển sang một giải pháp khác.
Gole Ramblar

Câu trả lời:


515

Bạn có thể tránh việc sử dụng vô ích catvà xử lý các trích dẫn không khớp tốt hơn với điều này:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

Nếu bạn không trích dẫn biến khi bạn lặp lại nó, dòng mới sẽ bị mất. Trích dẫn nó bảo tồn chúng:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Nếu bạn muốn sử dụng thụt lề cho khả năng đọc trong mã nguồn, hãy sử dụng dấu gạch ngang sau ít hơn. Việc thụt lề phải được thực hiện chỉ bằng các tab (không có khoảng trắng).

$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
    EOF
$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Thay vào đó, nếu bạn muốn giữ các tab trong nội dung của biến kết quả, bạn cần xóa tab khỏi IFS. Dấu đầu cuối cho tài liệu ở đây ( EOF) không được thụt lề.

$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF
$ echo "$VAR"
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''

Các tab có thể được chèn vào dòng lệnh bằng cách nhấn Ctrl- V Tab. Nếu bạn đang sử dụng trình chỉnh sửa, tùy thuộc vào trình chỉnh sửa nào, điều đó cũng có thể hoạt động hoặc bạn có thể phải tắt tính năng tự động chuyển đổi tab thành không gian.


117
Tôi nghĩ rằng đáng để đề cập rằng nếu bạn có set -o errexit(aka set -e) trong tập lệnh của mình và bạn sử dụng tập lệnh này thì nó sẽ chấm dứt tập lệnh của bạn vì readtrả về mã trả về khác không khi đạt đến EOF.
Mark Byers

14
@MarkByer: Đó là một trong những lý do tôi không bao giờ sử dụng set -evà luôn khuyến nghị không sử dụng nó. Thay vào đó, tốt hơn là sử dụng xử lý lỗi thích hợp. traplà bạn của bạn. Những người bạn khác: else||trong số những người khác.
Tạm dừng cho đến khi có thông báo mới.

7
Là sự tránh né catthực sự có giá trị trong trường hợp như vậy? Gán một di sản cho một biến với catlà một thành ngữ nổi tiếng. Bằng cách nào đó sử dụng readobfuscates những thứ cho lợi ích nhỏ imho.
Gregory Pakosz

6
@ulidtko Đó là vì bạn không có khoảng dtrắng giữa và chuỗi trống; bashsụp đổ -rd''để đơn giản là -rdtrước khi readnhìn thấy các đối số của nó, vì vậy VARđược coi là đối số -d.
chepner

6
Trong định dạng này, readsẽ trả về với mã thoát khác không. Điều này làm cho phương thức này ít hơn lý tưởng trong một tập lệnh có bật kiểm tra lỗi (ví dụ set -e).
Thụy Sĩ

245

Sử dụng $ () để gán đầu ra catcho biến của bạn như thế này:

VAR=$(cat <<'END_HEREDOC'
abc'asdf"
$(dont-execute-this)
foo"bar"''
END_HEREDOC
)

# this will echo variable with new lines intact
echo "$VAR"
# this will echo variable without new lines (changed to space character)
echo $VAR

Đảm bảo phân định bắt đầu END_HEREDOC bằng dấu ngoặc đơn.

Lưu ý rằng kết thúc dấu phân cách di truyền END_HEREDOCphải ở một mình trên dòng (do đó kết thúc dấu ngoặc đơn nằm trên dòng tiếp theo).

Cảm ơn @ephemientcâu trả lời.


1
Trên thực tế, điều này cho phép thông qua trích dẫn trong một số trường hợp. Tôi đã xoay sở để làm điều này trong Perl một cách dễ dàng ...
Neil

32
+1. Đây là giải pháp dễ đọc nhất, ít nhất là cho mắt tôi. Nó để lại tên của biến ở phía xa bên trái của trang, thay vì nhúng nó vào lệnh đọc.
Clayton Stanley

12
PSA: hãy nhớ rằng biến phải được trích dẫn để bảo toàn dòng mới. echo "$VAR"thay vì echo $VAR.
Sevko

7
Điều này thật tuyệt với ashvà OpenWRT khi readkhông hỗ trợ -d.
David Ehrmann

3
Vì những lý do tôi không thể hiểu được, điều này không thành công với lỗi "EOF bất ngờ" nếu bạn có một backtick không ghép đôi trong di truyền.
Radon Rosborough

79

đây là biến thể của phương pháp Dennis, trông thanh lịch hơn trong các tập lệnh.

định nghĩa hàm:

define(){ IFS='\n' read -r -d '' ${1} || true; }

sử dụng:

define VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

echo "$VAR"

thưởng thức

ps đã tạo một phiên bản 'vòng lặp đọc' cho các shell không hỗ trợ read -d. nên làm việc với set -eubackticks không ghép đôi , nhưng không được kiểm tra tốt:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }

1
Điều này dường như chỉ hoạt động bề ngoài. Hàm xác định sẽ trả về trạng thái là 1 và tôi không chắc chắn những gì cần phải sửa.
fny

1
Điều này cũng vượt trội so với câu trả lời được chấp nhận, bởi vì nó có thể được sửa đổi để hỗ trợ POSIX sh ngoài bash (một readvòng lặp trong chức năng, để tránh -d ''bashism cần thiết để duy trì dòng mới).
ELLIOTTCABLE

Không giống như tùy chọn cat-in-a-subshell, cách này hoạt động với các backticks không ghép đôi trong di truyền. Cảm ơn bạn!
Radon Rosborough

Giải pháp này hoạt động với set -etập hợp, trong khi câu trả lời được chọn thì không. Có vẻ là vìhttp://unix.stackexchange.com/a/265151/20650
ffledgling

2
@fny tình trạng trả lại ps đã được sửa từ lâu
ttt

36
VAR=<<END
abc
END

không hoạt động vì bạn đang chuyển hướng stdin sang thứ gì đó không quan tâm đến nó, cụ thể là bài tập

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A

hoạt động, nhưng có một back-tic trong đó có thể ngăn bạn sử dụng này. Ngoài ra, bạn thực sự nên tránh sử dụng backticks, tốt hơn là sử dụng ký hiệu thay thế lệnh $(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A

Tôi đã cập nhật câu hỏi của mình để bao gồm $ (có thể thực thi). Ngoài ra, làm thế nào để bạn bảo quản dòng mới?
Neil

2
@ l0st3d: Rất gần ... Sử dụng $(cat <<'END'thay thế. @Neil: Dòng mới cuối cùng sẽ không phải là một phần của biến, nhưng phần còn lại sẽ được bảo tồn.
ephemient

1
Dường như không có bất kỳ dòng mới nào được bảo tồn. Chạy ví dụ trên tôi thấy: "sdfsdf sdfsdf sdfsfds" ... ah! Nhưng viết echo "$A"(tức là đặt $ A trong dấu ngoặc kép) và bạn thấy dòng mới!
Darren Cook

1
@Darren: aha! Tôi đã nhận thấy vấn đề về dòng mới và việc sử dụng các trích dẫn xung quanh biến đầu ra sẽ khắc phục vấn đề. cám ơn!
javadba

1
Thật thú vị, do sự châm biếm của ví dụ đầu tiên, trong một nhúm bạn có thể sử dụng nó cho các khối nhận xét tạm thời như thế này : REM=<< 'REM' ... comment block goes here ... REM. Hoặc gọn hơn , : << 'REM' .... Trong đó "REM" có thể là một cái gì đó như "GHI CHÚ" hoặc "SCRATCHPAD", v.v.
Beejor

34

Hiện vẫn chưa có giải pháp bảo tồn dòng mới.

Điều này không đúng - có lẽ bạn chỉ đang bị đánh lừa bởi hành vi của tiếng vang:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines


5
Thực sự đây là hành vi của cách trích dẫn một biến hoạt động. Không có dấu ngoặc kép, nó sẽ chèn chúng dưới dạng các tham số khác nhau, dấu cách được phân cách, trong khi với dấu ngoặc kép, toàn bộ nội dung biến sẽ được coi là một đối số
Czipperz

11

Phân nhánh câu trả lời của Neil , bạn thường không cần một var nào cả, bạn có thể sử dụng một hàm theo cách tương tự như một biến và nó dễ đọc hơn nhiều so với các readgiải pháp nội tuyến hoặc dựa trên.

$ complex_message() {
  cat <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
}

$ echo "This is a $(complex_message)"
This is a abc'asdf"
$(dont-execute-this)
foo"bar"''

9

Một mảng là một biến, vì vậy trong trường hợp đó mapfile sẽ hoạt động

mapfile y <<z
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

Sau đó, bạn có thể in như thế này

printf %s "${y[@]}"

3

gán giá trị heredoc cho một biến

VAR="$(cat <<'VAREOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
VAREOF
)"

được sử dụng như là một đối số của một lệnh

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"

Khi tôi thử phương pháp đầu tiên, dường như không có dấu kết thúc dòng giữa các dòng. Phải là một loại cấu hình trên máy linux của tôi?
Kemin Zhou

Điều này có thể có nghĩa là khi bạn lặp lại biến của mình, bạn đã không đặt dấu ngoặc kép xung quanh nó ... Hãy thử như vậy:echo "$VAR"
Brad

1

Tôi thấy mình phải đọc một chuỗi có NULL trong đó, vì vậy đây là một giải pháp sẽ đọc bất cứ điều gì bạn ném vào nó. Mặc dù nếu bạn thực sự đang giao dịch với NULL, bạn sẽ cần phải xử lý vấn đề đó ở cấp độ hex.

$ mèo> đọc.dd.sh

read.dd() {
     buf= 
     while read; do
        buf+=$REPLY
     done < <( dd bs=1 2>/dev/null | xxd -p )

     printf -v REPLY '%b' $( sed 's/../ \\\x&/g' <<< $buf )
}

Bằng chứng:

$ . read.dd.sh
$ read.dd < read.dd.sh
$ echo -n "$REPLY" > read.dd.sh.copy
$ diff read.dd.sh read.dd.sh.copy || echo "File are different"
$ 

Ví dụ HEREDOC (với ^ J, ^ M, ^ I):

$ read.dd <<'HEREDOC'
>       (TAB)
>       (SPACES)
(^J)^M(^M)
> DONE
>
> HEREDOC

$ declare -p REPLY
declare -- REPLY="  (TAB)
      (SPACES)
(^M)
DONE

"

$ declare -p REPLY | xxd
0000000: 6465 636c 6172 6520 2d2d 2052 4550 4c59  declare -- REPLY
0000010: 3d22 0928 5441 4229 0a20 2020 2020 2028  =".(TAB).      (
0000020: 5350 4143 4553 290a 285e 4a29 0d28 5e4d  SPACES).(^J).(^M
0000030: 290a 444f 4e45 0a0a 220a                 ).DONE

0

Cảm ơn câu trả lời của dimo414 , điều này cho thấy giải pháp tuyệt vời của anh ấy hoạt động như thế nào và cho thấy rằng bạn cũng có thể có các trích dẫn và biến trong văn bản một cách dễ dàng:

đầu ra ví dụ

$ ./test.sh

The text from the example function is:
  Welcome dev: Would you "like" to know how many 'files' there are in /tmp?

  There are "      38" files in /tmp, according to the "wc" command

kiểm tra

#!/bin/bash

function text1()
{
  COUNT=$(\ls /tmp | wc -l)
cat <<EOF

  $1 Would you "like" to know how many 'files' there are in /tmp?

  There are "$COUNT" files in /tmp, according to the "wc" command

EOF
}

function main()
{
  OUT=$(text1 "Welcome dev:")
  echo "The text from the example function is: $OUT"
}

main

Sẽ rất thú vị khi xem một trích dẫn chưa từng có trong văn bản để xem cách nó xử lý điều đó. Có lẽ `Đừng sợ hãi, có các tệp" $ COUNT ", vì vậy dấu nháy đơn / trích dẫn có thể làm cho mọi thứ trở nên thú vị.
dragon788

-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
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.