Как написать цикл в Makefile?

Ответы:

273

Следующее будет делать, если, как я предполагаю, вы используете ./a.outплатформу UNIX.

for number in 1 2 3 4 ; do \
    ./a.out $$number ; \
done

Тест следующим образом:

target:
    for number in 1 2 3 4 ; do \
        echo $$number ; \
    done

производит:

1
2
3
4

Для больших диапазонов используйте:

target:
    number=1 ; while [[ $$number -le 10 ]] ; do \
        echo $$number ; \
        ((number = number + 1)) ; \
    done

Это выводит от 1 до 10 включительно, просто измените whileусловие завершения с 10 на 1000 для гораздо большего диапазона, как указано в вашем комментарии.

Вложенные циклы можно сделать так:

target:
    num1=1 ; while [[ $$num1 -le 4 ]] ; do \
        num2=1 ; while [[ $$num2 -le 3 ]] ; do \
            echo $$num1 $$num2 ; \
            ((num2 = num2 + 1)) ; \
        done ; \
        ((num1 = num1 + 1)) ; \
    done

производство:

1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
4 1
4 2
4 3
paxdiablo
источник
1
qwert - это просто целевое имя, которое вряд ли будет настоящим файлом. Правилам Makefile нужна цель. Что касается синтаксической ошибки, вам не хватает некоторых вещей - смотрите обновление.
paxdiablo
2
Поскольку вы предполагаете, что его оболочка распознает ((...)), почему бы не использовать гораздо более простое для ((i = 0; i <WHATEVER; ++ i)); делать ...; сделано ?
Idelic
1
Обратите внимание, что seqкоманда, которая генерирует последовательность чисел, существует в большинстве (всех?) Unix-систем, поэтому вы можете написать for number in ``seq 1 1000``; do echo $$number; done(Поставьте один знак обратной галочки на каждой стороне команды seq, а не две, я не знаю, как правильно отформатировать это используя синтаксис stackoverflow)
Сюзанна Дюперон,
3
Спасибо, мне не хватало двойного $$ для ссылки на переменную цикла for.
Лейф Грюнвольдт
1
@Jonz: символ продолжения строки объясняется тем, что makeобычно каждая строка обрабатывается как отдельная суб-оболочка. Без продолжения он попытался бы запустить подоболочку только с (например) for number in 1 2 3 4 ; do, без остальной части цикла. С ним это фактически становится единственной линией формы while something ; do something ; done, которая является полным утверждением. $$Вопрос ответил здесь: stackoverflow.com/questions/26564825/... , с кодом , казалось бы , извлеченным из этого самого ответа :-)
paxdiablo
267

Если вы используете GNU make, вы можете попробовать

НОМЕРА = 1 2 3 4
сделай это:
        $ (foreach var, $ (NUMBERS),. / a.out $ (var);)

который будет генерировать и выполнять

./a.out 1; ./a.out 2; ./a.out 3; ./a.out 4;
Idelic
источник
27
Этот ответ ИМХО лучше, потому что он не требует использования какой-либо оболочки, это чистый make-файл (даже если он специфичен для GNU).
Джоселин Делаланде
7
точка с запятой имеет решающее значение, иначе будет выполнена только первая итерация
Джереми Лейпциг
7
Это решение скрывает код выхода ./a.out 1. ./a.out 2будет выполняться независимо.
bobbogo
1
@ Александр: не могли бы вы уточнить, на какое ограничение вы ссылаетесь?
Идеально
1
@ Идеально, да. Для следующих make-файлов и сценариев шеллскрипт работает (передает аргумент ls), в то время как make-файл выдает ошибку: make: execvp: / bin / sh: список аргументов слишком длинный Makefile: ix.io/d5L шеллскрипт: ix.io / d5M Это проблема в функции foreach, с которой я столкнулся на работе, когда необычно длинный список имен файлов (также с длинными путями) был передан команде. Мы должны были найти обходной путь.
Александр
107

Основная причина использования делают ИМХО это -jфлаг. make -j5будет запускать 5 команд оболочки одновременно. Это хорошо, если у вас 4 процессора, и хороший тест любого make-файла.

По сути, вы хотите, чтобы make увидел что-то вроде:

.PHONY: all
all: job1 job2 job3

.PHONY: job1
job1: ; ./a.out 1

.PHONY: job2
job2: ; ./a.out 2

.PHONY: job3
job3: ; ./a.out 3

Это -jдружелюбно (хороший знак). Можете ли вы определить котельную плиту? Мы могли бы написать:

.PHONY: all job1 job2 job3
all: job1 job2 job3
job1 job2 job3: job%:
    ./a.out $*

для того же эффекта (да, это то же самое, что и предыдущая формулировка, что касается make , только немного более компактно).

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

LAST := 1000
NUMBERS := $(shell seq 1 ${LAST})
JOBS := $(addprefix job,${NUMBERS})
.PHONY: all ${JOBS}
all: ${JOBS} ; echo "$@ success"
${JOBS}: job%: ; ./a.out $*

