Яка користь від використання $ () замість зворотних посилань у скриптах оболонки?


175

Є два способи захоплення виводу командного рядка в bash:

  1. Задані оболонки Legacy Bourne ``:

    var=`command`
  2. $() синтаксис (який, наскільки я знаю, є специфічним для Bash або, принаймні, не підтримується старими оболонками, які не є POSIX, як оригінальний Bourne)

    var=$(command)

Чи є якась користь від використання другого синтаксису порівняно з backticks? Або два цілком 100% еквівалента?


14
$()є POSIX і підтримується всіма сучасними оболонками Bourne, наприклад, ksh, bash, ash, dash, zsh, busybox, ви його називаєте. (Не таким сучасним є Solaris /bin/sh, але на Solaris ви б обов'язково /usr/xpg4/bin/shзамість цього використовували сучасний ).
Єнс

27
Також примітка про використання $()та зворотних посилань у псевдонімах. Якщо у вас є alias foo=$(command)ваш, .bashrcто commandвін буде виконаний, коли сама команда псевдонім запускається під час .bashrcінтерпретації. З alias foo=`command`, commandбуде виконуватися щоразу, коли запускається псевдонім. Але якщо уникнути форми $з $()формою (наприклад alias foo=\$(command)), вона також буде виконуватися кожного разу, коли запускається псевдонім, а не під час .bashrcінтерпретації. Наскільки я можу сказати, тестуючи, все одно; Я не можу знайти нічого в баш-документах, які пояснюють цю поведінку.
грунт

4
@dirtside Яка оболонка це, я перевірив bash та POSIX shell, backtick дійсно виконується, коли я надсилаю джерело. Простий приклад: псевдонім curDate = `дата` Після того, як я його запускаю і запускаю curDate, я отримую повідомлення, що він не може знайти команду Mon (Sourced on Monday), наприклад.
thecarpy

@dirtside Це неправда. Навіть з псевдонімом foo = `command` commandвиконується лише один раз. Я перевірив це: функція aaa () {printf дата; echo aaa >> ~ / test.txt; } псевдонім test1 = aaa. Функція aaa виконується лише один раз (після кожного входу), незалежно від того, скільки разів test1було виконано псевдонім ( ). Я використовував .bashrc (на Debian 10).
vitaliydev

Не маю ідеї, це була б будь-яка версія басу, яку я використовував п'ять з половиною років тому, який, мабуть, був досить старим навіть на той момент. Можливо, моє тестування було неправильним, але це навряд чи має значення, тому що я сумніваюся, хтось все ще використовує будь-яку версію, яка була.
грунт

Відповіді:


156

Найважливішим є вміння гніздовати їх, команди в межах команд, не втрачаючи розуму, намагаючись зрозуміти, чи буде якась форма втечі спрацьовувати на задній панелі.

Приклад, хоча дещо надуманий:

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

який дасть вам список усіх файлів у /dirдереві каталогів, які мають те саме ім’я, що і найдавніший текстовий файл із датою від грудня 2011 року (a) .

Іншим прикладом може бути щось на зразок отримання імені (не повного шляху) батьківського каталогу:

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

(a) Тепер, коли конкретна команда може насправді не працювати, я не перевіряв функціональність. Отже, якщо ви проголосуєте за це, ви втратили з поля зору наміри :-) Це мається на увазі лише як ілюстрація того, як можна гніздо, а не як фрагмент, готовий до виробництва, який не готовий до помилок.


87
Я очікую, що весь код на SO - це готові фрагменти, розроблені відповідно до стандартів надійності коду NASA. Все, що менше отримує прапор та видаляє голосування.
DVK

1
@DVK У випадку, якщо ви не жартуєте, я не погоджуюся з тим, що внесок у код повинен бути позначений за те, що не вдалося автоматично передати рецензію далеко від стандартних налаштувань SO (ліцензії CC-BY-SA або MIT), щоб забезпечити такі гарантії або придатність до призначення. Я б натомість повторно використовував код на ПД на свій страх і ризик і голосував за внески відповідно до корисності, технічних достоїнств тощо
chrstphrchvz

9
@chrstphrchvz, якщо ви подивитесь на мій профіль, ви побачите цей маленький фрагмент: "Весь код, який я розміщую у" Переповнюванні стека ", поширюється на ліцензію" Зроби з ним все, що ти хочеш ", повний текст якого: Do whatever the heck you want with it." :-) У будь-якому випадку, я впевнений, що це був гумор від DVK.
paxdiablo

1
а як бути, якщо це було "cd / home / pax / xyzzy / plover"? Ви опинилися б у лабіринті закручених маленьких уривків, всі різні?
Дункан

