Lợi ích của việc sử dụng $ () thay vì backticks trong shell script là gì?


175

Có hai cách để nắm bắt đầu ra của dòng lệnh trong bash:

  1. Backticks vỏ di sản Bourne ``:

    var=`command`
  2. $() cú pháp (theo như tôi biết là đặc thù của Bash, hoặc ít nhất là không được hỗ trợ bởi các shell cũ không phải POSIX như Bourne gốc)

    var=$(command)

Có bất kỳ lợi ích nào khi sử dụng cú pháp thứ hai so với backticks không? Hoặc là hai hoàn toàn tương đương 100%?


14
$()là POSIX và được hỗ trợ bởi tất cả các shell Bourne hiện đại, ví dụ: ksh, bash, ash, dash, zsh, busybox, bạn đặt tên cho nó. (Một thứ không quá hiện đại là Solaris /bin/sh, nhưng trên Solaris bạn sẽ đảm bảo sử dụng hiện đại /usr/xpg4/bin/shthay thế).
Jens

27
Ngoài ra, một lưu ý về việc sử dụng $()và backticks trong bí danh. Nếu bạn có alias foo=$(command)trong .bashrcđó thì commandsẽ được thực thi khi lệnh bí danh được chạy trong khi .bashrcgiải thích. Với alias foo=`command`, commandsẽ được thực hiện mỗi khi bí danh được chạy. Nhưng nếu bạn thoát khỏi $với các $()hình thức (ví dụ alias foo=\$(command)), nó cũng sẽ thực hiện mỗi lần bí danh là chạy, thay vì trong .bashrcgiải thích. Theo như tôi có thể nói bằng cách thử nghiệm, dù sao đi nữa; Tôi không thể tìm thấy bất cứ điều gì trong các tài liệu bash giải thích hành vi này.
dirtside

4
@dirtside Đây là shell nào, tôi đã thử bash và POSIX shell, backtick sẽ được thực thi khi tôi lấy nguồn. Ví dụ đơn giản: alias curDate = `date` Sau khi tôi lấy nguồn và chạy curDate, sau đó tôi nhận được thông báo, nó không thể tìm thấy lệnh Mon (Sourced vào thứ Hai), ví dụ.
thecarpy

@dirtside Điều đó không đúng. Ngay cả với bí danh foo = chỉ `command` commandđược thực hiện một lần. Tôi đã kiểm tra nó: function aaa () {printf date; tiếng vang aaa >> ~ / test.txt; } bí danh test1 = aaa. Hàm aaa chỉ thực hiện một lần (sau mỗi lần đăng nhập) bất kể bao nhiêu lần bí danh ( test1) được thực thi. Tôi đã sử dụng .bashrc (trên Debian 10).
Vitaliydev

Không có ý tưởng, đó là bất cứ phiên bản bash nào tôi đã sử dụng năm năm rưỡi trước, có lẽ đã khá cũ ngay cả vào thời điểm đó. Có thể thử nghiệm của tôi không chính xác, nhưng hiện tại hầu như không có vấn đề gì vì tôi nghi ngờ bất kỳ ai vẫn đang sử dụng bất kỳ phiên bản nào.
dirtside

Câu trả lời:


156

Cái chính là khả năng lồng chúng, ra lệnh trong các lệnh, mà không mất đi sự tỉnh táo của bạn khi cố gắng tìm hiểu xem một số hình thức thoát sẽ hoạt động trên backticks.

Một ví dụ, mặc dù hơi giả tạo:

deps=$(find /dir -name $(ls -1tr 201112[0-9][0-9]*.txt | tail -1l) -print)

sẽ cung cấp cho bạn một danh sách tất cả các tệp trong /dircây thư mục có cùng tên với tệp văn bản sớm nhất từ ​​tháng 12 năm 2011 (a) .

Một ví dụ khác sẽ giống như lấy tên (không phải đường dẫn đầy đủ) của thư mục mẹ:

