Що не так з "echo $ (stuff)" чи "echo` stuff "?


31

Я використав одне з наступних

echo $(stuff)
echo `stuff`

(Де stuffзнаходиться , наприклад , pwdабо , dateабо що - то більш складне).

Тоді мені сказали, що цей синтаксис є неправильним, погана практика, неелегантна, надмірна, надмірна, надмірно складна, програмування культового культу, нобіш, наївність тощо.

Але команда робить роботу, так що саме трапилося з ним?


11
Це зайве, тому його не слід використовувати. Порівняйте з марним використанням кота: porkmail.org/era/unix/award.html
dr01

7
echo $(echo $(echo $(echo$(echo $(stuff)))))також робить роботу, і до сих пір ви , ймовірно , не будете використовувати його, НЕ так? ;-)
Пітер - Відновіть Моніку

1
Що ви саме намагаєтесь зробити з цим розширенням?
curiousguy

@curiousguy Я намагаюся допомогти іншим користувачам, звідси моя відповідь нижче. Нещодавно я бачив щонайменше три питання, які використовують цей синтаксис. Їх автори намагалися робити ... різні stuff. : D
Каміль Маціоровський

2
Також, чи не всі ми згодні, що нерозумно обмежувати себе ехо-камерою ?? ;-)
Пітер - Відновіть Моніку

Відповіді:


63

тл; д-р

Підошва stuff, швидше за все, працює для вас.



Повна відповідь

Що сталося

Коли ви запускаєтесь foo $(stuff), це відбувається так:

  1. stuff пробіжки;
  2. його вихід (stdout) замість друку замінює $(stuff)на виклик foo;
  3. потім fooзапускається, аргументи його командного рядка очевидно залежать від того, що stuffповернулося.

Цей $(…)механізм називається "підміна команд". У вашому випадку головна команда - echoце в основному друкує аргументи командного рядка до stdout. Тож те, що stuffнамагається надрукувати в stdout, захоплюється, передається echoта друкується до stdout echo.

Якщо ви хочете, щоб вихід stuffбув надрукований у stdout, просто запустіть підошву stuff.

`…`Синтаксис служить ті ж цілям, що і $(…)(під той же назвою: «команда заміна»), є кілька відмінностей , хоча, так що ви не можете сліпо поміняти їх. Дивіться це поширене запитання та це запитання .


Чи слід уникати, echo $(stuff)незважаючи ні на що?

Є причина, яку ви можете використовувати, echo $(stuff)якщо ви знаєте, що робите. З тієї ж причини вам слід уникати, echo $(stuff)якщо ви насправді не знаєте, що робите.

Справа є stuffі echo $(stuff)не є рівнозначною. Останнє означає викликати оператор split + glob на виході stuffз значенням за замовчуванням $IFS. Подвійне цитування підстановки команд перешкоджає цьому. Одноразове цитування підстановки команд змушує її більше не бути заміною команди.

Щоб спостерігати за цим, коли мова заходить про розщеплення, виконайте ці команди:

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")'

А для глобалізації:

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

Як бачите echo "$(stuff)", еквівалент (-ish *) stuff. Ви можете використовувати його, але який сенс ускладнювати речі таким чином?

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

Є команди, що генерують вихід, які слід оцінювати (що включає розщеплення, глобулювання тощо) та запускати оболонку, тому eval "$(stuff)"є можливість (див. Цю відповідь ). Я ніколи не бачив команди, яка потребує її виводу, щоб пройти додаткове розщеплення + глобулювання перед друком . Навмисне використання echo $(stuff)здається дуже рідкісним.


Про що var=$(stuff); echo "$var"?

Влучне зауваження. Цей фрагмент:

var=$(stuff)
echo "$var"

має бути еквівалентним echo "$(stuff)"(-ish *) stuff. Якщо це весь код, просто запустіть stuffзамість цього.

Якщо, однак, вам потрібно використовувати результати stuffбільше, ніж один раз, тоді цей підхід

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

зазвичай краще, ніж

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

Навіть , якщо fooце echoі ви отримаєте echo "$var"в вашому коді, це може бути краще , щоб зберегти його таким чином. Що слід врахувати:

  1. З var=$(stuff) stuffпробіжками один раз; навіть якщо команда швидка, уникати обчислення одного і того ж результату двічі - це правильна річ. Або, можливо, stuffє інші ефекти, крім запису до stdout (наприклад, створення тимчасового файлу, запуск послуги, запуск віртуальної машини, повідомлення про віддалений сервер), тому ви не хочете запускати його кілька разів.
  2. Якщо stuffгенерується залежний від часу або дещо випадковий вихід, ви можете отримати непослідовні результати від foo "$(stuff)"та bar "$(stuff)". Після того, var=$(stuff)як значення $varбуде зафіксовано, ви можете бути впевнені foo "$var"і bar "$var"отримати ідентичний аргумент командного рядка.