@wchargin Ви знали, що цей інцидент був основним мотиватором для додавання використаних визначених літералів до мови C ++? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod

59

Припустимо, ви хочете знайти каталог lib, відповідний тому, де gccвстановлено. У вас є вибір:

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

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

Перше простіше, ніж друге - використовувати перше.


4
Було б добре побачити деякі цитати навколо цих підстановок команд!
Том Фенек

1
Принаймні для bash, коментар @TomFenech не стосується завдань. x=$(f); x=`f` поводитись так само, як x="$(f)"; x="`f`". На відміну від цього , присвоювання масиву x=($(f)); x=(`f`) роблять виробляти розбивка на $IFSсимволи , як очікується , при виклику команди. Це зручно ( x=1 2 3 4не має сенсу), але непослідовно.
kdb

@kdb Ви маєте рацію щодо x=$(f)роботи без лапок. Я повинен був бути більш конкретним; Я пропонував використовувати libdir=$(dirname "$(dirname "$(which gcc)")")/lib(цитати навколо внутрішніх підстановок команд). Якщо залишити без котирування, ви все ще піддаєтеся звичайному розщепленню слів та розширенню глобальних слів.
Том Фенек

38

Повернення ( `...`) - це застарілий синтаксис, необхідний лише найстарішому з не сумісних з POSIX послідовностям бурштину і $(...)є POSIX та більш кращим з кількох причин:

  • Зворотні риски ( \) всередині задньої панелі обробляються неочевидно:

    $ 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 \\
  • Вкладене цитування всередині $()набагато зручніше:

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

    замість:

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

    або написати щось на кшталт:

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

    оскільки $()використовує абсолютно новий контекст для цитування

    яка не є портативною, оскільки оболонки Борна і Корна вимагатимуть цих зворотних нахилів, в той час як Bash і Dash цього не роблять.

  • Синтаксис для гніздових підстановок команд простіше:

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

    ніж:

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

    тому що $() застосовує абсолютно новий контекст для цитування, тому кожна заміна команди захищена і може бути оброблена самостійно без особливого занепокоєння з приводу цитування та виходу. При використанні задніх поверхонь воно стає більш бідним і неприємним після двох і вище рівнів.

    Ще кілька прикладів:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • Він вирішує проблему непослідовної поведінки при використанні зворотних лапок:

    • echo '\$x' виходи \$x
    • echo `echo '\$x'` виходи $x
    • echo $(echo '\$x') виходи \$x
  • Синтаксис зворотних посилань має історичні обмеження щодо вмісту вбудованої команди і не може обробляти деякі дійсні сценарії, що містять зворотні котирування, в той час як новіша $()форма може обробляти будь-який дійсний вбудований скрипт.

    Наприклад, ці вбудовані інакше вбудовані сценарії не працюють у лівій колонці, але працюють у правій 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 ')'
    `                              )

Таким чином, кращим методом має бути синтаксис для $-призначеної заміни команд , оскільки він чітко зрозумілий із чистим синтаксисом (покращує читабельність людини та машини), він нестабільний та інтуїтивний, його внутрішній синтаксичний аналіз є окремим, а також більш послідовним (з всі інші розширення, які розбираються з подвійних лапок), де посилання є єдиним винятком, і `персонаж легко маскується, якщо знаходиться поруч із" ще складнішого читання, особливо з невеликими або незвичними шрифтами.

Джерело: Чому $(...)віддається перевага над `...`(заднім числом)?на BashFAQ

Дивитися також:


1
Вкладене цитування підстановки в стилі акцента гравіса насправді не визначене, ви можете використовувати подвійні лапки як зовні, так і всередині, але не обидва, портативно. Раковини трактують їх по-різному; деякі вимагають зворотної косої риси, щоб уникнути їх, деякі вимагають, щоб вони не були відхиленими від нахилу.
mirabilos

23

Від man 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

Окрім інших відповідей,

$(...)

виділяється візуально краще, ніж

`...`

Задні посилання занадто схожі на апострофи; це залежить від шрифту, який ви використовуєте.

(І, як я щойно помітив, вкладати в основні зразки вбудованого коду набагато складніше.)


2
у вас повинна бути дивна клавіатура (чи я це роблю?). Для мене набагато простіше вводити задники - це клавіша верхнього лівого кута, не потрібно SHIFT або ALT.
DVK

1
@DVK: Я говорив про їхню зовнішність, а не про легкість набору тексту. (Моя клавіатура, мабуть, така сама, як і ваша.) Тим не менш, тепер, коли ви це згадуєте, я думаю, що у мене краща м'язова пам’ять $ (і )ніж у мене для бетіка; YMMV.
Кіт Томпсон,