pax> cd /home/pax/xyzzy/plugh
pax> parent=$(basename $(dirname $PWD))
pax> echo $parent
xyzzy

(a) Bây giờ lệnh cụ thể đó có thể không thực sự hoạt động, tôi đã không kiểm tra chức năng. Vì vậy, nếu bạn bỏ phiếu cho tôi, bạn đã đánh mất ý định :-) Nó có ý nghĩa như một minh họa về cách bạn có thể lồng, chứ không phải là một đoạn trích sẵn sàng sản xuất không có lỗi.


86
Tôi hy vọng tất cả các mã trên SO sẽ là các đoạn mã sẵn sàng sản xuất, được thiết kế theo tiêu chuẩn độ tin cậy mã của tàu con thoi của NASA. Bất cứ điều gì ít hơn được một cờ và bỏ phiếu xóa.
DVK

1
@DVK Trong trường hợp bạn không nói đùa, tôi không đồng ý rằng các đóng góp mã phải được gắn cờ vì không tự động gửi lại từ các mặc định SO (giấy phép CC-BY-SA hoặc MIT) để cho phép các bảo hành hoặc mục đích đó. Thay vào đó, tôi sẽ sử dụng lại mã trên SO với rủi ro của riêng mình và bỏ phiếu đóng góp theo mức độ hữu ích, giá trị kỹ thuật, v.v.
chrstphrchvz

9
@chrstphrchvz, nếu bạn nhìn vào hồ sơ của tôi, bạn sẽ thấy đoạn trích nhỏ này: "Tất cả mã tôi đăng trên Stack Overflow được bao phủ bởi giấy phép" Làm bất cứ điều gì bạn muốn với nó ", toàn văn là: Do whatever the heck you want with it." :-) Trong mọi trường hợp, tôi khá chắc chắn đó là sự hài hước từ DVK.
paxdiablo

1
Nhưng nếu đó là "cd / home / pax / xyzzy / plover" thì sao? Bạn có thấy mình trong một mê cung của những đoạn nhỏ ngoằn ngoèo, tất cả đều khác nhau không?
Duncan

@wchargein bạn có biết rằng sự cố này là một động lực chính cho việc bổ sung các nghĩa đen được sử dụng vào ngôn ngữ C ++ không? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod

59

Giả sử bạn muốn tìm thư mục lib tương ứng với nơi gccđược cài đặt. Bạn có một sự lựa chọn:

libdir=$(dirname $(dirname $(which gcc)))/lib

libdir=`dirname \`dirname \\\`which gcc\\\`\``/lib

Cái thứ nhất dễ hơn cái thứ hai - sử dụng cái thứ nhất.


4
Sẽ thật tốt khi thấy một số trích dẫn xung quanh những thay thế lệnh đó!
Tom Fenech

1
Ít nhất là đối với bash, nhận xét của @TomFenech không áp dụng cho các bài tập. x=$(f); x=`f` cư xử giống như x="$(f)"; x="`f`". Ngược lại, chuyển nhượng mảng x=($(f)); x=(`f`) làm thực hiện chia tách vào $IFSnhân vật như mong đợi khi gọi lệnh. Điều này là thuận tiện ( x=1 2 3 4không có ý nghĩa) nhưng không nhất quán.
kdb

@kdb bạn nói đúng về công x=$(f)việc không có dấu ngoặc kép. Tôi nên đã được cụ thể hơn; Tôi đã đề xuất sử dụng libdir=$(dirname "$(dirname "$(which gcc)")")/lib(trích dẫn xung quanh thay thế lệnh bên trong ). Nếu không được trích dẫn, bạn vẫn phải chịu sự chia tách từ thông thường và mở rộng toàn cầu.
Tom Fenech

38

