Jaka jest korzyść z używania $ () zamiast znaków odwrotnych w skryptach powłoki?

175

Istnieją dwa sposoby przechwytywania danych wyjściowych wiersza poleceń w bash:

  1. Legacy backticks powłoki Bourne'a ``:

    var=`command`
  2. $() składnia (która, o ile wiem, jest specyficzna dla Bash lub przynajmniej nie jest obsługiwana przez stare powłoki niezgodne z POSIX, takie jak oryginalny Bourne)

    var=$(command)

Czy jest jakaś korzyść z używania drugiej składni w porównaniu do odwrotnych apostrofów? A może oba są w 100% równoważne?

DVK
źródło
14
$()jest POSIX i jest obsługiwany przez wszystkie współczesne powłoki Bourne'a, np. ksh, bash, ash, dash, zsh, busybox, możesz to nazwać. (Nie tak nowoczesny jest Solaris /bin/sh, ale w Solarisie powinieneś /usr/xpg4/bin/shzamiast tego użyć nowoczesnego ).
Jens
27
Ponadto uwaga na temat używania $()i odwrotnych apostrofów w aliasach. Jeśli masz alias foo=$(command)w swoim .bashrcto commandzostanie wykonane, gdy samo polecenie alias zostanie uruchomione podczas .bashrcinterpretacji. Z alias foo=`command`, commandzostanie wykonane za każdym razem, gdy alias zostanie uruchomiony. Ale jeśli uciekniesz $z $()formularza (np. alias foo=\$(command)), To również zostanie wykonane za każdym razem, gdy alias zostanie uruchomiony, zamiast podczas .bashrcinterpretacji. W każdym razie, o ile mogę stwierdzić na podstawie testów; Nie mogę znaleźć niczego w dokumentach bash, które wyjaśniają to zachowanie.
dirtside
4
@dirtside Która powłoka to ta, przetestowałem powłoki bash i POSIX, lewy przycisk jest wykonywany, gdy źródłuję. Prosty przykład: alias curDate = `date` Po tym, jak go pobiorę i uruchomię curDate, otrzymuję komunikat, że na przykład nie może znaleźć polecenia Mon (Źródło w poniedziałek).
thecarpy
@dirtside To nieprawda. Nawet z aliasem foo = `command` commandjest wykonywane tylko raz. Sprawdziłem to: function aaa () {printf date; echo aaa >> ~ / test.txt; } alias test1 = aaa. Funkcja aaa jest wykonywana tylko raz (po każdym logowaniu) bez względu na to, ile razy alias ( test1) był wykonywany. Użyłem .bashrc (w Debianie 10).
vitaliydev
Nie mam pojęcia, to była ta wersja basha, z której korzystałem pięć i pół roku temu, która prawdopodobnie była już dość stara. Możliwe, że moje testy były nieprawidłowe, ale teraz nie ma to większego znaczenia, ponieważ wątpię, czy ktokolwiek nadal używa tej wersji, która była.
dirtside

Odpowiedzi:

156

Najważniejszą z nich jest umiejętność zagnieżdżania ich, wydawania poleceń w ramach poleceń, bez utraty poczytalności, próbując dowiedzieć się, czy jakaś forma ucieczki zadziała na backticks.

Przykład, choć nieco wymyślony:

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

co da ci listę wszystkich plików w /dirdrzewie katalogów, które mają taką samą nazwę jak najwcześniejszy datowany plik tekstowy z grudnia 2011 (a) .

Innym przykładem może być pobranie nazwy (nie pełnej ścieżki) katalogu nadrzędnego:

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

(a) Teraz, gdy określone polecenie może nie działać, nie testowałem jego funkcjonalności. Tak więc, jeśli zagłosujesz na mnie za tym, straciłeś z oczu zamiar :-) Jest to tylko ilustracja tego, jak możesz zagnieżdżać, a nie jako wolny od błędów fragment gotowy do produkcji.

