Как использовать команды оболочки в Makefile

99

Я пытаюсь использовать результат lsв других командах (например, echo, rsync):

all:
    <Building, creating some .tgz files - removed for clarity>
    FILES = $(shell ls)
    echo $(FILES)

Но я получаю:

make
FILES = Makefile file1.tgz file2.tgz file3.tgz
make: FILES: No such file or directory
make: *** [all] Error 1

Я пробовал использовать echo $$FILES, echo ${FILES}и безуспешно echo $(FILES).

Адам Матан
источник

Ответы:

149

С участием:

FILES = $(shell ls)

с таким отступом внизу all, это команда сборки. Итак, это расширяется $(shell ls), а затем пытается выполнить команду FILES ....

Если FILESпредполагается, что это makeпеременная, эти переменные должны быть назначены вне части рецепта, например:

FILES = $(shell ls)
all:
        echo $(FILES)

Конечно, это означает, что FILESон будет установлен на «вывод из ls» перед запуском любой из команд, создающих файлы .tgz. (Хотя, как отмечает Каз, переменная каждый раз повторно расширяется, поэтому в конечном итоге она будет включать файлы .tgz; некоторые варианты make должны FILES := ...избегать этого для эффективности и / или правильности. 1 )

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

all:
        FILES="$(shell ls)"

Однако каждая строка запускается отдельной оболочкой, поэтому эта переменная не сохранится до следующей строки, поэтому вы должны немедленно использовать ее:

        FILES="$(shell ls)"; echo $$FILES

Это все немного глупо, поскольку оболочка будет расширяться *(и другие выражения оболочки) в первую очередь за вас, поэтому вы можете просто:

        echo *

как вашу команду оболочки.

Наконец, как общее правило (не совсем применимо к этому примеру): как отмечает эсперанто в комментариях, использование вывода из lsне совсем надежно (некоторые детали зависят от имен файлов, а иногда даже от версии ls; некоторые версии lsпопытки очистить вывод в некоторых случаях). Таким образом, как l0b0 и idelic примечание, если вы используете GNU make, вы можете использовать $(wildcard)и $(subst ...)выполнять все внутри makeсебя (избегая проблем с «странными символами в имени файла»). (В shсценариях, в том числе в make-файлах с рецептами, можно использовать другой метод, find ... -print0 | xargs -0чтобы избежать появления пробелов, символов новой строки, управляющих символов и т. Д.)


1 В документации GNU Make далее отмечается, что POSIX добавила ::=присваивание в 2012 году . Я не нашел для этого краткую справочную ссылку на документ POSIX и не знаю, какие makeварианты поддерживают ::=присваивание, хотя GNU make делает это сегодня с тем же значением, что и :=, т. Е. Выполняет назначение прямо сейчас с расширением.

Обратите внимание, что VAR := $(shell command args...)это также может быть записано VAR != command args...в нескольких makeвариантах, включая все современные варианты GNU и BSD, насколько мне известно. У этих других вариантов нет, $(shell)поэтому использование VAR != command args...лучше в том, что они короче и работают в большем количестве вариантов.

торек
источник
Спасибо. Я хочу использовать сложную команду (например, lswith sedи cut), а затем использовать результаты в rsync и других командах. Должен ли я повторять длинную команду снова и снова? Нельзя ли сохранить результаты во внутренней переменной Make?
Адам Матан
1
Gnu make может иметь способ сделать это, но я никогда не использовал его, и все ужасно сложные make-файлы, которые мы используем, просто используют переменные оболочки и гигантские однострочные команды оболочки, построенные с помощью "; \" в конце каждой строки как необходимо. (не могу заставить кодировку кода работать с последовательностью обратной косой черты здесь, хм)
torek
1
Может быть, что-то вроде: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
Уильям Моррис
2
@William: makeможет сделать это без использования оболочки: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic 05
1
Я хотел бы отметить, что, хотя в данном случае это не кажется очень важным, вы не должны автоматически анализировать вывод ls. ls предназначен для показа информации людям, а не для привязки к скриптам. Дополнительная информация здесь: mywiki.wooledge.org/ParsingLs Возможно, в случае, если make не предлагает вам подходящего расширения с подстановочными знаками, «find» лучше, чем «ls».
Рауль Салинас-Монтеагудо
53

Кроме того, в дополнение к ответу Торека: одна вещь, которая выделяется, - это то, что вы используете лениво вычисляемое назначение макроса.

Если вы используете GNU Make, используйте :=присваивание вместо =. Это назначение вызывает немедленное расширение правой части и сохранение ее в левой переменной.

FILES := $(shell ...)  # expand now; FILES is now the result of $(shell ...)

FILES = $(shell ...)   # expand later: FILES holds the syntax $(shell ...)

Если вы используете =присвоение, это означает, что каждое отдельное вхождение $(FILES)будет расширять $(shell ...)синтаксис и, таким образом, вызывать команду оболочки. Это замедлит выполнение вашей задачи make или даже приведет к неожиданным последствиям.

Каз
источник
1
Теперь, когда у нас есть список, как нам перебрать каждый элемент в списке и выполнить для него команду? Такие как сборка или тест?
anon58192932
3
@ anon58192932 Эта конкретная итерация для выполнения команды обычно выполняется во фрагменте синтаксиса оболочки в рецепте сборки, поэтому она выполняется в оболочке, а не в make : for x in $(FILES); do command $$x; done. Обратите внимание на удвоение, $$которое передает сингл $в оболочку. Также фрагменты снаряда однострочны; для написания многострочного кода оболочки вы используете продолжение обратной косой черты, которое обрабатывается makeсамо по себе и сворачивается в одну строку. Это означает, что точки с запятой, разделяющие команды оболочки, являются обязательными.
Kaz