Có gì sai với tiếng vang tiếng vang $ (thứ)


31

Tôi đã sử dụng một trong những điều sau đây

echo $(stuff)
echo `stuff`

(nơi stuffđược ví dụ pwdhoặc datehoặc một cái gì đó phức tạp hơn).

Sau đó, tôi đã nói cú pháp này là sai, một thực tiễn xấu, không thanh lịch, quá mức, dư thừa, quá phức tạp, một chương trình sùng bái hàng hóa, noobish, ngây thơ, vv

Nhưng lệnh không hoạt động, vậy chính xác thì nó có vấn đề gì?


11
Nó dư thừa và do đó không nên được sử dụng. So sánh với việc sử dụng mèo vô dụng: pigmail.org/era/unix/award.html
dr01

7
echo $(echo $(echo $(echo$(echo $(stuff)))))cũng không làm việc, và vẫn có thể bạn sẽ không sử dụng nó, phải không? ;-)
Peter - Tái lập Monica

1
Chính xác thì bạn đang cố gắng làm gì với bản mở rộng đó?
tò mò

@cquilguy Tôi đang cố gắng giúp đỡ những người dùng khác, vì vậy câu trả lời của tôi dưới đây. Gần đây, tôi đã thấy ít nhất ba câu hỏi sử dụng cú pháp này. Các tác giả của họ đã cố gắng làm khác nhau stuff. : D
Kamil Maciorowski

2
Ngoài ra, không phải tất cả chúng ta đều đồng ý rằng việc nhốt mình vào buồng dội lại là không khôn ngoan ?? ;-)
Peter - Tái lập Monica

Câu trả lời:


63

tl; dr

Một duy nhất stuffsẽ có lẽ hầu hết việc cho bạn.



Câu trả lời đầy đủ

Chuyện gì xảy ra

Khi bạn chạy foo $(stuff), đây là những gì xảy ra:

  1. stuff chạy;
  2. đầu ra của nó (stdout), thay vì được in, thay thế $(stuff)trong lời gọi của foo;
  3. sau đó foochạy, đối số dòng lệnh của nó rõ ràng phụ thuộc vào những gì được stufftrả về.

Đây $(…)cơ chế được gọi là "lệnh thay". Trong trường hợp của bạn, lệnh chính là echovề cơ bản in các đối số dòng lệnh của nó thành thiết bị xuất chuẩn. Vì vậy, bất cứ điều gì stuffcố gắng in ra thiết bị xuất chuẩn đều được ghi lại, chuyển đến echovà in ra thiết bị xuất chuẩn echo.

Nếu bạn muốn đầu ra của stuffđược in ra thiết bị xuất chuẩn, chỉ cần chạy đế stuff.

Các `…`cú pháp phục vụ cùng một mục đích như $(…)(dưới cái tên giống nhau: "lệnh thay"), có vài sự khác biệt mặc dù, vì vậy bạn không thể nhắm mắt trao đổi chúng. Xem Câu hỏi thường gặp nàycâu hỏi này .


Tôi có nên tránh echo $(stuff)không có vấn đề gì?

Có một lý do bạn có thể muốn sử dụng echo $(stuff)nếu bạn biết những gì bạn đang làm. Vì lý do tương tự, bạn nên tránh echo $(stuff)nếu bạn không thực sự biết những gì bạn đang làm.

Điểm này là stuffecho $(stuff)không chính xác tương đương. Cái sau có nghĩa là gọi toán tử split + global trên đầu ra stuffvới giá trị mặc định là $IFS. Trích dẫn kép thay thế lệnh ngăn chặn điều này. Trích dẫn đơn thay thế lệnh làm cho nó không còn là một thay thế lệnh.

Để quan sát điều này khi chia tách, hãy chạy các lệnh sau:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

Và cho toàn cầu:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

Như bạn có thể thấy echo "$(stuff)"là tương đương (-ish *) với stuff. Bạn có thể sử dụng nó nhưng điểm phức tạp của mọi thứ theo cách này là gì?

Mặt khác, nếu bạn muốn đầu ra stufftrải qua quá trình phân tách + tạo khối thì bạn có thể thấy echo $(stuff)hữu ích. Nó phải là quyết định có ý thức của bạn mặc dù.

Có các lệnh tạo đầu ra nên được đánh giá (bao gồm chia tách, tạo khối và nhiều hơn nữa) và được chạy bởi trình bao, do đó eval "$(stuff)"là một khả năng (xem câu trả lời này ). Tôi chưa bao giờ thấy một lệnh nào cần đầu ra của nó để trải qua quá trình phân tách + tạo khối bổ sung trước khi được in . Cố tình sử dụng echo $(stuff)có vẻ rất không phổ biến.


Thế còn var=$(stuff); echo "$var"?

Điểm tốt. Đoạn trích này:

var=$(stuff)
echo "$var"

nên tương đương với echo "$(stuff)"tương đương (-ish *) với stuff. Nếu đó là toàn bộ mã, chỉ cần chạy stuffthay thế.

Tuy nhiên, nếu bạn cần sử dụng đầu ra của stuffnhiều lần thì phương pháp này

var=$(stuff)
foo "$var"
bar "$var"

thường tốt hơn

foo "$(stuff)"
bar "$(stuff)"

