Як записати цикл у 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

1
qwert - це лише цільове ім'я, яке навряд чи буде справжнім файлом. Правилам Makefile потрібна ціль. Що стосується синтаксичної помилки, то вам не вистачає деяких матеріалів - див. Оновлення.
paxdiablo

2
Оскільки ви припускаєте, що його оболонка розпізнає ((...)), то чому б не використати набагато простіше для ((i = 0; i <WHATEVER; ++ i)); робити ...; зробили?
Іделік

1
Зауважте, що seqкоманда, яка генерує послідовність чисел, існує у більшості (усіх?) Unix-систем, тому ви можете писати for number in ``seq 1 1000``; do echo $$number; done(Покладіть по одній зворотному коду з кожної сторони команди seq, а не дві, я не знаю, як це правильно відформатувати з використанням синтаксису
stackoverflow

3
Дякую, мені не вистачало подвійного $$ для посилання на змінну циклу.
Лейф Грюнвольдт

1
@Jonz: характер продовження рядка полягає в тому, що makeзазвичай розглядає кожен рядок як річ, яку слід запускати в окремій підкожці . Без продовження, він би спробував запустити нижню for number in 1 2 3 4 ; doчастину корпусу просто (наприклад) , без решти циклу. З його допомогою воно фактично перетворюється на єдиний рядок форми while something ; do something ; done, що є повним твердженням. На $$питання тут: stackoverflow.com/questions/26564825/… , з коду, здавалося б, витягнуто з цієї самої відповіді :-)
paxdiablo

265

Якщо ви використовуєте GNU make, ви можете спробувати

ЧИСЛИ = 1 2 3 4
Зроби це:
        $ (foreach var, $ (NUMBERS),. / a.out $ (var);)

який буде генерувати та виконувати

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

27
Ця відповідь краще IMHO, тому що вона не потребує використання жодної оболонки, це чистий makefile (навіть якщо це специфічно для GNU).
Jocelyn delalande

7
крапка з комою має вирішальне значення, інакше виконається лише перша ітерація
Джеремі Лейпциг

7
Це рішення приховує код виходу ./a.out 1. ./a.out 2буде виконано незалежно.
bobbogo

1
@Alexander: чи могли б ви детальніше розглянути питання про обмеження, яке ви маєте на увазі?
Іделік

1
@Idelic, так. Для наступного makefile та оболонки shellscript працює (передає аргумент до ls), тоді як Makefile видає помилку: make: execvp: / bin / sh: Аргумент занадто довгий Makefile: ix.io/d5L Shellscript: ix.io / d5M Ця проблема у функції foreach, з якою я стикався на роботі, коли команді було передано незвично довгий список імен (з довгими шляхами). Треба було знайти обхід.
Олександр

107

Основна причина використання роблять на мою думку це -jпрапор. make -j5запустить одразу 5 команд оболонки. Це добре, якщо у вас є 4 процесора, скажімо, і хороший тест будь-якого makefile.

В основному, ви хочете, щоб побачити щось на зразок:

.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не має жодних хороших арифметичних макросів, тому я тут обманюю і використовую $(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.


7
Це, безумовно, найкраща відповідь, з тієї причини, що Боббого згадав ( -j ).
dbn

Якась конкретна причина цього використовує суфікси .PHONY? Вони потрібні для чого-небудь?
Себ

6
@seb: Без .PHONY: all, make буде шукати файл з назвою all. Якщо такий файл існує, перевірте останній час зміни цього файлу, і makefile майже точно не виконає те, що ви задумали. .PHONYДекларація говорить зробити , що allє символічною мішенню. Таким чином, Make вважатиме allціль завжди застарілою. Ідеально. Дивіться посібник.
bobbogo

4
Як працює цей рядок: $ {JOBS}: робота%: Для чого складається друга крапка з комою? Я нічого не бачив у gnu.org/software/make/manual/make.htm
Joe

2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (я думаю, ти мав на увазі для чого 2-я двокрапка * ). Не плутайте їх з неприємними (IMHO) правилами шаблону . Правила статичної картини дуже корисні, коли список цілей може бути узгоджений за однією з чітких моделей make (це стосується залежностей). Тут я використовую лише для зручності те, що все, що відповідає %правилу, доступне, як і $*в рецепті.
Боббого

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, не вимагає оболонки (хоча очевидно, що ОП намагався запускати програму кожен раз - ось я просто показую повідомлення). Якщо в межах ITERATE призначено ввести значення 0, оскільки $ (word ...) помилиться інакше.

Зауважте, що зростаюча рядок, яка служить лічильником, використовується тому, що вбудований $ (слова ...) може забезпечити арабську кількість, але це робить інакше не підтримує математичні операції (Ви не можете призначити 1 + 1 чомусь і отримати 2, якщо ви не викликаєте щось із оболонки, щоб виконати це за вас, або не використовуєте однаково сумісну операцію макросу). Це чудово підходить для лічильника НЕПРАВИЛЬНОГО, але не дуже вдалого для ВИЗНАЧЕННЯ.

Я цим самим не користуюся, але останнім часом мені довелося написати рекурсивну функцію для оцінки залежностей бібліотеки в багатобінарному середовищі побудови кількох бібліотек, куди потрібно знати, щоб залучити ІНШІ бібліотеки, коли ви включаєте якусь бібліотеку, яка сама по собі має інші залежності (деякі з яких змінюються залежно від параметрів збірки), і я використовую метод $ (eval) та counter, подібний до вищевказаного (у моєму випадку лічильник використовується для того, щоб ми якось не переходили у нескінченний цикл, а також як діагностика, щоб повідомити, скільки ітерації було потрібно).

Щось ще нічого не варте, хоч і не має значення для Q: $ (eval ...) OP дає метод для обходу внутрішнього відхилення make до кругових посилань, що все добре і добре, щоб застосувати, коли змінна є макро-типом (ініціалізований з =), проти негайного призначення (ініціалізованого з: =). Бувають випадки, коли ви хочете мати можливість використовувати змінну у своєму власному призначенні, і $ (eval ...) дозволить вам це зробити. Тут важливо враховувати, що під час запуску eval змінна стає вирішеною, і та частина, яка розв'язана, більше не трактується як макрос. Якщо ви знаєте, що ви робите, і ви намагаєтесь використовувати змінну на RHS присвоєння собі, це, як правило, те, що ви хочете все-таки статися.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Щасливого макіяжу.


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; поки [[$$ NUM <1]]; do \ echo "Я тут"; \ echo $$ NUM дамп $$ {NUM} .txt; \ var = "SSA_CORE $$ {NUM} _MAINEXEC"; \ echo $$ var; \ var1 = eval echo \$${$(var)}; \ echo $$ var1; \ ((NUM = NUM ​​+ 1)); \ зробили все: set_var тут 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;

3

Ви можете використовувати set -eяк префікс for-loop. Приклад:

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

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}))

-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. 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.