จะเขียนลูปใน Makefile ได้อย่างไร


217

ฉันต้องการรันคำสั่งต่อไปนี้:

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

วิธีการเขียนสิ่งนี้เป็นวงใน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 เป็น 1,000 สำหรับช่วงที่ใหญ่กว่ามากตามที่ระบุไว้ในความคิดเห็นของคุณ

ลูปซ้อนสามารถทำได้ดังนี้:

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)); ทำ ... ; เสร็จแล้ว
Idelic

1
โปรดทราบว่าseqคำสั่งที่สร้างลำดับของตัวเลขนั้นมีอยู่ในระบบ unix ส่วนใหญ่ (ทั้งหมด?) ดังนั้นคุณสามารถเขียนfor number in ``seq 1 1000``; do echo $$number; done(ใส่ backtick เดียวที่ด้านข้างของแต่ละคำสั่ง seq ไม่ใช่สองฉันไม่ทราบวิธีการจัดรูปแบบนี้อย่างถูกต้อง ใช้ไวยากรณ์ของ stackoverflow)
Suzanne Dupéron

3
ขอขอบคุณฉันไม่ได้ใช้ double $$ เพื่ออ้างอิงตัวแปร for loop
Leif Gruenwoldt

1
@Jonz: อักขระที่มีต่อเนื่องของบรรทัดนั้นเป็นเพราะmakeโดยทั่วไปถือว่าแต่ละบรรทัดเป็นสิ่งที่เรียกใช้ในsub-shell แยก หากไม่มีการต่อเนื่องมันจะพยายามเรียกใช้ subshell ด้วย (ตัวอย่าง) for number in 1 2 3 4 ; doโดยไม่มีลูปที่เหลือ ด้วยมันจะกลายเป็นบรรทัดเดียวของแบบฟอร์มได้อย่างมีประสิทธิภาพwhile something ; do something ; doneซึ่งเป็นคำสั่งที่สมบูรณ์ $$คำถามเป็นคำตอบที่นี่: stackoverflow.com/questions/26564825/...กับรหัสที่สกัดจากดูเหมือนว่าคำตอบนี้มาก :-)
paxdiablo

265

ถ้าคุณใช้ GNU ทำคุณสามารถลอง

NUMBERS = 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-specific)
Jocelyn delalande

7
อัฒภาคมีความสำคัญเป็นอย่างอื่นเฉพาะการทำซ้ำครั้งแรกเท่านั้นที่จะดำเนินการ
Jeremy Leipzig

7
./a.out 1วิธีการแก้ปัญหานี้จะซ่อนรหัสทางออกของ ./a.out 2จะถูกดำเนินการโดยไม่คำนึงถึง
bobbogo

1
@Alexander: คุณช่วยอธิบายรายละเอียดเกี่ยวกับข้อ จำกัด ที่คุณอ้างถึงได้อย่างไร
Idelic

1
@Idelic ใช่ สำหรับ makefile และ shellscript ต่อไปนี้ shellscript ทำงาน (ส่งผ่านอาร์กิวเมนต์ไปยัง ls) ในขณะที่ Makefile ให้ข้อผิดพลาด: make: execvp: / bin / sh: รายการอาร์กิวเมนต์ยาวเกินไป Makefile: ix.io/d5L Shellscript: ix.io / d5M นี่เป็นปัญหาในฟังก์ชั่น foreach ที่ฉันพบในที่ทำงานเมื่อรายชื่อไฟล์ที่ยาวผิดปกติ (ที่มีพา ธ ยาวเกินไป) ถูกส่งไปยังคำสั่ง เราต้องหาวิธีแก้ปัญหา
Alexander

107

เหตุผลสำคัญที่จะทำให้การใช้งาน IMHO เป็น-jธง make -j5จะเรียกใช้ 5 คำสั่งเชลล์ในครั้งเดียว นี่เป็นสิ่งที่ดีถ้าคุณมี 4 ซีพียูพูดและทำการทดสอบที่ดีของไฟล์ใด ๆ

โดยทั่วไปคุณต้องการให้เห็นสิ่งที่ชอบ:

.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เป็น 1,000


7
นี่เป็นคำตอบที่ดีที่สุดอย่างแน่นอนเพราะเหตุผลที่ bobbogo พูดถึง ( -j )
dbn

มีเหตุผลใดที่ทำให้ใช้. พอนีต่อท้าย? พวกเขาต้องการอะไรหรือไม่?
Seb

6
@seb: โดยไม่ต้องให้จะมองหาไฟล์ที่เรียกว่า.PHONY: all allหากไฟล์ดังกล่าวมีอยู่ให้ทำการตรวจสอบไฟล์ล่าสุดที่เปลี่ยนแปลงและ makefile นั้นแทบจะไม่ทำสิ่งที่คุณต้องการแน่นอน การ.PHONYประกาศบอกว่าallเป็นเป้าหมายที่เป็นสัญลักษณ์ ทำให้จะพิจารณาallเป้าหมายที่จะล้าสมัยเสมอ สมบูรณ์ ดูคู่มือ
bobbogo

4
บรรทัดนี้ทำงานอย่างไร: $ {JOBS}: job%: เครื่องหมายอัฒภาคตัวที่สองคืออะไร? ฉันไม่เห็นอะไรเลยในgnu.org/software/make/manual/make.htm
Joe