ніколи не запрограмований на bash (пропущений зі старого ksh до Perl), тому точно не пам'ять для цього конкретного синтаксису :)
DVK

1
@DVK, я думав, що Кіт мав на увазі той факт, що тут неблоковий код (блок-код означає використання чотирьох пробілів на початку рядка) використовує задні посилання для його позначення, що ускладнює розміщення в них зворотних посилань, інша ілюстрація труднощі з введенням :-) FWIW, ви можете знайти код та / кодові теги (інший спосіб виконання неблокового коду) може легше містити зворотні посилання.
paxdiablo

@Pax - отримав. Да! Я дійсно подумки зупинився на блок-кодах чомусь.
DVK


4

Саме стандарт POSIX визначає $(command)форму підстановки команд. Більшість оболонок, що використовуються сьогодні, сумісні з POSIX та підтримують цю бажану форму над архаїчними позначеннями backtick. У розділі підстановки команд (2.6.3) документа мови Shell описано це:

Підстановка команд дозволяє замінити результат команди замість самої назви команди. Заміна команди відбувається, коли команда додається наступним чином:

$(command)

або (зворотна версія):

`command`

Оболонка повинна розширювати підстановку команд, виконуючи команду в середовищі підрозділу (див. Середовище виконання оболонки ) і замінюючи заміну команди (текст команди плюс додається "$ ()" або зворотні котирування) на стандартний вихід команди, видаляючи послідовності одного або декількох <newline>символів в кінці підстановки. Вбудовані <newline>символи до кінця виводу не видаляються; однак вони можуть розглядатися як роздільники поля та усуватися під час розбиття поля, залежно від значення IFS та котирування, що діє. Якщо вихід містить будь-які нульові байти, поведінка не визначено.

У стилі підстановки команд, що <backslash>запам'ятовується, він зберігає своє буквальне значення, за винятком випадків, коли слідує: ' $', ' `' або <backslash>. Пошук відповідного зворотного котирування повинен задовольнятися першим незапропонованим цитатам, що не залишилося цитатами; під час цього пошуку, якщо в коментарі оболонки зустрічається невідбутесь зворотне котирування, виникає документ тут, вбудована командна заміна форми $ ( command ) або рядка, що цитується, не визначені результати. Рядок з одноцитованим або подвійним цитуванням, який починається, але не закінчується, в " `...`" послідовності створює невизначені результати.

У формі $ ( команда ) всі символи, що слідують за відкритими дужками до відповідних дужок, що закриваються, складають команду . Будь-який дійсний скрипт оболонки може використовуватися для команди , за винятком сценарію, що складається виключно з перенаправлень, які дають не визначені результати.

Результати заміни команд не обробляються для подальшого розширення тильди, розширення параметрів, підстановки команд або арифметичного розширення. Якщо підміна команди відбувається всередині подвійних лапок, розбиття полів та розширення імені шляху за результатами заміни не проводиться.

Заміна команд може бути вкладена. Щоб вказати вкладення у версії з котируванням, програма повинна передувати внутрішнім зворотним котируванням із <backslash>символами; наприклад:

\`command\`

Синтаксис команди команди оболонки має неоднозначність для розширень, починаючи з "$((", який може ввести арифметичне розширення або заміну команд, що починається з підшару. Арифметичне розширення має перевагу; тобто оболонка спочатку визначає, чи може вона розбирати розширення як арифметичне розширення, і розбирає розширення лише як команду заміщення, якщо він визначає, що він не може аналізувати розширення як арифметичне розширення. Оболонці не потрібно оцінювати вкладені розширення при виконанні цього визначення. Якщо він стикається з кінцем вводу, не визначивши, що він не може аналізувати розширення як арифметичне розширення, оболонка розглядає розширення як неповне арифметичне розширення та повідомляє про синтаксичну помилку. Відповідна програма повинна гарантувати, що вона відокремлює "$( " та "('на два лексеми (тобто розділіть їх пробілом) у підстановці команд, яка починається з підшару. Наприклад, заміна команди, що містить одну підшару, може бути записана як:

$( (command) )


4

Це застаріле питання, але я придумав цілком вагомий приклад $(...)закінчення `...`.

Я використовував віддалений робочий стіл для вікон під керуванням cygwin і хотів перенести результат команди. На жаль, символу "backtick" було неможливо ввести, чи то через віддалений робочий стіл, чи через саму cygwin.

Цілком припустимо припустити, що в таких дивних налаштуваннях простіше буде набрати знак долара та круглі дужки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.