Thay thế gấp đôi và gấp ba trong bash và zsh


22

Theo dõi phần nền trong câu hỏi này .

Trong bashtôi có thể sử dụng ${!FOO}để thay thế kép, trong zsh ${(P)FOO}. Trong cả hai, trường học cũ (hack-y) eval \$$FOOhoạt động.

Vì vậy, điều thông minh và hợp lý nhất đối với tôi sẽ là ${${FOO}}, ${${${FOO}}}…thay thế gấp đôi / gấp ba / n. Tại sao công việc này không như mong đợi?

Thứ hai: Những gì \làm trong evaltuyên bố? Tôi nghĩ đó là một lối thoát, làm cho một cái gì đó như eval \$$$FOOkhông thể. Làm thế nào để thay thế triple / n với cái đó hoạt động trong mọi shell?

Câu trả lời:


15

Các \phải được sử dụng để ngăn chặn sự bành trướng của $$(hiện quá trình id). Để thay thế ba lần, bạn cần gấp đôi eval, do đó cũng có nhiều lối thoát hơn để tránh các mở rộng không mong muốn trong mỗi eval:

#! /bin/bash
l0=value
l1=l0
l2=l1
l3=l2
l4=l3

echo $l0
eval echo \$$l1
eval eval echo \\$\$$l2
eval eval eval echo \\\\$\\$\$$l3
eval eval eval eval echo \\\\\\\\$\\\\$\\$\$$l4

Tuy nhiên: l3=l2; eval eval eval echo \\\$\\$\$$l353294vì vậy không chính xác mô-đun.
Profpatsch

2
Tại sao tôi không thể làm một cái gì đó như thế eval $(eval echo \$$l2)nào? Tôi ghét bash, nó hoàn toàn không có ý nghĩa.
Profpatsch

@Profpatsch: Đã thêm ví dụ.
choroba

À, đó là n². Tôi thậm chí không muốn cố gắng hiểu tại sao nó như thế này.
Profpatsch

2
@Profpatsch: Dễ thôi. Trong mỗi bước, số lượng dấu gạch chéo ngược giảm một nửa (thực tế là 2ⁿ).
choroba


6

Giả sử giá trị của FOOlà một tên biến hợp lệ (giả sử BAR), eval \$$FOOchia giá trị BARthành các từ riêng biệt, coi mỗi từ là một mẫu ký tự đại diện và thực hiện từ đầu tiên của kết quả dưới dạng một lệnh, chuyển các từ khác thành đối số. Dấu gạch chéo ngược phía trước đồng đô la làm cho nó được xử lý theo nghĩa đen, do đó, đối số được truyền cho evalnội dung là chuỗi bốn ký tự$BAR .

${${FOO}}là một lỗi cú pháp. Nó không thực hiện thay thế hai lần vì một vì không có tính năng như vậy trong bất kỳ hệ vỏ thông thường nào (không phải với cú pháp này). Trong zsh, ${${FOO}} hợp lệ và là sự thay thế kép, nhưng nó hoạt động khác với những gì bạn thích: nó thực hiện hai phép biến đổi liên tiếp trên giá trị của FOO, cả hai đều là phép biến đổi danh tính, vì vậy nó chỉ là một cách viết lạ mắt ${FOO}.

Nếu bạn muốn coi giá trị của một biến là một biến, hãy cẩn thận khi trích dẫn mọi thứ đúng cách. Sẽ dễ dàng hơn rất nhiều nếu bạn đặt kết quả thành một biến:

eval "value=\${$FOO}"

1
Đặt nó là biến, sao cho từ đầu tiên không được sử dụng làm lệnh? Man, loại logic này là khó nắm bắt. Đó là vấn đề chung mà tôi dường như gặp phải với bash.
Profpatsch

4

{ba,z}sh dung dịch

Đây là một chức năng hoạt động trong cả hai {ba,z}sh. Tôi tin rằng nó cũng tuân thủ POSIX.

Không cần phát điên với trích dẫn, bạn có thể sử dụng nó cho nhiều cấp độ gián tiếp như vậy:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

Hoặc nếu bạn có nhiều cấp độ (!?!), Bạn có thể sử dụng một vòng lặp.

Nó cảnh báo khi được đưa ra:

  • Đầu vào không
  • Một tên biến không được đặt

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
var_expand() {
  if [ -z "${1-}" ] || [ $# -ne 1 ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

Một giải pháp xuyên vỏ làm việc cho tôi. Hy vọng rằng "mở rộng biến kép kép tương thích POSIX" sẽ chuyển hướng đến câu trả lời này trong tương lai.
BlueDrink9

2

Cũng giống như ${$foo}không làm việc thay thế ${(P)foo}trong zsh, cũng không ${${$foo}}. Bạn chỉ cần xác định từng cấp độ gián tiếp:

$ foo=bar
$ bar=baz
$ baz=3
$ echo $foo
bar
$ echo ${(P)foo}
baz
$ echo ${(P)${(P)foo}}
3

Tất nhiên, ${!${!foo}}không hoạt động bash, bởi vì bashkhông cho phép thay thế lồng nhau.


Cảm ơn, đó là điều tốt để biết. Thật xấu hổ vì đây chỉ là zsh, vì vậy bạn thực sự không thể đưa nó vào một kịch bản (mọi người đều có bash, nhưng nó không phải là cách khác).
Profpatsch

Tôi nghi ngờ zshđược cài đặt thường xuyên hơn nhiều so với bạn có thể nghĩ. Nó có thể không được liên kết shnhư bashthường (nhưng không phải luôn luôn), nhưng nó vẫn có thể có sẵn cho các kịch bản shell. Hãy nghĩ về nó giống như bạn làm Perl hoặc Python.
giữ trẻ

2

Tại sao bạn cần phải làm điều đó?

Bạn luôn có thể làm điều đó trong một số bước như:

eval "l1=\${$var}"
eval "l2=\${$l1}"
...

Hoặc sử dụng một chức năng như:

deref() {
  if [ "$1" -le 0 ]; then
    eval "$3=\$2"
  else
    eval "deref $(($1 - 1)) \"\${$2}\" \"\$3\""
  fi
}

Sau đó:

$ a=b b=c c=d d=e e=blah
$ deref 3 a res; echo "$res"
d
$ deref 5 a res; echo "$res"
blah

FWIW, trong zsh:

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah

Huh, sử dụng một số bước không xảy ra với tôi cho đến bây giờ, kỳ lạ.
Profpatsch

Rõ ràng, theo quan điểm của tôi, tại sao @Profpatsch muốn làm điều đó . Bởi vì đó là cách trực quan nhất để làm điều đó. Là khó khăn để thực hiện nhiều thay trong bash là chuyện khác.
Nikos Alexandris

2

Giải pháp POSIX

Không cần phát điên với trích dẫn, bạn có thể sử dụng chức năng này cho nhiều cấp độ gián tiếp như vậy:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

Hoặc nếu bạn có nhiều cấp độ (!?!), Bạn có thể sử dụng một vòng lặp.

Nó cảnh báo khi được đưa ra:

  • Đầu vào không
  • Nhiều hơn một đối số
  • Một tên biến không được đặt

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
function var_expand {
  if [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

Có một lý do nào khiến bạn không kết hợp câu trả lời này với câu hỏi trước không? Nhìn thoáng qua tôi không thấy sự khác biệt giữa chúng.
BlueDrink9
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.