Вы запускаете это make -j5 LAST=550со значением по LASTумолчанию 1000.

bobbogo
источник
7
Это, безусловно, лучший ответ, по той причине, что упомянут Боббого ( -j ).
д.б.н.
По какой-то конкретной причине это использует суффиксы .PHONY? Они требуются для чего-нибудь?
Себ
6
@seb: без .PHONY: all, make будет искать файл с именем all. Если такой файл существует, make затем проверяет время последнего изменения этого файла, и makefile почти наверняка не будет выполнять то, что вы хотели. .PHONYДекларация говорит сделать , что allявляется символической мишенью. Поэтому Make будет считать allцель всегда устаревшей. Отлично. Смотрите руководство.
Боббого
4
Как работает эта строка: $ {JOBS}: job%: Для чего нужна вторая точка с запятой? Я ничего не видел в gnu.org/software/make/manual/make.htm
Джо,
2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (я думаю , что ты имел в виду , что это вторая * * двоеточие для ). Не путайте их с нехорошими (ИМХО) шаблонными правилами . Правила статических шаблонов действительно полезны всякий раз, когда список целей может быть сопоставлен с одним из шаблонов make noddy (то же самое относится и к зависимостям). Здесь я использую один только для удобства, что все, что соответствует %в правиле, доступно как $*в рецепте.
Боббого
22

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

встроенный макрос $ (eval ....) - ваш друг. Или может быть по крайней мере.

define ITERATE
$(eval ITERATE_COUNT :=)\
$(if $(filter ${1},0),,\
  $(call ITERATE_DO,${1},${2})\
)
endef

define ITERATE_DO
$(if $(word ${1}, ${ITERATE_COUNT}),,\
  $(eval ITERATE_COUNT+=.)\
  $(info ${2} $(words ${ITERATE_COUNT}))\
  $(call ITERATE_DO,${1},${2})\
)
endef

