30

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

echo $(stuff)
echo `stuff`

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

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

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

2 ответа2

62

ТЛ; др

Единственный stuff , скорее всего, будет работать для вас.



Полный ответ

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

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

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

Этот $(…) механизм называется "подстановка команд". В вашем случае основной командой является echo которая в основном выводит свои аргументы командной строки в stdout. Поэтому все, 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 добавляет его обратно. So 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

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

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .