Makefile'de döngü nasıl yazılır?


217

Aşağıdaki komutları yürütmek istiyorum:

./a.out 1
./a.out 2
./a.out 3
./a.out 4
.
.
. and so on

Bu şeyi bir döngüde bir döngü olarak nasıl yazabilirim Makefile?


Benzer, ancak biraz daha genel: makefile'da çok
satırlı

Yanıtlar:


273

Bunu kullandığınızı varsaydığım gibi ./a.out, UNIX tipi bir platformdaysanız aşağıdakiler yapılacaktır.

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

Aşağıdaki gibi test edin:

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

üretir:

1
2
3
4

Daha büyük aralıklar için şunları kullanın:

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

Bu, 1'den 10'a kadar çıkışlar içerir while, yorumunuzda belirtildiği gibi çok daha geniş bir aralık için sonlandırma koşulunu 10'dan 1000'e değiştirin .

İç içe döngüler şu şekilde yapılabilir:

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

üreten:

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, gerçek bir dosya olması muhtemel olmayan bir hedef addır. Makefile kuralları bir hedefe ihtiyaç duyar. Sözdizimi hatasına gelince, bazı şeyler eksik - güncellemeye bakın.
paxdiablo

2
Kabuğunun ((...)) tanıdığını varsaydığınız için, neden ((i = 0; i <WHATEVER; ++ i)); yapmak ...; bitti mi?
İdelic

1
O Not seqyazabilir bu yüzden bir sayı dizisidir çoğu (hepsi?) Unix sistemleri üzerinde var üretir komut for number in ``seq 1 1000``; do echo $$number; done(seq komutu, iki değil her iki tarafında tek backtick koyun, bu doğru biçimlendirmek için nasıl bilmiyorum stackoverflow'un sözdizimini kullanıyor)
Suzanne Dupéron

3
Teşekkürler, ben döngü değişkeni başvurmak için çift $$ eksikti.
Leif Gruenwoldt

1
@Jonz: satır devam karakteri makenormalde her satıra ayrı bir alt kabukta çalışacak bir şey olarak davranmasıdır . Devam for number in 1 2 3 4 ; doetmeden, döngünün geri kalanı olmadan sadece (örneğin) bir alt kabuk çalıştırmaya çalışırdı . Bununla birlikte, formun tek bir satırı haline gelir while something ; do something ; done, bu tam bir ifadedir. $$Sorumun yanıtı burada edilir: stackoverflow.com/questions/26564825/... , görünüşte :-) bu çok cevap çıkarılan kodla
paxdiablo

265

GNU marka kullanıyorsanız, deneyebilirsiniz

SAYILAR = 1 2 3 4
yap:
        $ (foreach var, $ (NUMBERS),. / a.out $ (var);)

üretecek ve çalıştıracak

./a. çıkış 1; ./a. çıkış 2; ./a. çıkış 3; ./a. çıkış 4;