default:
  $(call ITERATE,5,somecmd)
  $(call ITERATE,0,nocmd)
  $(info $(call ITERATE,8,someothercmd)

Это упрощенный пример. При больших значениях он не будет хорошо масштабироваться - он работает, но поскольку строка ITERATE_COUNT будет увеличиваться на 2 символа (пробел и точка) для каждой итерации, когда вы поднимаетесь на тысячи, для подсчета слов требуется все больше времени. Как написано, он не обрабатывает вложенные итерации (для этого вам понадобится отдельная функция итерации и счетчик). Это чисто gnu make, не требующий оболочки (хотя, очевидно, OP пытался запустить программу каждый раз - здесь я просто отображаю сообщение). If в ITERATE предназначено для перехвата значения 0, потому что в противном случае $ (word ...) выдаст ошибку.

Обратите внимание, что растущая строка, служащая счетчиком, используется потому, что встроенная переменная $ (words ...) может обеспечить арабский счет, но в противном случае make не поддерживает математические операции (вы не можете присвоить 1 + 1 чему-либо и получить 2, если вы не вызываете что-то из оболочки, чтобы выполнить это для вас, или используете не менее сложную макрооперацию). Это прекрасно работает для счетчика ИНКРЕМЕНТОВ, но не так хорошо для счетчика УМЕНЬШЕНИЯ.

Я сам этим не пользуюсь, но в последнее время мне нужно было написать рекурсивную функцию для оценки библиотечных зависимостей в многобинарной, многобиблиотечной среде сборки, где вам нужно знать, чтобы использовать ДРУГИЕ библиотеки, когда вы включаете какую-то библиотеку, которая сам по себе имеет другие зависимости (некоторые из которых варьируются в зависимости от параметров сборки), и я использую метод $ (eval) и counter, аналогичный приведенному выше (в моем случае счетчик используется, чтобы гарантировать, что мы каким-то образом не перейдем к бесконечному цикл, а также в качестве диагностики, чтобы сообщить, сколько итераций было необходимо).

Что-то еще ничего не стоящее, хотя и незначительное для Q оператора Q: $ (eval ...), предоставляет метод, позволяющий обойти внутреннее отвращение make к циклическим ссылкам, что хорошо и хорошо для применения, когда переменная является типом макроса (инициализируется с помощью =), по сравнению с немедленным назначением (инициализируется с помощью: =). Иногда вы хотите иметь возможность использовать переменную в своем собственном присваивании, и $ (eval ...) позволит вам сделать это. Здесь важно учесть, что во время запуска eval переменная разрешается, а разрешенная часть больше не рассматривается как макрос. Если вы знаете, что делаете, и пытаетесь использовать переменную RHS для присвоения себе, это, как правило, то, что вы хотите в любом случае произойти.

  SOMESTRING = foo

  # will error.  Comment out and re-run
  SOMESTRING = pre-${SOMESTRING}

  # works
  $(eval SOMESTRING = pre${SOMESTRING}

default:
  @echo ${SOMESTRING}

Счастливый макияж.

s.straw
источник
20

Для кроссплатформенной поддержки настройте разделитель команд (для выполнения нескольких команд в одной строке).

Если вы используете MinGW на платформе Windows, например, разделитель команд &:

NUMBERS = 1 2 3 4
CMDSEP = &
doit:
    $(foreach number,$(NUMBERS),./a.out $(number) $(CMDSEP))

Это выполняет объединенные команды в одну строку:

./a.out 1 & ./a.out 2 & ./a.out 3 & ./a.out 4 &

Как уже упоминалось, на платформе * nix CMDSEP = ;.

Чёрно
источник
7

Это не совсем чистый ответ на вопрос, а разумный способ обойти такие проблемы:

вместо того, чтобы писать сложный файл, просто делегируйте управление, например, скрипту bash, например: makefile

foo : bar.cpp baz.h
    bash script.sh

и script.sh выглядит так:

for number in 1 2 3 4
do
    ./a.out $number
done
Виллем Ван Онсем
источник
Я застрял на шаг при написании Makefile. У меня есть следующий код: set_var: @ NUM = 0; while [[$$ NUM <1]]; do \ echo "Я здесь"; \ echo $$ NUM dump $$ {NUM} .txt; \ var = "SSA_CORE $$ {NUM} _MAINEXEC"; \ echo $$ var; \ var1 = eval echo \$${$(var)}; \ echo $$ var1; \ ((NUM = NUM ​​+ 1)); \ done all: set_var here SSA_CORE0_MAINEXEC - это переменная среды, которая уже установлена. Поэтому я хочу, чтобы это значение было оценено или напечатано с использованием переменной var1. Я попробовал это, как показано выше, но не работает. пожалуйста помоги.
XYZ_Linux
2
Это действительно простой обходной путь, однако он не позволяет использовать хорошую опцию «make -j 4» для параллельного запуска процессов.
TabeaKischka
1
@TabeaKischka: действительно. Это не был « рекомендуемый » способ, но он более важен, если нужны некоторые функции, которые не предлагаются в Makefile, тогда можно вернуться к реализации bashи, таким образом, использовать эти функции. Цикл является одной из особенностей, для которой это можно продемонстрировать.
Виллем Ван Онсем
4

Может быть, вы можете использовать:

xxx:
    for i in `seq 1 4`; do ./a.out $$i; done;
Donwellus
источник
3

Вы можете использовать set -eв качестве префикса для цикла for. Пример:

all:
    set -e; for a in 1 2 3; do /bin/false; echo $$a; done

makeвыйдет сразу с кодом выхода <> 0.

Фредерик Тейхерт
источник
0

Хотя инструментарий таблиц GNUmake имеет истинный whileцикл (что бы это ни значило в программировании GNUmake с его двумя или тремя фазами выполнения), если вещь, которая необходима, - это итеративный список, есть простое решение с помощью interval. Для удовольствия мы также конвертируем числа в шестнадцатеричные:

include gmtt/gmtt.mk

# generate a list of 20 numbers, starting at 3 with an increment of 5
NUMBER_LIST := $(call interval,3,20,5)

# convert the numbers in hexadecimal (0x0 as first operand forces arithmetic result to hex) and strip '0x'
NUMBER_LIST_IN_HEX := $(foreach n,$(NUMBER_LIST),$(call lstrip,$(call add,0x0,$(n)),0x))

# finally create the filenames with a simple patsubst
FILE_LIST := $(patsubst %,./a%.out,$(NUMBER_LIST_IN_HEX))

$(info $(FILE_LIST))

Вывод:

./a3.out ./a8.out ./ad.out ./a12.out ./a17.out ./a1c.out ./a21.out ./a26.out ./a2b.out ./a30.out ./a35.out ./a3a.out ./a3f.out ./a44.out ./a49.out ./a4e.out ./a53.out ./a58.out ./a5d.out ./a62.out
Vroomfondel
источник
0

Простое, не зависящее от оболочки / платформы, чистое макро-решение ...

%sequence = $(if $(word ${1},${2}),$(wordlist 1,${1},${2}),$(call %sequence,${1},${2} $(words _ ${2})))

$(foreach i,$(call %sequence,10),$(info ./a.out ${i}))
rivy
источник
-1
#I have a bunch of files that follow the naming convention
#soxfile1  soxfile1.o  soxfile1.sh   soxfile1.ini soxfile1.txt soxfile1.err
#soxfile2  soxfile2.o   soxfile2.sh  soxfile2.ini soxfile2.txt soxfile2.err
#sox...        ....        .....         ....         ....        ....
#in the makefile, only select the soxfile1.. soxfile2... to install dir
#My GNU makefile solution follows:
tgt=/usr/local/bin/          #need to use sudo
tgt2=/backup/myapplication/  #regular backup 

install:
        for var in $$(ls -f sox* | grep -v '\.' ) ; \
        do \
                sudo  cp -f $$var ${TGT} ;     \
                      cp -f  $$var ${TGT2} ;  \
        done


#The ls command selects all the soxfile* including the *.something
#The grep command rejects names with a dot in it, leaving  
#My desired executable files in a list. 
Лесли Сатенштейн
источник