У деяких випадках замість foo "$var"вас, можливо, потрібно (потрібно) використовувати foo $var, особливо якщо stuffгенерує кілька аргументів для foo(змінна масиву може бути кращою, якщо ваша оболонка підтримує її). Знову ж таки, знайте, що ви робите. Коли мова йде echoпро різницю між echo $varі echo "$var"є такою ж, як між echo $(stuff)і echo "$(stuff)".


* Еквівалент ( -іш )?

Я сказав echo "$(stuff)", що рівнозначно (-ish) до stuff. Є щонайменше два питання, які роблять це не зовсім рівнозначним:

  1. $(stuff)працює stuffв нижній частині, тому краще сказати echo "$(stuff)", що еквівалентно (-ish) (stuff). Команди, які впливають на оболонку, в яку вони запускаються, якщо вони знаходяться в нижній частині, не впливають на основну оболонку.

    У цьому прикладі stuffє a=1; echo "$a":

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

    Порівняйте його з

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

    і с

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

    Інший приклад, почніть з stuffтого, щоб бути cd /; pwd:

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

    і тест stuffта (stuff)версії.

  2. echoне є хорошим інструментом для відображення неконтрольованих даних . Це echo "$var"ми повинні були говорити printf '%s\n' "$var". Але оскільки питання згадується echoі оскільки найімовірніше рішення - це використовувати не echoв першу чергу, я вирішив не вводити printfдо цього часу.

  3. stuffабо (stuff)буде перемежовувати вивід stdout та stderr, при цьому echo $(stuff)буде надруковано весь висновок stderr з stuff(який запускається спочатку), і лише тоді вихід висновку stdout засвоюється echo(який працює останнім).

  4. $(…)знімає будь-який echoзворотний рядок, а потім додає його назад. Таким чином , echo "$(printf %s 'a')" | xxdдає інший результат , ніж printf %s 'a' | xxd.

  5. Деякі команди ( lsнаприклад) працюють по-різному, залежно від того, стандартний вихід - консоль чи ні; так ls | catне робить те ж саме ls. Аналогічно echo $(ls)буде працювати інакше, ніж ls.

    Введення в lsсторону, в загальному випадку , якщо у вас є , щоб змусити то це інша поведінка stuff | catкраще echo $(ls)або echo "$(ls)"тому , що він не викликає всі інші питання , згадані тут.

  6. Можливо, інший статус виходу (згаданий для повноти відповіді у вікі; для детальної інформації див. Іншу відповідь, яка заслуговує на зарахування).


5
Ще одна відмінність: stuffспосіб буде перемежовуватися stdoutта stderrвиводитися, тоді як echo `stuff` буде надруковано весь stderrвихід із stuff, і лише потім stdoutвихід.
Руслан

3
І це ще один приклад: навряд чи може бути занадто багато цитат у скриптах bash. echo $(stuff)може поставити вас у набагато більше неприємностей, як echo "$(stuff)"ніби ви не хочете, щоб чітко глобалізуватись і розширюватись.
rexkogitans

2
Ще одна незначна відмінність: $(…)знімає будь-який останній рядок і потім echoдодає його назад. Таким чином , echo "$(printf %s 'a')" | xxdдає інший результат , ніж printf %s 'a' | xxd.
дероберт

1
Не слід забувати, що деякі команди ( lsнаприклад) працюють по-різному залежно від того, стандартний вихід - консоль чи ні; так ls | catне робить те ж саме ls. І звичайно echo $(ls)буде працювати інакше, ніж ls.
Мартін Розенау

@Ruslan Відповідь - це вікі спільноти. Я додав ваш корисний коментар у вікі. Дякую.
Каміль Маціоровський

5

Ще одна відмінність: код виходу під оболонки втрачається, тому echoзамість нього витягується код виходу .

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

3
Ласкаво просимо до Супер Користувача! Чи можете ви пояснити різницю, яку ви демонструєте? Спасибі
bertieb

4
Я вважаю, що це свідчить про те, що код повернення $?буде кодом повернення echo(для echo $(stuff)) замість одного returnредактора stuff. stuffФункція return 1(код виходу), але echoвстановлює його в «0» незалежно від повернення коду stuff. Ще слід відповісти.
Ісмаїл Мігель

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