2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (ฉันคิดว่าคุณหมายถึงลำไส้ใหญ่ที่สองคือ * สำหรับ ) อย่าสับสนพวกเขาด้วยการยกเลิกการดี (IMHO) กฎรูปแบบ กฎรูปแบบคงที่มีประโยชน์จริง ๆ เมื่อใดก็ตามที่รายการของเป้าหมายสามารถจับคู่โดยหนึ่งในรูปแบบของการทำ noddy (เช่นเดียวกับการอ้างอิง) ที่นี่ฉันใช้เพียงเพื่อความสะดวกในสิ่งที่ตรงกับ%ในกฎที่มีอยู่$*ในสูตร
bobbogo

22

ฉันรู้ว่าคำถามมีอายุหลายปี แต่โพสต์นี้อาจยังใช้กับใครบางคนเพราะมันแสดงให้เห็นถึงวิธีการที่แตกต่างจากข้างต้นและไม่พึ่งพาการดำเนินการของเชลล์ทั้งสองและไม่ต้องการให้นักพัฒนาสร้าง schpeel สตริงของค่าตัวเลข

builtin $ built-in มาโครเป็นเพื่อนของคุณ หรืออาจเป็นอย่างน้อย

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 ล้วนๆ, ไม่มีข้อกำหนดของเชลล์ (แม้ว่าชัด ๆ ว่า OP ต้องการให้รันโปรแกรมทุกครั้ง - ที่นี่, ฉันแค่แสดงข้อความ) ถ้าภายใน ITERATE มีวัตถุประสงค์เพื่อจับค่า 0 เนื่องจาก $ (คำ ... ) จะผิดพลาดเป็นอย่างอื่น

โปรดสังเกตว่ามีการใช้สตริงที่เพิ่มขึ้นเพื่อทำหน้าที่เป็นตัวนับเนื่องจากบิวด์ $ (คำ ... ... ) สามารถจัดให้มีการนับภาษาอาหรับ แต่สิ่งที่ทำไม่สนับสนุนการดำเนินการทางคณิตศาสตร์เป็นอย่างอื่น (คุณไม่สามารถกำหนด 1 + 1 ให้กับบางสิ่ง นอกเสียจากว่าคุณกำลังเรียกบางอย่างจากเชลล์เพื่อให้สำเร็จหรือใช้การทำงานของแมโครที่ซับซ้อนเท่ากัน) วิธีนี้ใช้งานได้ดีสำหรับตัวนับ INCREMENTAL แต่ก็ไม่ค่อยดีสำหรับ DECREMENT

ฉันไม่ได้ใช้สิ่งนี้ด้วยตัวเอง แต่เมื่อเร็ว ๆ นี้ฉันจำเป็นต้องเขียนฟังก์ชันแบบเรียกซ้ำเพื่อประเมินการพึ่งพาของไลบรารีในสภาพแวดล้อมการสร้างแบบ multi-binary, multi-library ที่คุณจำเป็นต้องรู้เพื่อนำมาไว้ในห้องสมุดอื่น ๆ ตัวเองมีการอ้างอิงอื่น ๆ (บางอย่างแตกต่างกันไปขึ้นอยู่กับพารามิเตอร์การสร้าง) และฉันใช้วิธี $ (eval) และตัวนับคล้ายกับข้างต้น (ในกรณีของฉันตัวนับใช้เพื่อให้แน่ใจว่าเราจะไม่สิ้นสุด วนรอบและยังเป็นการวินิจฉัยเพื่อรายงานจำนวนการวนซ้ำที่จำเป็น)

อย่างอื่นไม่มีค่าอะไร แต่ไม่มีนัยสำคัญต่อ Q ของ $ OP (eval ... ) ให้วิธีการหลีกเลี่ยงความเกลียดชังภายในของการอ้างอิงวงกลมซึ่งเป็นสิ่งที่ดีและดีที่จะบังคับใช้เมื่อตัวแปรเป็นประเภทแมโคร (intialized ด้วย =), กับการมอบหมายทันที (เริ่มต้นด้วย: =) มีหลายครั้งที่คุณต้องการใช้ตัวแปรภายในการมอบหมายของตนเองและ $ (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 &

เป็นที่กล่าวถึงที่อื่น ๆ CMDSEP = ;ในการใช้งานแพลตฟอร์มระวัง


7

นี่ไม่ใช่คำตอบที่แท้จริงของคำถาม แต่เป็นวิธีที่ชาญฉลาดในการแก้ไขปัญหาดังกล่าว:

แทนที่จะเขียนไฟล์ที่ซับซ้อนเพียงแค่มอบหมายการควบคุมให้ตัวอย่างเช่นสคริปต์ทุบตีเช่น: 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]]; ทำ \ 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 ที่นี่ SSA_CORE0_MAINEXEC เป็นตัวแปรสภาพแวดล้อมที่มีการตั้งค่าไว้แล้วดังนั้นฉันต้องการให้ค่านั้นได้รับการประเมินหรือพิมพ์โดยใช้ตัวแปร var1 ฉันลองมันตามที่แสดงด้านบน แต่ไม่ทำงาน กรุณาช่วย.
XYZ_Linux

2
นี่เป็นวิธีแก้ปัญหาที่ง่าย แต่ก็ทำให้คุณไม่สามารถใช้ตัวเลือก "make -j 4" ที่ดีเพื่อให้กระบวนการทำงานแบบขนาน
TabeaKischka

1
@TabeaKischka: แน่นอน มันไม่ใช่วิธีที่ " แนะนำ " แต่มีความหมายมากกว่านี้หากต้องการคุณสมบัติบางอย่างที่ไม่ได้นำเสนอโดย Makefile ดังนั้นจึงสามารถย้อนกลับไปbashใช้งานได้ การวนซ้ำเป็นหนึ่งในคุณสมบัติที่สามารถแสดงให้เห็นได้
Willem Van Onsem


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. 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.