Sự khác biệt giữa $ {var}, thời gian $ var và và $ $ var var trong vỏ Bash là gì?


133

Có gì tiêu đề nói: những gì nó có nghĩa là để đóng gói một biến trong {}, ""hoặc "{}?" Tôi đã không thể tìm thấy bất kỳ lời giải thích trực tuyến về điều này - tôi đã không thể giới thiệu cho họ ngoại trừ việc sử dụng các biểu tượng, mà không mang lại bất cứ điều gì.

Đây là một ví dụ:

declare -a groups

groups+=("CN=exampleexample,OU=exampleexample,OU=exampleexample,DC=example,DC=com")
groups+=("CN=example example,OU=example example,OU=example example,DC=example,DC=com")

Điều này:

for group in "${groups[@]}"; do
    echo $group
done

Chứng tỏ là khác nhiều so với điều này:

for group in $groups; do
    echo $group
done

và điều này:

for group in ${groups}; do
    echo $group
done

Chỉ có cái đầu tiên thực hiện những gì tôi muốn: lặp qua từng phần tử trong mảng. Tôi không thực sự rõ ràng về sự khác biệt giữa $groups, "$groups", ${groups}"${groups}". Nếu bất cứ ai có thể giải thích nó, tôi sẽ đánh giá cao nó.

Như một câu hỏi thêm - có ai biết cách được chấp nhận để đề cập đến các đóng gói này không?


Câu trả lời:


228

Niềng răng ( $varso với ${var})

Trong hầu hết các trường hợp, $var${var}đều giống nhau:

var=foo
echo $var
# foo
echo ${var}
# foo

Niềng răng chỉ cần thiết để giải quyết sự mơ hồ trong biểu thức:

var=foo
echo $varbar
# Prints nothing because there is no variable 'varbar'
echo ${var}bar
# foobar

Báo giá ( $varso "$var"với vs."${var}" )

Khi bạn thêm dấu ngoặc kép xung quanh một biến, bạn nói với shell để coi nó là một từ duy nhất, ngay cả khi nó chứa khoảng trắng:

var="foo bar"
for i in "$var"; do # Expands to 'for i in "foo bar"; do...'
    echo $i         #   so only runs the loop once
done
# foo bar

Tương phản hành vi đó với những điều sau đây:

var="foo bar"
for i in $var; do # Expands to 'for i in foo bar; do...'
    echo $i       #   so runs the loop twice, once for each argument
done
# foo
# bar

Như với $varso với ${var}, niềng răng chỉ cần thiết cho sự định hướng, ví dụ:

var="foo bar"
for i in "$varbar"; do # Expands to 'for i in ""; do...' since there is no
    echo $i            #   variable named 'varbar', so loop runs once and
done                   #   prints nothing (actually "")

var="foo bar"
for i in "${var}bar"; do # Expands to 'for i in "foo barbar"; do...'
    echo $i              #   so runs the loop once
done
# foo barbar

Lưu ý rằng "${var}bar"trong ví dụ thứ hai ở trên cũng có thể được viết "${var}"bar, trong trường hợp đó bạn không cần niềng răng nữa, tức là "$var"bar. Tuy nhiên, nếu bạn có nhiều trích dẫn trong chuỗi của mình, các hình thức thay thế này có thể khó đọc (và do đó khó duy trì). Trang này cung cấp một giới thiệu tốt để trích dẫn trong Bash.

Mảng ( $varso $var[@]với so với ${var[@]})

Bây giờ cho mảng của bạn. Theo hướng dẫn sử dụng bash :

Tham chiếu một biến mảng không có chỉ mục con tương đương với tham chiếu mảng có chỉ số 0.

Nói cách khác, nếu bạn không cung cấp chỉ mục [], bạn sẽ nhận được phần tử đầu tiên của mảng:

foo=(a b c)
echo $foo
# a

Điều này giống hệt như

foo=(a b c)
echo ${foo}
# a

Để có được tất cả các phần tử của một mảng, bạn cần sử dụng @làm chỉ mục, ví dụ ${foo[@]}. Các dấu ngoặc được yêu cầu với các mảng vì không có chúng, shell sẽ mở rộng $foophần đầu tiên, đưa ra phần tử đầu tiên của mảng theo sau là một chữ [@]:

foo=(a b c)
echo ${foo[@]}
# a b c
echo $foo[@]
# a[@]

Trang này là một giới thiệu tốt về mảng trong Bash.

Trích dẫn xem lại ( ${foo[@]}so với "${foo[@]}")

Bạn đã không hỏi về điều này nhưng đó là một sự khác biệt tinh tế rất tốt để biết về nó. Nếu các phần tử trong mảng của bạn có thể chứa khoảng trắng, bạn cần sử dụng dấu ngoặc kép để mỗi phần tử được coi là một "từ:" riêng biệt