Thậm chí nếu fooechovà bạn nhận được echo "$var"trong mã của bạn, nó có thể là tốt hơn để giữ nó theo cách này. Những điều cần cân nhắc:

  1. Với var=$(stuff) stuffchạy một lần; ngay cả khi lệnh nhanh, tránh tính toán cùng một đầu ra hai lần là điều đúng đắn. Hoặc có thể stuffcó các hiệu ứng khác ngoài việc ghi vào thiết bị xuất chuẩn (ví dụ: tạo tệp tạm thời, khởi động dịch vụ, khởi động máy ảo, thông báo cho máy chủ từ xa), vì vậy bạn không muốn chạy nó nhiều lần.
  2. Nếu stufftạo đầu ra phụ thuộc thời gian hoặc hơi ngẫu nhiên, bạn có thể nhận được kết quả không nhất quán từ foo "$(stuff)"bar "$(stuff)". Sau khi var=$(stuff)giá trị của $varđược cố định và bạn có thể chắc chắn foo "$var"bar "$var"nhận được đối số dòng lệnh giống hệt nhau.

Trong một số trường hợp thay vì foo "$var"bạn có thể muốn (cần) sử dụng foo $var, đặc biệt nếu stufftạo nhiều đối số cho foo(một biến mảng có thể tốt hơn nếu shell của bạn hỗ trợ nó). Một lần nữa, biết những gì bạn đang làm. Khi nói đến echosự khác biệt giữa echo $varecho "$var"giống như giữa echo $(stuff)echo "$(stuff)".


* Tương đương ( -ish )?

Tôi nói echo "$(stuff)"là tương đương (-ish) với stuff. Có ít nhất hai vấn đề khiến nó không tương đương chính xác:

  1. $(stuff)chạy stufftrong một subshell, vì vậy tốt hơn echo "$(stuff)"là nói tương đương (-ish) với (stuff). Các lệnh ảnh hưởng đến lớp vỏ mà chúng chạy trong, nếu trong lớp con, không ảnh hưởng đến lớp vỏ chính.

    Trong ví dụ stuffnày là a=1; echo "$a":

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    So sánh nó với

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    và với

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    Một ví dụ khác, bắt đầu với stuffviệc cd /; pwd:

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    và thử nghiệm stuff(stuff)các phiên bản.

  2. echokhông phải là một công cụ tốt để hiển thị dữ liệu không được kiểm soát . Điều này echo "$var"chúng ta đã nói về lẽ ra phải có printf '%s\n' "$var". Nhưng vì câu hỏi đề cập echovà vì giải pháp có thể xảy ra nhất là không sử dụng echongay từ đầu, nên tôi quyết định không giới thiệu printfcho đến bây giờ.

  3. stuffhoặc (stuff)sẽ xen kẽ đầu ra stdout và stderr, trong khi echo $(stuff)sẽ in tất cả đầu ra stderr từ stuff(chạy trước) và chỉ sau đó đầu ra stdout được tiêu hóa bởi echo(chạy cuối cùng).

  4. $(…)loại bỏ bất kỳ dòng mới nào và sau đó echothêm nó trở lại. Vì vậy, echo "$(printf %s 'a')" | xxdcho đầu ra khác nhau hơn printf %s 'a' | xxd.

  5. Một số lệnh ( lsví dụ) hoạt động khác nhau tùy thuộc vào việc đầu ra tiêu chuẩn có phải là bàn điều khiển hay không; vì thế ls | catkhông giống nhau lskhông. Tương tự echo $(ls)sẽ làm việc khác hơn ls.

    Đặt lssang một bên, trong trường hợp chung nếu bạn phải ép buộc hành vi khác này thì stuff | cattốt hơn echo $(ls)hoặc echo "$(ls)"bởi vì nó không kích hoạt tất cả các vấn đề khác được đề cập ở đây.

  6. Có thể trạng thái thoát khác nhau (được đề cập cho tính đầy đủ của câu trả lời wiki này; để biết chi tiết, hãy xem câu trả lời khác xứng đáng với tín dụng).


5
Một điểm khác biệt nữa: stuffcách thức sẽ xen kẽ stdoutstderrđầu ra, trong khi echo `stuff` sẽ in tất cả stderrđầu ra từ stuff, và chỉ sau đó là stdoutđầu ra.
Ruslan

3
Và đây là một ví dụ khác cho: Khó có thể có quá nhiều trích dẫn trong các tập lệnh bash. echo $(stuff)có thể khiến bạn gặp nhiều rắc rối hơn như echo "$(stuff)"thể bạn không muốn toàn cầu hóa và mở rộng.
rexkogitans

2
Thêm một điểm khác biệt nữa: $(…)loại bỏ bất kỳ dòng mới nào và sau đó echothêm lại. Vì vậy, echo "$(printf %s 'a')" | xxdcho đầu ra khác nhau hơn printf %s 'a' | xxd.
derobert

1
Bạn không nên quên rằng một số lệnh ( lsví dụ) hoạt động khác nhau tùy thuộc vào việc đầu ra tiêu chuẩn có phải là bàn điều khiển hay không; vì thế ls | catkhông giống nhau lskhông. Và tất nhiên echo $(ls)sẽ làm việc khác hơn ls.
Martin Rosenau

@Ruslan Câu trả lời bây giờ là wiki cộng đồng. Tôi đã thêm bình luận hữu ích của bạn vào wiki. Cảm ơn bạn.
Kamil Maciorowski

5

Một điểm khác biệt: Mã thoát của vỏ phụ bị mất, do đó, mã thoát của echođược lấy ra.

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0

3
Chào mừng bạn đến với Siêu người dùng! Bạn có thể giải thích sự khác biệt bạn đang thể hiện? Cảm ơn
bertieb

4
Tôi tin rằng điều này cho thấy mã trả về $?sẽ là mã trả về của echo(cho echo $(stuff)) thay vì mã returned bởi stuff. Các stuffchức năng return 1(exit code), nhưng echobộ nó thành "0" không phụ thuộc vào mã trở lại của stuff. Vẫn nên trong câu trả lời.
Ismael Miguel

giá trị trả về từ lớp con đã bị mất
phuclv
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.