paxdiablo
źródło
87
Oczekuję, że cały kod w SO będą fragmentami gotowymi do produkcji, zaprojektowanymi zgodnie ze standardami niezawodności kodu NASA. Cokolwiek mniej dostaje flagę i głosowanie na usunięcie.
DVK,
1
@DVK Jeśli nie żartujesz, nie zgadzam się, że wkłady kodu powinny być oznaczane jako nieudane automatyczne ponowne udzielenie licencji z dala od domyślnych licencji SO (licencje CC-BY-SA lub MIT), aby umożliwić takie gwarancje lub przydatność do celu. Zamiast tego ponownie użyłbym kodu na SO na własne ryzyko i głosowałbym wkład w zależności od przydatności, zalet technicznych itp.
chrstphrchvz
9
@chrstphrchvz, jeśli spojrzysz na mój profil, zobaczysz ten mały fragment: „Cały kod, który publikuję w Stack Overflow jest objęty licencją„ Rób z nim co chcesz ”, której pełny tekst to: Do whatever the heck you want with it.” :-) W każdym razie jestem prawie pewien, że był to humor z DVK.
paxdiablo
1
ale co by było, gdyby był to „cd / home / pax / xyzzy / plover”? Czy znalazłeś się w labiryncie krętych małych przejść, z których każdy jest inny?
Duncan
@wchargin, czy wiesz, że ten incydent był głównym czynnikiem motywującym do dodania używanych zdefiniowanych literałów do języka C ++? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod
59

Załóżmy, że chcesz znaleźć katalog lib odpowiadający temu, gdzie gccjest zainstalowany. Masz wybór:

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

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

Pierwsza jest łatwiejsza niż druga - skorzystaj z pierwszej.

Jonathan Leffler
źródło
4
Byłoby dobrze zobaczyć kilka cudzysłowów wokół tych podstawień poleceń!
Tom Fenech
1
Przynajmniej w przypadku basha komentarz @TomFenech nie dotyczy zadań. x=$(f); x=`f` zachowują się tak samo jak x="$(f)"; x="`f`". Natomiast przypisanie tablicy x=($(f)); x=(`f`) zrobić wykonać podział na $IFSbohaterów jak spodziewanych podczas wywoływania polecenia. Jest to wygodne ( x=1 2 3 4nie ma sensu), ale niespójne.
kdb
@kdb masz rację co do x=$(f)pracy bez cudzysłowów. Powinienem był być bardziej szczegółowy; Proponowałem użycie libdir=$(dirname "$(dirname "$(which gcc)")")/lib(cudzysłowy wokół wewnętrznych podstawień poleceń). Jeśli pozostaniesz bez cytowania, nadal podlegasz zwykłemu podziałowi na słowa i rozszerzaniu globów.
Tom Fenech,
38

Backticks ( `...`) to starsza składnia wymagana tylko przez najstarsze niezgodne z POSIX powłoki burne i $(...)jest POSIX i jest bardziej preferowana z kilku powodów:

  • Backslashes ( \) wewnątrz backticks są obsługiwane w nieoczywisty sposób:

    $ 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 \\
    
  • Zagnieżdżone cytowanie wewnątrz $()jest znacznie wygodniejsze:

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

    zamiast:

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

    lub napisz coś takiego:

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

    ponieważ $()używa zupełnie nowego kontekstu cytowania

    który nie jest przenośny, ponieważ powłoki Bourne'a i Korna wymagałyby tych odwrotnych ukośników, podczas gdy Bash i Dash nie.

  • Składnia zastępowania poleceń zagnieżdżania jest łatwiejsza:

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

    niż:

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

    ponieważ $()wymusza zupełnie nowy kontekst cytowania, więc każde podstawienie polecenia jest chronione i może być traktowane samodzielnie, bez szczególnej uwagi na cytowanie i ucieczkę. Kiedy używasz grawisów, staje się brzydszy i brzydszy po dwóch i więcej poziomach.

    Jeszcze kilka przykładów:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
    
  • Rozwiązuje problem niespójnego zachowania podczas używania cudzysłowów:

    • echo '\$x' wyjścia \$x
    • echo `echo '\$x'` wyjścia $x
    • echo $(echo '\$x') wyjścia \$x
  • Składnia Backticks ma historyczne ograniczenia dotyczące zawartości osadzonego polecenia i nie może obsługiwać niektórych prawidłowych skryptów zawierających cudzysłowy, podczas gdy nowszy $()formularz może przetwarzać dowolny prawidłowy osadzony skrypt.

    Na przykład te prawidłowe skrypty osadzone nie działają w lewej kolumnie, ale działają na prawej 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 ')'
    `                              )
    

Dlatego składnia podstawiania poleceń z$ prefiksem powinna być metodą preferowaną, ponieważ jest wizualnie przejrzysta dzięki przejrzystej składni (poprawia czytelność dla ludzi i maszyn), jest zagnieżdżona i intuicyjna, jej wewnętrzne parsowanie jest oddzielne, a także jest bardziej spójne (z wszystkie inne rozszerzenia, które są analizowane z poziomu cudzysłowów), w których jedynym wyjątkiem są grawerowane napisy, a znak można łatwo zakamuflować, gdy sąsiaduje, co czyni go jeszcze trudniejszym do odczytania, zwłaszcza w przypadku małych lub nietypowych czcionek.`"