foo=("the first" "the second")
for i in "${foo[@]}"; do # Expands to 'for i in "the first" "the second"; do...'
    echo $i              #   so the loop runs twice
done
# the first
# the second

Tương phản điều này với hành vi không có dấu ngoặc kép:

foo=("the first" "the second")
for i in ${foo[@]}; do # Expands to 'for i in the first the second; do...'
    echo $i            #   so the loop runs four times!
done
# the
# first
# the
# second

3
Có một trường hợp khác : ${var:?}, sẽ cung cấp lỗi khi biến không được đặt hoặc không được đặt. REF: github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyễn

4
@NamNguyen Nếu bạn muốn nói về các hình thức khác của việc mở rộng tham số , có ít nhất một chục hơn: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}, và vân vân. Tôi nghĩ rằng những điều đó nằm ngoài phạm vi của câu trả lời này.
ThisSuitIsBlackNot

Trên thực tế, các cuộc thảo luận về dấu ngoặc kép là loại không đầy đủ. Xem thêm stackoverflow.com/questions/10067266/
Mạnh

11

TL; DR

Tất cả các ví dụ bạn đưa ra là các biến thể của Mở rộng Bash Shell . Mở rộng xảy ra theo một thứ tự cụ thể, và một số có trường hợp sử dụng cụ thể.

Niềng răng như dấu phân cách mã thông báo

Các ${var}cú pháp được sử dụng chủ yếu cho phân chia ranh giới tokens mơ hồ. Ví dụ, hãy xem xét những điều sau đây:

$ var1=foo; var2=bar; var12=12
$ echo $var12
12
$ echo ${var1}2
foo2

Niềng răng trong Mở rộng Mảng

Các dấu ngoặc nhọn được yêu cầu để truy cập các phần tử của một mảng và cho các mở rộng đặc biệt khác . Ví dụ:

$ foo=(1 2 3)

# Returns first element only.
$ echo $foo
1

# Returns all array elements.
$ echo ${foo[*]}
1 2 3

