Что не так с «echo $ (вещи)» или «эхо` вещи` »?

4198
Kamil Maciorowski

Я использовал один из следующих

echo $(stuff) echo `stuff` 

(где stuff, например, pwdили dateили что-то более сложное).

Затем мне сказали, что этот синтаксис неправильный, плохая практика, не элегантный, чрезмерный, избыточный, чрезмерно сложный, программирование культового груза, нубистский, наивный и т. Д.

Но команда делает работу, так что именно случилось с ним?

30
Это избыточно и поэтому не должно использоваться. Сравните с бесполезным использованием кошки: http://porkmail.org/era/unix/award.html dr01 5 лет назад 11
`echo $ (echo $ (echo $ (echo $ (echo $ (stuff)))))` также * работает *, и все же вы, вероятно, не будете его использовать, верно? ;-) Peter A. Schneider 5 лет назад 7
Что именно вы пытаетесь сделать с этим расширением? curiousguy 5 лет назад 1
@curiousguy Я пытаюсь помочь другим пользователям, поэтому мой ответ ниже. Недавно я видел по крайней мере три вопроса, которые используют этот синтаксис. Их авторы пытались сделать ... различные "вещи". : D Kamil Maciorowski 5 лет назад 0
Кроме того, не все ли мы согласны с тем, что неразумно ограничиваться эхо-камерой? ;-) Peter A. Schneider 5 лет назад 2
Означает ли тег [tag: sh] вопрос `/ bin / sh`? curiousguy 5 лет назад 0
@curiousguy "Стандартный интерпретатор командного языка". Я добавил это, чтобы указать, что оболочки совместимы и / или получены из `sh` (например, не [tag: Powershell]). Это не о какой-то одной оболочке; речь идет о концепции использования `echo` с подстановкой команд таким образом. Kamil Maciorowski 5 лет назад 0
@KamilMaciorowski В основном это совместимые оболочки с поддержкой всех основных конструкций, но иногда с другой семантикой. `zsh` интересно. curiousguy 5 лет назад 0
@curiousguy Какова ваша точка зрения? Как я могу улучшить вопрос тогда? Kamil Maciorowski 5 лет назад 0

2 ответа на вопрос

62
Kamil Maciorowski

ТЛ; др

Подошва stuff, скорее всего, будет работать для вас.



Полный ответ

Что просходит

Когда вы бежите foo $(stuff), вот что происходит:

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

Этот $(…)механизм называется «подстановка команд». В вашем случае основной командой является то, echoчто в основном выводит свои аргументы командной строки в стандартный вывод. Поэтому все, что stuffпытается распечатать на стандартный вывод, захватывается, передается echoи распечатывается на стандартный выводecho .

Если вы хотите, чтобы вывод команды выводился stuffна стандартный вывод, просто запуститеstuff .

`…`Синтаксис служит той же цели, что и $(…)(под тем же названием: «подстановки команд»), есть несколько отличий, хотя, так что вы не можете слепо поменять их. Смотрите этот FAQ и этот вопрос .


Должен ли я избежать 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 иметь другие эффекты, кроме записи в стандартный вывод (например, создание временного файла, запуск службы, запуск виртуальной машины, уведомление удаленного сервера), поэтому вы не хотите запускать его несколько раз.
  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. Возможно другой статус выхода (упомянуто для полноты этого вики-ответа; подробности см. В другом ответе, который заслуживает доверия).

Еще одно отличие: способ `stuff` будет чередовать выходные данные` stdout` и `stderr`, в то время как` `echo` stuff` `` будет печатать весь вывод `stderr` из` stuff`, и только потом вывод `stdout` , Ruslan 5 лет назад 5
И это еще один пример: вряд ли в bash-скриптах может быть слишком много кавычек. `echo $ (stuff)` может доставить вам гораздо больше неприятностей, чем `echo" $ (stuff) "`, если вы не хотите явно использовать глобализацию и расширение. rexkogitans 5 лет назад 3
Еще одно небольшое отличие: `$ (…)` удаляет любой завершающий символ новой строки, а затем `echo` добавляет его обратно. Так что `echo" $ (printf% s 'a') "| xxd` дает другой вывод, чем `printf% s 'a' | xxd`. derobert 5 лет назад 2
Не следует забывать, что некоторые команды (например, `ls`) работают по-разному, в зависимости от того, является ли стандартный вывод консолью или нет; так `ls | cat` не то же самое, что ls. И, конечно, `echo $ (ls)` будет работать не так, как `ls`. Martin Rosenau 5 лет назад 1
@Ruslan Ответ теперь вики сообщества. Я добавил ваш полезный комментарий в вики. Спасибо. Kamil Maciorowski 5 лет назад 0
@derobert Ответ теперь вики сообщества. Я добавил ваш полезный комментарий в вики. Спасибо. Kamil Maciorowski 5 лет назад 0
@MartinRosenau Ответ теперь вики сообщества. Я добавил ваш полезный комментарий в вики. Спасибо. Kamil Maciorowski 5 лет назад 0
5
WaffleSouffle

Еще одно отличие: код выхода вложенной оболочки теряется, поэтому echoвместо него возвращается код выхода .

> stuff() { return 1 } > stuff; echo $? 1 > echo $(stuff); echo $? 0 
Добро пожаловать в Супер пользователя! Можете ли вы * объяснить * разницу, которую вы демонстрируете? Спасибо bertieb 5 лет назад 3
Я полагаю, что это показывает, что код возврата `$?` Будет кодом возврата `echo` (для` echo $ (stuff) `) вместо того, который` возвращен` с помощью `stuff`. Функция `stuff`` возвращает 1` (код выхода), но `echo` устанавливает его в" 0 "независимо от кода возврата` stuff`. Все еще должно быть в ответе. Ismael Miguel 5 лет назад 4
возвращаемое значение из подоболочки было потеряно phuclv 5 лет назад 0