Źródło: Dlaczego jest $(...)preferowany w stosunku do `...`(grawisów)? w BashFAQ

Zobacz też:

kenorb
źródło
1
Zagnieżdżone cytowanie w podstawianiu akcentującym w stylu gravisa jest w rzeczywistości niezdefiniowane, możesz używać podwójnych cudzysłowów zarówno na zewnątrz, jak i wewnątrz, ale nie obu, przenośnie. Muszle interpretują je inaczej; niektóre wymagają odwrotnego ukośnika, aby przed nimi uciec, inne wymagają, aby nie były one poprzedzane odwrotnym ukośnikiem.
mirabilos
23

Od człowieka 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.
dgw
źródło
9

Oprócz innych odpowiedzi

$(...)

wyróżnia się wizualnie lepiej niż

`...`

Backticks za bardzo przypominają apostrofy; zależy to od używanej czcionki.

(I, jak właśnie zauważyłem, znaczniki odwrotne są znacznie trudniejsze do wprowadzenia w próbkach kodu wbudowanego).

Keith Thompson
źródło
2
musisz mieć dziwną klawiaturę (czy ja mam?). Dla mnie o wiele łatwiej jest wpisywać lewe klawisze - są to klawisze lewego górnego rogu, nie jest wymagany SHIFT ani ALT.
DVK,
1
@DVK: Mówiłem o ich wyglądzie, a nie o łatwości pisania. (Moja klawiatura jest prawdopodobnie taka sama jak twoja.) Jednak teraz, kiedy o niej wspomniałeś, myślę, że mam lepszą pamięć mięśniową dla $ (i )niż dla backticka; YMMV.
Keith Thompson
nigdy nie programowany w bashu (przeskakiwany ze starego ksh do Perla), więc zdecydowanie brak pamięci dla tej konkretnej składni :)
DVK
1
@DVK, myślałem, że Keith odnosi się do faktu, że kod nieblokowy (kod blokowy oznacza użycie czterech spacji na początku wiersza) używa do oznaczenia odwrotnych apostrofów, co utrudnia umieszczanie w nich grawisów, kolejna ilustracja trudności z zagnieżdżaniem :-) FWIW, może się okazać, że kod i znaczniki / code (inny sposób robienia kodu nieblokowego) mogą łatwiej zawierać znaki grawitacyjne.
paxdiablo
@Pax - rozumiem. Duh! Rzeczywiście, z jakiegoś powodu psychicznie utknąłem na kodach blokowych.
DVK,
7

$() umożliwia zagnieżdżanie.

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

Myślę, że backticks na to nie pozwala.

Shiplu Mokaddim
źródło
2
Możesz zagnieżdżać backticks; o wiele trudniej jest: out=`echo today is \`date\`` .
Jonathan Leffler
4

To standard POSIX definiuje $(command)formę podstawiania poleceń. Większość używanych obecnie powłok jest zgodna z POSIX i obsługuje tę preferowaną formę zamiast archaicznej notacji odwrotnego znaku. Sekcja podstawiania poleceń (2.6.3) w dokumencie języka powłoki opisuje to:

Podstawianie poleceń umożliwia zastąpienie danych wyjściowych polecenia w miejsce samej nazwy polecenia. Podstawienie polecenia następuje, gdy polecenie jest ujęte w następujący sposób:

$(command)

lub (wersja z cytatem wstecznym):

`command`