# Returns number of elements in array.
$ echo ${#foo[*]}
3

Mã thông báo

Hầu hết các câu hỏi còn lại của bạn đều liên quan đến việc trích dẫn và cách trình bày mã hóa đầu vào. Xem xét sự khác biệt trong cách trình bao thực hiện phân tách từ trong các ví dụ sau:

$ var1=foo; var2=bar; count_params () { echo $#; }

# Variables are interpolated into a single string.
$ count_params "$var1 $var2"
1

# Each variable is quoted separately, created two arguments.
$ count_params "$var1" "$var2"
2

Các @biểu tượng tương tác với trích dẫn khác nhau hơn *. Đặc biệt:

  1. $@ "[e] xpands cho các tham số vị trí, bắt đầu từ một. Khi mở rộng xảy ra trong dấu ngoặc kép, mỗi tham số sẽ mở rộng thành một từ riêng biệt."
  2. Trong một mảng, "[i] f từ được trích dẫn kép, ${name[*]}mở rộng thành một từ duy nhất với giá trị của từng thành viên mảng được phân tách bằng ký tự đầu tiên của biến IFS và ${name[@]}mở rộng từng thành phần tên thành một từ riêng biệt."

Bạn có thể thấy điều này trong hành động như sau:

$ count_params () { echo $#; }
$ set -- foo bar baz 

$ count_params "$@"
3

$ count_params "$*"
1

Việc sử dụng một phần mở rộng được trích dẫn rất quan trọng khi các biến tham chiếu đến các giá trị có khoảng trắng hoặc ký tự đặc biệt có thể ngăn vỏ từ chia tách theo cách bạn dự định. Xem trích dẫn để biết thêm về cách trích dẫn hoạt động trong Bash.


7

Bạn cần phân biệt giữa các mảng và các biến đơn giản - và ví dụ của bạn đang sử dụng một mảng.

Đối với các biến đơn giản:

  • $var${var}hoàn toàn tương đương.
  • "$var""${var}"hoàn toàn tương đương.

Tuy nhiên, hai cặp không giống nhau 100% trong mọi trường hợp. Hãy xem xét đầu ra dưới đây:

$ var="  abc  def  "
$ printf "X%sX\n" $var
XabcX
XdefX
$ printf "X%sX\n" "${var}"
X  abc  def  X
$

Không có dấu ngoặc kép xung quanh biến, khoảng cách bên trong sẽ bị mất và việc mở rộng được coi là hai đối số của printflệnh. Với các dấu ngoặc kép xung quanh biến, khoảng cách bên trong được giữ nguyên và việc mở rộng được coi là một đối số cho printflệnh.

Với mảng, các quy tắc đều giống nhau và khác nhau.

  • Nếu groupslà một mảng, tham chiếu $groupshoặc ${groups}tương đương với tham chiếu ${groups[0]}, phần tử zeroth của mảng.
  • Tham chiếu "${groups[@]}"tương tự như tham chiếu"$@" ; nó bảo tồn khoảng cách trong các phần tử riêng lẻ của mảng và trả về một danh sách các giá trị, một giá trị cho mỗi phần tử của mảng.
  • Tham chiếu ${groups[@]}không có dấu ngoặc kép sẽ không bảo toàn khoảng cách và có thể giới thiệu nhiều giá trị hơn các phần tử trong mảng nếu một số phần tử chứa khoảng trắng.

Ví dụ:

$ groups=("abc def" "  pqr  xyz  ")
$ printf "X%sX\n" ${groups[@]}
XabcX
XdefX
XpqrX
XxyzX
$ printf "X%sX\n" "${groups[@]}"
Xabc defX
X  pqr  xyz  X
$ printf "X%sX\n" $groups
XabcX
XdefX
$ printf "X%sX\n" "$groups"
Xabc defX
$

Sử dụng *thay vì @dẫn đến kết quả tinh tế khác nhau.

Xem thêm Cách lặp lại các đối số trong một bashtập lệnh .


3

Câu thứ hai của đoạn đầu tiên dưới Parameter Expansion trong man bashnói,

Tên tham số hoặc ký hiệu được mở rộng có thể được đặt trong dấu ngoặc nhọn, là tùy chọn nhưng phục vụ để bảo vệ biến được mở rộng khỏi các ký tự ngay sau ký tự có thể được hiểu là một phần của tên.

Điều này cho bạn biết rằng tên chỉ đơn giản là niềng răng , và mục đích chính là để làm rõ nơi tên bắt đầu và kết thúc:

foo='bar'
echo "$foobar"
# nothing
echo "${foo}bar"
barbar

Nếu bạn đọc thêm bạn khám phá,

Niềng răng được yêu cầu khi tham số là tham số vị trí có nhiều hơn một chữ số

Hãy thử nghiệm:

$ set -- {0..100}
$ echo $22
12
$ echo ${22}
20

Huh. Khéo léo. Tôi thực sự không biết rằng trước khi viết điều này (tôi chưa bao giờ có nhiều hơn 9 tham số vị trí trước đó.)

Tất nhiên, bạn cũng cần niềng răng để thực hiện các tính năng mở rộng tham số mạnh mẽ như

${parameter:-word}
${parameter:=word}
${parameter:?word}
 [read the section for more]

cũng như mở rộng mảng.


3

Một trường hợp liên quan không được đề cập ở trên. Trích dẫn một biến trống dường như thay đổi mọi thứ cho test -n. Điều này được đưa ra cụ thể như một ví dụ trong infovăn bản cho coreutils, nhưng không thực sự giải thích:

16.3.4 String tests
-------------------

These options test string characteristics.  You may need to quote
STRING arguments for the shell.  For example:

     test -n "$V"

  The quotes here prevent the wrong arguments from being passed to
`test' if `$V' is empty or contains special characters.

Tôi muốn nghe giải thích chi tiết. Thử nghiệm của tôi xác nhận điều này và hiện tôi đang trích dẫn các biến của mình cho tất cả các thử nghiệm chuỗi, để tránh có -z-ntrả về cùng một kết quả.

$ unset a
$ if [ -z $a ]; then echo unset; else echo set; fi
unset
$ if [ -n $a ]; then echo set; else echo unset; fi    
set                                                   # highly unexpected!

$ unset a
$ if [ -z "$a" ]; then echo unset; else echo set; fi
unset
$ if [ -n "$a" ]; then echo set; else echo unset; fi
unset                                                 # much better

2

Chà, tôi biết rằng việc đóng gói một biến giúp bạn làm việc với một cái gì đó như:

${groups%example}

hoặc cú pháp như thế, nơi bạn muốn làm gì đó với biến của mình trước khi trả về giá trị.

Bây giờ, nếu bạn thấy mã của mình, tất cả các phép thuật đều ở bên trong

${groups[@]}

phép màu ở đó vì bạn không thể viết: $groups[@]

Bạn đang đặt biến của mình vào bên trong {}vì bạn muốn sử dụng các ký tự đặc biệt []@. Bạn không thể đặt tên hoặc gọi biến của mình chỉ: @hoặc something[]vì đây là các ký tự dành riêng cho các hoạt động và tên khác.


Điều này không chỉ ra ý nghĩa rất quan trọng của dấu ngoặc kép và cách mã mà không có chúng về cơ bản bị phá vỡ.
tripleee
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.