Многострочные команды bash в make-файле

121

У меня есть очень удобный способ скомпилировать проект с помощью нескольких строк команд bash. Но теперь мне нужно скомпилировать его через make-файл. Учитывая, что каждая команда запускается в собственной оболочке, мой вопрос: как лучше всего запустить многострочную команду bash, зависящую друг от друга, в make-файле? Например, вот так:

for i in `find`
do
    all="$all $i"
done
gcc $all

Кроме того, может кто-нибудь объяснить, почему даже однострочная команда bash -c 'a=3; echo $a > file'работает правильно в терминале, но создает пустой файл в случае файла makefile?

Jofsey
источник
4
Вторая часть должна быть отдельным вопросом. Добавление еще одного вопроса просто создает шум.
l0b0

Ответы:

137

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

foo:
    for i in `find`;     \
    do                   \
        all="$$all $$i"; \
    done;                \
    gcc $$all

Но если вы просто хотите взять весь список, возвращенный findвызовом, и передать его gcc, вам на самом деле не обязательно нужна многострочная команда:

foo:
    gcc `find`

Или, используя более традиционный $(command)подход к оболочке (обратите внимание на $экранирование):

foo:
    gcc $$(find)
Эльдар Абусалимов
источник
2
Для многострочности вам все равно нужно избегать знаков доллара, как указано в комментариях в другом месте.
Tripleee
1
Да, короткий ответ 1. $: = $$ 2. добавить многострочность с помощью \ BTW, ребята из bash обычно рекомендуют заменять вызов `find` на $$ (find)
Евгений Якимович
89

Как указано в вопросе, каждая подкоманда запускается в собственной оболочке . Это делает написание нетривиальных сценариев оболочки немного беспорядочным - но это возможно! Решение состоит в том, чтобы объединить ваш скрипт в одну подкоманду (одну строку), которую make будет рассматривать.

Советы по написанию сценариев оболочки в make-файлах:

  1. Чтобы избежать использования сценария, $заменив его на$$
  2. Преобразуйте скрипт, чтобы он работал как одну строку, вставив ;между командами
  3. Если вы хотите написать сценарий на нескольких строках, экранируйте конец строки с помощью \
  4. Необязательно начать с, set -eчтобы соответствовать положению make для прерывания при сбое подкоманды
  5. Это совершенно необязательно, но вы можете заключить скрипт в скобки ()или {}подчеркнуть связность многострочной последовательности - это не типичная последовательность команд make-файла.

Вот пример, вдохновленный OP:

mytarget:
    { \
    set -e ;\
    msg="header:" ;\
    for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
    msg="$$msg :footer" ;\
    echo msg=$$msg ;\
    }
Брент Брэдберн
источник
4
Вы также можете захотеть SHELL := /bin/bashвключить в свой make-файл специфические для BASH функции, такие как подстановка процесса .
Брент Брэдберн
8
Тонкий момент: пробел после {важен для предотвращения интерпретации {setкак неизвестной команды.
Брент Брэдберн
3
Тонкий момент №2: в make-файле это, вероятно, не имеет значения, но разница между оберткой {}и ()имеет большое значение, если вы иногда хотите скопировать сценарий и запустить его прямо из приглашения оболочки. Вы можете нанести ущерб своему экземпляру оболочки, объявив переменные и особенно изменив состояние с помощью setвнутри {}. ()предотвращает изменение сценарием вашей среды, что, вероятно, предпочтительнее. Пример ( это конец вашей оболочки сессии ): { set -e ; } ; false.
Брент Брэдберн,
Вы можете включать комментарии, используя следующую форму command ; ## my comment \` (the comment is between :; `и` \ `). Кажется, это работает нормально, за исключением того, что если вы запустите команду вручную (путем копирования и вставки), история команд будет включать комментарий таким образом, что команда прервется (если вы попытаетесь использовать его повторно). [Примечание: выделение синтаксиса для этого комментария нарушено из-за использования обратной косой черты внутри обратного апострофа.]
Брент Брэдберн,
Если вы хотите разбить строку в bash / perl / script внутри make-файла, закройте кавычку строки, обратную косую черту, новую строку (без отступа), открыв кавычку строки. Пример; perl -e QUOTE print 1; QUOTE BACKSLASH NEWLINE QUOTE print 2 QUOTE
mosh
2

Что плохого в простом вызове команд?

foo:
       echo line1
       echo line2
       ....

И для вашего второго вопроса вам нужно избежать $ , используя $$вместо этого, то есть bash -c '... echo $$a ...'.

РЕДАКТИРОВАТЬ: ваш пример можно переписать в однострочный скрипт следующим образом:

gcc $(for i in `find`; do echo $i; done)
JesperE
источник
5
Причина «каждая команда запускается в своей собственной оболочке», а мне нужно использовать результат command1 в command2. Как в примере.
Джофси
Если вам нужно распространять информацию между вызовами оболочки, вам необходимо использовать какую-либо форму внешнего хранилища, например временный файл. Но вы всегда можете переписать свой код для выполнения нескольких команд в одной оболочке.
JesperE
+1, особенно для упрощенной версии. И разве это не то же самое, что просто выполнить команду gcc `find`?
Эльдар Абусалимов
1
Да, это так. Но вы, конечно, можете делать более сложные вещи, чем просто «эхо».
JesperE
2

Конечно, правильный способ написать Makefile - это фактически задокументировать, какие цели зависят от источников. В тривиальном случае предлагаемое решение будет fooзависеть от самого себя, но, конечно, makeдостаточно умен, чтобы отказаться от циклической зависимости. Но если вы добавите временный файл в свой каталог, он «волшебным образом» станет частью цепочки зависимостей. Лучше раз и навсегда создать явный список зависимостей, возможно, с помощью скрипта.

GNU Make знает , как работать , gccчтобы получить исполняемый из набора из .cи .hфайлов, так что, может быть , все , что вам действительно нужно , чтобы количество

foo: $(wildcard *.h) $(wildcard *.c)
tripleee
источник
1

Директива ONESHELL позволяет писать несколько строковых рецептов, которые будут выполняться при одном вызове оболочки.

SOURCE_FILES = $(shell find . -name '*.c')

.ONESHELL:
foo: ${SOURCE_FILES}
    for F in $^; do
        gcc -c $$F
    done

Однако есть недостаток: специальные символы префикса ('@', '-' и '+') интерпретируются по-разному.

https://www.gnu.org/software/make/manual/html_node/One-Shell.html

Жан-Батист Пуаттевин
источник