В поисках ответа, лучшее предложение, которое я мог найти, было , чтобы разбить список на серию меньших списков и обработать их, используя shell for
loops .Но вы не всегда можете сделать это, и даже когда это возможно, это грязный хак: например, не очевидно, как получить обычное make
поведение остановки, как только команда не выполняется.К счастью, после долгих поисков и экспериментов выясняется, что общее решение действительно существует.
Рецепты и переводы строк
make
рецепты вызывают отдельный подоболочку для каждой строки в рецепте.Такое поведение может быть раздражающим и нелогичным: например, команда cd
в одной строке не повлияет на последующие команды, потому что они выполняются в отдельных подоболочках.Тем не менее, это действительно то, что нам нужно, чтобы получить make
для выполнения действий с очень длинными списками файлов.
Обычно, если вы строите «многострочный» список файлов с регулярным назначением переменной, который использует обратную косую черту, чтобы разбитьоператор в нескольких строках, make
удаляет все новые строки:
# The following two statements are equivalent
FILES := a b c
FILES := \
a \
b \
c
Однако, используя директиву define
, можно создавать значения переменных, содержащие символы новой строки.Более того, если вы подставите такую переменную в рецепт, каждая строка будет действительно выполняться с использованием отдельной подоболочки, так что, например, запуск make test
из /home/jbloggs
с использованием make-файла ниже (и при условии, что файла с именем test
не существует) выдаст вывод /home/jbloggs
, поскольку эффект команды cd ..
теряется при завершении ее подоболочки:
define CMDS
cd ..
pwd
endef
test:
$(CMDS)
Если мы создадим переменную, которая содержит символы новой строки, используя define
, она может бытькак обычно, объединяется с другим текстом и обрабатывается с использованием всех обычных функций make
.Это, в сочетании с функцией $(foreach)
, позволяет нам получить то, что мы хотим:
# Just a single newline! Note 2 blank lines are needed.
define NL
endef
combined.txt: $(INPUTS)
rm $@
$(foreach f,$(INPUTS),cat $(f) >> $@$(NL))
Мы просим $(foreach)
преобразовать каждое имя файла в команду terminline-terminated , котораябудет выполнен в своей собственной оболочке.Для более сложных задач вы можете вместо этого записать список имен файлов в файл с серией команд echo
, а затем использовать xargs
.
Примечания
- Директива
define
описывается как необязательное получение токена =
, :=
или +=
в конце первой строки, чтобы определить, какой переменный аромат должен быть создан -- но учтите, что это работает только на версиях GNU make
3.82 и выше!Возможно, вы, как и я, работаете с популярной версией 3.81, которая ничего не назначает переменной, если вы добавляете один из этих токенов, что приводит к большому разочарованию.Подробнее см. здесь . - Все строки рецепта должны начинаться с буквенного символа табуляции, а не с 8 пробелов, которые я здесь использовал.