Powłoka powinna rozszerzyć podstawianie poleceń, wykonując polecenie w środowisku podpowłoki (patrz Środowisko wykonawcze powłoki ) i zastępując podstawienie polecenia (tekst polecenia plus otaczający znak „$ ()” lub odwrotne cudzysłowy) standardowym wyjściem polecenia, usuwając sekwencje jednego lub więcej <newline>znaków na końcu podstawienia. Osadzone <newline>znaki przed końcem wyjścia nie będą usuwane; jednakże mogą być traktowane jako ograniczniki pól i eliminowane podczas dzielenia pól, w zależności od wartości IFS i obowiązującego kwotowania. Jeśli dane wyjściowe zawierają jakiekolwiek bajty o wartości null, zachowanie jest nieokreślone.

W stylu zastępowania poleceń w cudzysłowie, <backslash>zachowuje swoje dosłowne znaczenie, z wyjątkiem sytuacji, gdy następuje po nim: „ $”, „ `”, lub <backslash>. Poszukiwanie pasującego odwrotnego cudzysłowu spełnia pierwszy niezacytowany odwrotny cudzysłów; podczas tego wyszukiwania, jeśli w komentarzu powłoki zostanie napotkany odwrotny cudzysłów bez znaku ucieczki, dokument znajdujący się w miejscu, osadzone polecenie podstawienia w postaci $ ( polecenie ) lub ciąg cytowany w cudzysłowie, pojawią się niezdefiniowane wyniki. Ciąg w apostrofach lub podwójnych cudzysłowach, który zaczyna się, ale nie kończy w `...`sekwencji „ ”, daje niezdefiniowane wyniki.

W postaci $ ( polecenie ) wszystkie znaki następujące po nawiasie otwierającym do pasującego nawiasu zamykającego stanowią polecenie . Do polecenia można użyć dowolnego poprawnego skryptu powłoki , z wyjątkiem skryptu składającego się wyłącznie z przekierowań, który daje nieokreślone wyniki.

Wyniki podstawiania poleceń nie będą przetwarzane w celu dalszego interpretowania tyldy, podstawiania parametrów, podstawiania poleceń ani interpretacji wyrażeń arytmetycznych. Jeżeli podstawianie komend następuje w cudzysłowach, dzielenie pól i rozwijanie nazw plików nie powinny być wykonywane na wynikach podstawiania.

Podstawianie poleceń można zagnieżdżać. Aby określić zagnieżdżenie w wersji z cudzysłowami wstecznymi, aplikacja powinna poprzedzać wewnętrzne cudzysłowy <backslash>znakami; na przykład:

\`command\`

Składnia języka poleceń powłoki jest niejednoznaczna dla rozszerzeń zaczynających się od „$((", który może wprowadzić interpretację arytmetyczną lub podstawienie polecenia rozpoczynające się od podpowłoki. Interpretacja arytmetyczna ma pierwszeństwo; to znaczy powłoka powinna najpierw określić, czy może przeanalizować interpretację jako interpretację arytmetyczną i przeanalizować ją tylko jako polecenie podstawienie, jeśli stwierdzi, że nie może przeanalizować interpretacji jako interpretacji arytmetycznej. Powłoka nie musi oceniać zagnieżdżonych ekspansji podczas wykonywania tego określenia. Jeśli napotka koniec danych wejściowych bez wcześniejszego ustalenia, że ​​nie może przeanalizować interpretacji jako interpretacji arytmetycznej, powłoka powinna traktować rozwinięcie jako niepełne rozwinięcie arytmetyczne i zgłosić błąd składni. Zgodna aplikacja powinna zapewnić, że oddziela " $(" i "('na dwa tokeny (to znaczy oddziel je spacjami) w podstawieniu polecenia, które zaczyna się od podpowłoki. Na przykład podstawienie polecenia zawierające pojedynczą podpowłokę można zapisać jako:

$( (command) )

JRFerguson
źródło
4

To jest pytanie starsze, ale wymyśliłem doskonały przykład $(...)ponad `...`.

Używałem zdalnego pulpitu do systemu Windows z uruchomionym cygwin i chciałem iterować po wyniku polecenia. Niestety, wpisanie znaku backtick było niemożliwe, ani z powodu zdalnego pulpitu, ani samego cygwina.

Rozsądnie jest założyć, że znak dolara i nawiasy będą łatwiejsze do wpisania w tak dziwnych ustawieniach.

Piotr Zierhoffer
źródło