27
Bu cevap IMHO daha iyidir, çünkü herhangi bir kabuk kullanmasına gerek yoktur, saf bir dosyadır (GNU'ya özgü olsa bile).
Jocelyn delalande

7
noktalı virgül çok önemlidir, aksi takdirde yalnızca ilk yineleme yürütülür
Jeremy Leipzig

7
Bu çözüm, çıkış kodunu gizler ./a.out 1. ./a.out 2ne olursa olsun idam edilecek.
bobbogo

1
@Alexander: Bahsettiğiniz sınırlama hakkında ayrıntılı bilgi verebilir misiniz?
Idelic

1
@Edelic, evet. Aşağıdaki makefile ve shellscript için, shellscript çalışır (bağımsız değişkeni ls'ye iletir ), Makefile bir hata verir: make: execvp: / bin / sh: Bağımsız değişken listesi çok uzun Makefile: ix.io/d5L Shellscript: ix.io / d5M Bu, işte karşılaştığım foreach işlevinde, alışılmadık derecede uzun bir dosya adı listesi (uzun yollarla da) bir komuta iletildiğinde bir sorundur. Bir çözüm bulmak zorundaydık.
Alexander

107

Kullanım yapmak IMHO başlıca nedenidir -jbayrağı. make -j5aynı anda 5 kabuk komutu çalıştırır. Bu, 4 CPU'nuz varsa ve herhangi bir makefile için iyi bir test varsa iyidir.

Temel olarak, aşağıdaki gibi bir şey görmek istiyorsunuz:

.PHONY: all
all: job1 job2 job3

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

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

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

Bu -jdostça (iyi bir işaret). Kazan plakasını tespit edebilir misiniz? Yazabiliriz:

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

aynı etki için (evet, bu biraz daha kompakt hale gelince, önceki formülasyonla aynıdır ).

Komut satırında bir sınır belirleyebilmeniz için parametrelendirmenin bir başka biti ( makeiyi aritmetik makroları olmadığı için sıkıcı , bu yüzden burada hile yapacağım ve kullanacağım $(shell ...))

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

Bunu varsayılan olarak 1000 make -j5 LAST=550ile çalıştırırsınız LAST.


7
Bobbogo'nun bahsettiği için bu kesinlikle en iyi cevaptır ( -j ).
dbn

Bunun .PHONY soneklerini kullanmasının belirli bir nedeni var mı? Herhangi bir şey için gerekli mi?
Seb

6
@seb: Olmadan .PHONY: all, make adlı bir dosyayı arayacaktır all. Böyle bir dosya varsa, o zaman dosyanın son değiştirilme zamanını kontrol eder ve makefile neredeyse istediğinizi yapmayacaktır. .PHONYDeklarasyon markasını söyler allsembolik bir hedeftir. Bu nedenle Make, allhedefi daima güncelliğini yitirir. Mükemmel. El kitabına bakın.
bobbogo

4
Bu satır nasıl çalışır: $ {JOBS}: job%: İkinci noktalı virgül ne işe yarıyor? Gnu.org/software/make/manual/make.htm adresinde
Joe

2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (Sanırım 2. * iki nokta * ne demek istediniz ). Bunları hoş olmayan (IMHO) Desen Kurallarıyla karıştırmayın . Statik desen kuralları hedefleri listesi biri tarafından eşleştirilebilir zaman gerçekten yararlı olan marka 'noddy desenleri (aynı bağımlılıkları için geçerlidir) s. Burada %, kuralda eşleşen her şeyin $*tarifte olduğu gibi kullanılabilir olması için birini kullanıyorum.
bobbogo

22

Sorunun birkaç yaşında olduğunu anlıyorum, ancak bu yazı, yukarıdakilerden farklı bir yaklaşım gösterdiğinden ve kabuk işlemlerine veya geliştiricinin sabit kodlanmış bir şemaya gereksinim duymadığından hala birileri için yararlı olabilir. sayısal değerler dizesi.

$ (eval ....) yerleşik makrosu sizin dostunuzdur. Veya en azından olabilir.

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)

Bu basit bir örnek. Büyük değerler için güzel ölçeklenmez - işe yarar, ancak ITERATE_COUNT dizesi her yineleme için 2 karakter (boşluk ve nokta) artacağından, binlere yükseldikçe, kelimeleri saymak giderek daha uzun sürer. Yazıldığı gibi, iç içe yinelemeyi işlemez (bunu yapmak için ayrı bir yineleme işlevine ve sayacına ihtiyacınız olacaktır). Bu tamamen gnu yapmak, hiçbir kabuk gereksinimi (Açıkçası OP her seferinde bir program çalıştırmak istiyordu - burada, sadece bir mesaj gösteriyorum). ITERATE içindeki if değeri 0 değerini yakalamaya yöneliktir, çünkü $ (word ...) aksi halde hata verir.

Sayaç olarak hizmet etmek için büyüyen dize kullanıldığından, $ (kelimeler ...) yerleşiminin arapça bir sayı sağlayabildiğini, ancak bu işlemin matematik işlemlerini başka şekilde desteklemediğini unutmayın (Bir şeye 1 + 1 atayamazsınız ve 2, kabuktan sizin için bir şey çağırmıyorsanız veya eşit derecede kıvrık bir makro işlemi kullanmıyorsanız). Bu bir INCREMENTAL sayaç için harika çalışıyor, ancak bir DECREMENT sayaç için çok iyi değil.

Bunu kendim kullanmıyorum, ancak son zamanlarda, bazı kütüphaneleri eklediğinizde DİĞER kütüphanelere getirmeyi bilmeniz gereken çok ikili, çok kütüphaneli bir yapı ortamında kütüphane bağımlılıklarını değerlendirmek için özyinelemeli bir işlev yazmam gerekiyordu. Kendisinin başka bağımlılıkları vardır (bazıları yapı parametrelerine bağlı olarak değişir) ve yukarıdakine benzer bir $ (eval) ve counter yöntemi kullanıyorum (benim durumumda, sayaç bir şekilde sonsuzluğa girmediğimizden emin olmak için kullanılır döngü ve ayrıca ne kadar yineleme gerektiğini rapor etmek için bir teşhis olarak).

OP'nin Q: $ (eval ...) için önemli olmasa da, hiçbir şeye değmeyecek bir şey, bir değişkenin bir makro türü olduğunda ( =), acil bir atamaya karşı (: ile başlatıldı: =). Bir değişkeni kendi atamasında kullanmak istediğiniz zamanlar vardır ve $ (eval ...) bunu yapmanızı sağlar. Burada dikkate alınması gereken önemli nokta, eval'ü çalıştırdığınızda değişkenin çözülmesidir ve çözülen kısmın artık makro olarak ele alınmamasıdır. Ne yaptığınızı biliyorsanız ve kendisine bir ödevin RHS'sinde bir değişken kullanmaya çalışıyorsanız, bu genellikle yine de olmasını istediğiniz şeydir.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Mutlu olun.


20

Platformlar arası destek için, komut ayırıcıyı (aynı satırda birden fazla komut yürütmek için) yapılandırılabilir yapın.

Örneğin bir Windows platformunda MinGW kullanıyorsanız komut ayırıcısı &:

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

Bu, birleştirilmiş komutları tek bir satırda yürütür:

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

Başka yerlerde de belirtildiği gibi, bir * nix platform kullanımı CMDSEP = ;.


7

Bu gerçekten sorunun saf bir cevabı değil, bu tür problemleri çözmek için akıllı bir yol:

karmaşık bir dosya yazmak yerine, örneğin aşağıdaki gibi bir bash betiğine denetim yetkisi verin: makefile

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

ve script.sh şöyle görünür:

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

Makefile yazarken bir adımda takıldım. Aşağıdaki kod var: set_var: @ NUM = 0; [[$$ NUM <1]]; \ echo "Ben buradayım"; \ echo $$ NUM dökümü $$ {NUM} .txt; \ var = "SSA_CORE $$ {NUM} _MAINEXEC"; \ echo $$ var; \ var1 = eval echo \$${$(var)}; \ echo $$ var1; \ ((NUM = NUM ​​+ 1)); \ done all: set_var burada SSA_CORE0_MAINEXEC zaten ayarlanmış bir ortam değişkenidir.Bu yüzden bu değerin var1 değişkeni kullanılarak değerlendirilmesini veya yazdırılmasını istiyorum. Yukarıda gösterildiği gibi denedim ama çalışmıyor. lütfen yardım et.
XYZ_Linux

2
Bu gerçekten kolay bir çözümdür, ancak süreçlerin paralel çalışmasını sağlamak için güzel "make -j 4" seçeneğini kullanmanızı engeller.
TabeaKischka

1
@TabeaKischka: gerçekten. " Tavsiye edilen " yol değildi, ancak bir Makefile tarafından sunulmayan bazı özelliklere ihtiyaç duyuyorsa, daha sonra bir uygulamadaki bir uygulamaya geri dönebilir bashve böylece özellikleri kullanabilir. Döngü, bunun gösterilebildiği özelliklerden biridir.
Willem Van Onsem

4

Belki şunları kullanabilirsiniz:

xxx:
    for i in `seq 1 4`; do ./a.out $$i; done;

3

set -eFor-loop için önek olarak kullanabilirsiniz . Misal:

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

makehemen bir çıkış kodu ile çıkacaktır <> 0.


0

GNUmake tablo araç takımının gerçek bir whiledöngüsü olmasına rağmen (GNUmake programlamada iki veya üç yürütme aşamasıyla ne anlama gelirse), gerekli olan şey yinelemeli bir listeyse, basit bir çözüm vardır interval. Eğlenmek için sayıları onaltılık biçime dönüştürüyoruz:

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

Çıktı:

./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

Kabuktan / platformdan bağımsız, basit bir makro çözüm ...

%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. 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.