Backticks ( `...`) là cú pháp kế thừa được yêu cầu chỉ bởi những vỏ bourne không tương thích POSIX lâu đời nhất và $(...)là POSIX và được ưa thích hơn vì nhiều lý do:

  • Dấu gạch chéo ngược ( \) bên trong backticks được xử lý theo cách không rõ ràng:

    $ echo "`echo \\a`" "$(echo \\a)"
    a \a
    $ echo "`echo \\\\a`" "$(echo \\\\a)"
    \a \\a
    # Note that this is true for *single quotes* too!
    $ foo=`echo '\\'`; bar=$(echo '\\'); echo "foo is $foo, bar is $bar" 
    foo is \, bar is \\
  • Báo giá lồng nhau bên trong $()là thuận tiện hơn nhiều:

    echo "x is $(sed ... <<<"$y")"

    thay vì:

    echo "x is `sed ... <<<\"$y\"`"

    hoặc viết một cái gì đó như:

    IPs_inna_string=`awk "/\`cat /etc/myname\`/"'{print $1}' /etc/hosts`

    bởi vì $()sử dụng một bối cảnh hoàn toàn mới để trích dẫn

    vốn không có khả năng di động vì đạn Bourne và Korn sẽ yêu cầu các dấu gạch chéo ngược này, trong khi Bash và dash thì không.

  • Cú pháp để thay thế lệnh lồng nhau dễ dàng hơn:

    x=$(grep "$(dirname "$path")" file)

    hơn:

    x=`grep "\`dirname \"$path\"\`" file`

    bởi vì $() thực thi một bối cảnh hoàn toàn mới để trích dẫn, do đó, mỗi thay thế lệnh được bảo vệ và có thể được xử lý riêng mà không cần quan tâm đặc biệt đến trích dẫn và thoát. Khi sử dụng backticks, nó trở nên xấu hơn và xấu hơn sau hai cấp độ trở lên.

    Một vài ví dụ khác:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • Nó giải quyết vấn đề về hành vi không nhất quán khi sử dụng backquote:

    • echo '\$x' đầu ra \$x
    • echo `echo '\$x'` đầu ra $x
    • echo $(echo '\$x') đầu ra \$x
  • Cú pháp Backticks có các hạn chế lịch sử đối với nội dung của lệnh nhúng và không thể xử lý một số tập lệnh hợp lệ bao gồm backquote, trong khi $()biểu mẫu mới hơn có thể xử lý bất kỳ loại tập lệnh nhúng hợp lệ nào.

    Ví dụ: các tập lệnh nhúng hợp lệ này không hoạt động ở cột bên trái, nhưng hoạt động ở bên phải IEEE :

    echo `                         echo $(
    cat <<\eof                     cat <<\eof
    a here-doc with `              a here-doc with )
    eof                            eof
    `                              )
    
    
    echo `                         echo $(
    echo abc # a comment with `    echo abc # a comment with )
    `                              )
    
    
    echo `                         echo $(
    echo '`'                       echo ')'
    `                              )

Do đó, cú pháp thay thế lệnh$ -prefixed nên là phương thức ưa thích, vì nó rõ ràng với cú pháp rõ ràng (cải thiện khả năng đọc của người và máy), nó có thể lồng vào nhau và trực quan, phân tích cú pháp bên trong của nó là riêng biệt và cũng phù hợp hơn (với tất cả các bản mở rộng khác được phân tích cú pháp từ trong dấu ngoặc kép) trong đó backticks là ngoại lệ duy nhất và ký tự dễ dàng được ngụy trang khi liền kề để làm cho nó khó đọc hơn, đặc biệt là với các phông chữ nhỏ hoặc bất thường.`"

Nguồn: Tại sao được $(...)ưa thích hơn `...`(backticks)? tại BashFAQ

Xem thêm:


1
Lồng nhau trích dẫn trong giọng gravis kiểu thay thực sự là không xác định, bạn có thể sử dụng dấu ngoặc kép hoặc bên ngoài hoặc bên trong nhưng không phải cả hai, portably. Shell giải thích chúng khác nhau; một số yêu cầu dấu gạch chéo ngược để thoát khỏi chúng, một số yêu cầu chúng không được thoát dấu gạch chéo ngược.
mirabilos

23

Từ người đàn ông bash:

          $(command)
   or
          `command`

   Bash performs the expansion by executing command and replacing the com-
   mand  substitution  with  the  standard output of the command, with any
   trailing newlines deleted.  Embedded newlines are not deleted, but they
   may  be  removed during word splitting.  The command substitution $(cat
   file) can be replaced by the equivalent but faster $(< file).

   When the old-style backquote form of substitution  is  used,  backslash
   retains  its  literal  meaning except when followed by $, `, or \.  The
   first backquote not preceded by a backslash terminates the command sub-
   stitution.   When using the $(command) form, all characters between the
   parentheses make up the command; none are treated specially.

9

Ngoài các câu trả lời khác,

$(...)

trực quan tốt hơn

`...`

Backticks trông quá giống như dấu nháy đơn; điều này thay đổi tùy thuộc vào phông chữ bạn đang sử dụng.

(Và, như tôi vừa nhận thấy, backticks khó nhập hơn rất nhiều trong các mẫu mã nội tuyến.)


2
bạn phải có một bàn phím kỳ lạ (hoặc tôi làm?). Đối với tôi, việc nhập backticks dễ dàng hơn nhiều - chúng là phím góc trên cùng bên trái, không cần SHIFT hoặc ALT.
DVK

1
@DVK: Tôi đã nói về sự xuất hiện của họ, không dễ gõ. (Bàn phím của tôi có thể giống với bàn phím của bạn.) Tuy nhiên, bây giờ khi bạn đề cập đến nó, tôi nghĩ rằng tôi có bộ nhớ cơ tốt hơn $ ()hơn tôi làm cho backtick; YMMV.
Keith Thompson

không bao giờ được lập trình trong bash (được chuyển từ ksh cũ sang Perl) vì vậy chắc chắn không có bộ nhớ cho cú pháp cụ thể đó :)
DVK

1
@DVK, tôi nghĩ Keith đã đề cập đến thực tế là mã không chặn ở đây (mã khối có nghĩa là sử dụng bốn khoảng trắng ở đầu dòng) sử dụng backticks để chỉ ra nó, gây khó khăn cho việc đặt backticks vào chúng, một minh họa khác về Khó khăn lồng nhau :-) FWIW, bạn có thể tìm thấy mã và / thẻ mã (cách khác để thực hiện mã không chặn) có thể dễ dàng chứa backticks hơn.
paxdiablo

@Pax - hiểu rồi Tât nhiên! Tôi thực sự bị mắc kẹt về tinh thần trên các mã khối vì một số lý do.
DVK

7

$() cho phép làm tổ

out=$(echo today is $(date))

Tôi nghĩ backticks không cho phép nó.


2
Bạn có thể lồng backticks; nó khó hơn nhiều : out=`echo today is \`date\`` .
Jonathan Leffler

4

Đó là tiêu chuẩn POSIX xác định $(command)hình thức thay thế lệnh. Hầu hết các shell được sử dụng ngày nay đều tuân thủ POSIX và hỗ trợ hình thức ưa thích này qua ký hiệu backtick cổ xưa. Phần thay thế lệnh (2.6.3) của tài liệu Shell Language mô tả điều này:

Thay thế lệnh cho phép đầu ra của một lệnh được thay thế thay cho tên lệnh. Thay thế lệnh sẽ xảy ra khi lệnh được bao quanh như sau:

$(command)

hoặc (phiên bản rút gọn):

`command`

Shell sẽ mở rộng thay thế lệnh bằng cách thực thi lệnh trong môi trường lớp con (xem Môi trường thực thi Shell ) và thay thế lệnh thay thế (văn bản lệnh cộng với "$ ()" hoặc backquote kèm theo với đầu ra tiêu chuẩn của lệnh, loại bỏ trình tự của một hoặc nhiều <newline>ký tự ở cuối thay thế. Các <newline>ký tự nhúng trước khi kết thúc đầu ra sẽ không bị xóa; tuy nhiên, chúng có thể được coi là các dấu phân cách trường và bị loại bỏ trong quá trình tách trường, tùy thuộc vào giá trị của IFS và trích dẫn có hiệu lực. Nếu đầu ra chứa bất kỳ byte rỗng nào, hành vi không được chỉ định.

Trong kiểu thay thế lệnh <backslash>được trích dẫn , sẽ giữ nguyên nghĩa đen của nó, ngoại trừ khi được theo sau: ' $', ' `' hoặc <backslash>. Việc tìm kiếm backquote phù hợp sẽ được thỏa mãn bởi backquote không thoát được trích dẫn đầu tiên; trong quá trình tìm kiếm này, nếu gặp phải một backquote không thoát trong một nhận xét shell, tài liệu ở đây, thay thế lệnh nhúng của biểu mẫu $ ( lệnh ) hoặc chuỗi được trích dẫn, sẽ xảy ra kết quả không xác định. Một chuỗi trích dẫn đơn hoặc trích dẫn kép bắt đầu, nhưng không kết thúc, trong `...`chuỗi "" tạo ra kết quả không xác định.

Với biểu mẫu $ ( lệnh ), tất cả các ký tự theo dấu ngoặc đơn mở cho dấu ngoặc đơn đóng phù hợp sẽ tạo thành lệnh . Bất kỳ tập lệnh shell hợp lệ nào cũng có thể được sử dụng cho lệnh , ngoại trừ tập lệnh chỉ bao gồm các chuyển hướng tạo ra kết quả không xác định.

Các kết quả thay thế lệnh sẽ không được xử lý để mở rộng dấu ngã, mở rộng tham số, thay thế lệnh hoặc mở rộng số học. Nếu thay thế lệnh xảy ra bên trong dấu ngoặc kép, việc tách trường và mở rộng tên đường dẫn sẽ không được thực hiện trên kết quả của sự thay thế.

Thay thế lệnh có thể được lồng nhau. Để chỉ định lồng trong phiên bản backquote, ứng dụng phải đi trước các backquote bên trong với các <backslash>ký tự; ví dụ:

\`command\`

Cú pháp của ngôn ngữ lệnh shell có sự mơ hồ đối với các mở rộng bắt đầu bằng "$((. thay thế nếu nó xác định rằng nó không thể phân tích sự mở rộng như một sự mở rộng số học. Vỏ không cần đánh giá các mở rộng lồng nhau khi thực hiện xác định này. shell sẽ coi việc mở rộng là một mở rộng số học không hoàn chỉnh và báo cáo lỗi cú pháp. Một ứng dụng tuân thủ phải đảm bảo rằng nó tách biệt "$( " và '('thành hai mã thông báo (nghĩa là tách chúng bằng khoảng trắng) trong một thay thế lệnh bắt đầu bằng một lớp con. Ví dụ, một sự thay thế lệnh có chứa một chuỗi con có thể được viết là:

$( (command) )


4

Đây là một câu hỏi di sản, nhưng tôi đã đưa ra một ví dụ hoàn toàn hợp lệ về $(...)hơn `...`.

Tôi đã sử dụng một máy tính để bàn từ xa để các cửa sổ chạy cygwin và muốn lặp lại kết quả của một lệnh. Đáng buồn thay, nhân vật backtick không thể nhập, do điều máy tính từ xa hoặc chính cygwin.

Thật giả khi cho rằng một ký hiệu đô la và dấu ngoặc đơn sẽ dễ dàng nhập vào các thiết lập lạ như vậy.

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.