Làm thế nào để viết vòng lặp trong Makefile?


217

Tôi muốn thực hiện các lệnh sau:

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

Làm thế nào để viết điều này như một vòng lặp trong một Makefile?


Tương tự, nhưng chung chung hơn một chút: Các lệnh bash Multiline trong makefile
nobar

Câu trả lời:


273

Những điều sau đây sẽ làm điều đó nếu, như tôi giả sử khi bạn sử dụng ./a.out, bạn đang ở trên nền tảng loại UNIX.

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

Kiểm tra như sau:

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

sản xuất:

1
2
3
4

Đối với phạm vi lớn hơn, sử dụng:

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

Điều này bao gồm 1 đến 10, chỉ cần thay đổi whileđiều kiện kết thúc từ 10 thành 1000 cho phạm vi lớn hơn nhiều như được nêu trong nhận xét của bạn.

Các vòng lặp lồng nhau có thể được thực hiện như vậy:

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

sản xuất:

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 chỉ là một tên mục tiêu không chắc là một tập tin thực sự. Quy tắc Makefile cần một mục tiêu. Đối với lỗi cú pháp, bạn đang thiếu một số nội dung - xem cập nhật.
paxdiablo

2
Vì bạn giả sử trình bao của anh ta nhận ra ((...)), tại sao không sử dụng đơn giản hơn nhiều cho ((i = 0; i <WHATEVER; ++ i)); làm ...; làm xong ?
Idelic

1
Lưu ý rằng seqlệnh tạo ra một chuỗi số tồn tại trên hầu hết các hệ thống unix (tất cả?), Vì vậy bạn có thể viết for number in ``seq 1 1000``; do echo $$number; done(Đặt một backtick vào mỗi bên của lệnh seq, không phải hai, tôi không biết cách định dạng chính xác sử dụng cú pháp của stackoverflow)
Suzanne Dupéron

3
Cảm ơn, tôi đã thiếu $$ đôi để tham chiếu biến vòng lặp for.
Leif Gruenwoldt

1
@Jonz: ký tự tiếp tục dòng là vì makethông thường coi mỗi dòng là một thứ để chạy trong một vỏ con riêng biệt . Nếu không tiếp tục, nó sẽ cố chạy một subshell chỉ bằng (ví dụ) for number in 1 2 3 4 ; do, mà không có phần còn lại của vòng lặp. Với nó, nó thực sự trở thành một dòng duy nhất của biểu mẫu while something ; do something ; done, đó là một tuyên bố hoàn chỉnh. Câu $$hỏi được trả lời tại đây: stackoverflow.com/questions/26564825/ , với mã dường như được trích xuất từ ​​chính câu trả lời này :-)
paxdiablo

265

Nếu bạn đang sử dụng GNU, bạn có thể thử

SỐ = 1 2 3 4
làm đi:
        $ (foreach var, $ (NUMBERS) ,. / a.out $ (var);)

cái sẽ tạo và thực thi

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

27
Câu trả lời này là IMHO tốt hơn, vì nó không yêu cầu sử dụng bất kỳ shell nào, đó là tệp thực hiện thuần túy (ngay cả khi nó là đặc trưng cho GNU).
Jocelyn delalande

7
dấu chấm phẩy rất quan trọng nếu không chỉ lần lặp đầu tiên sẽ thực hiện
Jeremy Leipzig

7
Giải pháp này ẩn mã thoát của ./a.out 1. ./a.out 2sẽ được thực hiện bất kể.
bobbogo

1
@Alexander: bạn có thể giải thích về giới hạn nào bạn đang đề cập đến không?
Idelic

1
@Idelic, vâng. Đối với tệp Makefile và shellscript sau, shellscript hoạt động (chuyển đối số cho ls), trong khi Makefile đưa ra lỗi: make: execvp: / bin / sh: Danh sách đối số quá dài Makefile: ix.io/d5L Shellscript: ix.io / d5M Đây là một vấn đề trong chức năng foreach mà tôi gặp phải khi làm việc, khi một danh sách tên tệp dài bất thường (cũng có đường dẫn dài) được chuyển đến một lệnh. Chúng tôi đã phải tìm một cách giải quyết.
Alexander

107

THE lý do chính để sử dụng làm IMHO là -jcờ. make -j5sẽ chạy 5 lệnh shell cùng một lúc. Điều này là tốt nếu bạn có 4 CPU nói, và một bài kiểm tra tốt về bất kỳ makefile nào.

Về cơ bản, bạn muốn thực hiện để xem một cái gì đó như:

.PHONY: all
all: job1 job2 job3

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

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

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

Đây là -jthân thiện (một dấu hiệu tốt). Bạn có thể nhận ra tấm nồi hơi? Chúng tôi có thể viết:

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

cho tác dụng tương tự (có, điều này cũng giống như việc xây dựng trước càng xa càng làm cho là có liên quan, nhỏ gọn hơn chỉ là một chút).

Thêm một chút tham số hóa để bạn có thể chỉ định giới hạn cho dòng lệnh (tẻ nhạt vì makekhông có bất kỳ macro số học tốt nào, vì vậy tôi sẽ gian lận ở đây và sử dụng $(shell ...))

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

Bạn chạy cái này với make -j5 LAST=550, LASTmặc định là 1000.


7
Đây chắc chắn là câu trả lời tốt nhất, vì lý do mà bobbogo đã đề cập ( -j ).
dbn

Bất kỳ lý do cụ thể này sử dụng hậu tố .PHONY? Họ có cần thiết cho bất cứ điều gì?
Seb

6
@seb: Nếu không .PHONY: all, make sẽ tìm một tập tin gọi là all. Nếu một tệp như vậy tồn tại, hãy thực hiện kiểm tra lần thay đổi cuối cùng của tệp đó và tệp thực hiện gần như chắc chắn sẽ không làm những gì bạn dự định. Các .PHONYtuyên bố nói rằng làm alllà một mục tiêu mang tính biểu tượng. Do đó, Make sẽ xem xét allmục tiêu luôn luôn lỗi thời. Hoàn hảo. Xem hướng dẫn.
bobbogo

4
Dòng này hoạt động như thế nào: $ {JOBS}: job%: Dấu chấm phẩy thứ hai dùng để làm gì? Tôi đã không thấy bất cứ điều gì trong gnu.org/software/make/manual/make.htm
Joe

2
@JoeS gnu.org/software/make/manual/make.html#Static-Potype (Tôi nghĩ bạn có nghĩa là dấu hai chấm * cho * là gì ). Đừng nhầm lẫn chúng với Quy tắc mẫu không đẹp (IMHO) . Quy tắc mô hình tĩnh là thực sự hữu ích khi danh sách các mục tiêu có thể được khớp với một trong làm là mẫu Noddy (cùng áp dụng cho các phụ thuộc). Ở đây tôi sử dụng một thứ chỉ để thuận tiện cho bất cứ điều gì phù hợp với %quy tắc có sẵn như $*trong công thức.
bobbogo

22

Tôi nhận ra câu hỏi đã được vài năm tuổi, nhưng bài đăng này vẫn có thể được sử dụng cho một người nào đó vì nó thể hiện một cách tiếp cận khác với ở trên, và không phụ thuộc vào các hoạt động vỏ cũng như không cần nhà phát triển tìm ra mã hóa cứng chuỗi các giá trị số.

macro dựng sẵn $ (eval ....) là bạn của bạn. Hoặc có thể ít nhất.

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)

Đó là một ví dụ đơn giản. Nó sẽ không mở rộng quy mô cho các giá trị lớn - nó hoạt động, nhưng vì chuỗi ITERATE_COUNT sẽ tăng thêm 2 ký tự (dấu cách và dấu chấm) cho mỗi lần lặp, khi bạn lên đến hàng nghìn, sẽ mất nhiều thời gian hơn để đếm từ. Như đã viết, nó không xử lý phép lặp lồng nhau (bạn cần một hàm lặp và bộ đếm riêng biệt để làm như vậy). Đây hoàn toàn là một yêu cầu hoàn hảo, không yêu cầu hệ vỏ (mặc dù rõ ràng OP đang tìm cách chạy một chương trình mỗi lần - ở đây, tôi chỉ hiển thị một thông báo). Nếu trong ITERATE được dự định bắt giá trị 0, vì $ (từ ...) sẽ lỗi khác.

Lưu ý rằng chuỗi tăng trưởng để phục vụ như một bộ đếm được sử dụng vì tích hợp $ (từ ...) có thể cung cấp số đếm tiếng Ả Rập, nhưng điều đó không hỗ trợ các hoạt động toán học (Bạn không thể gán 1 + 1 cho thứ gì đó và nhận 2, trừ khi bạn gọi một cái gì đó từ trình bao để hoàn thành nó cho bạn hoặc sử dụng một thao tác macro phức tạp như nhau). Điều này hoạt động rất tốt cho một bộ đếm TĂNG CƯỜNG, tuy nhiên không tốt cho một bộ KHAI THÁC.

Tôi không sử dụng cái này cho mình, nhưng gần đây, tôi cần phải viết một hàm đệ quy để đánh giá các phụ thuộc thư viện trên một môi trường xây dựng đa thư viện, đa nhị phân, nơi bạn cần biết để đưa vào các thư viện KHÁC khi bạn bao gồm một số thư viện bản thân nó có các phụ thuộc khác (một số thay đổi tùy thuộc vào tham số xây dựng) và tôi sử dụng phương thức $ (eval) và bộ đếm tương tự như trên (trong trường hợp của tôi, bộ đếm được sử dụng để đảm bảo chúng ta không đi vào vô tận vòng lặp, và cũng như một chẩn đoán để báo cáo số lần lặp là cần thiết).

Một cái gì đó không có giá trị gì, mặc dù không có ý nghĩa đối với Q: $ (eval ...) của OP cung cấp một phương pháp để tránh sự ghê tởm nội bộ đối với các tham chiếu vòng tròn, tất cả đều tốt và tốt để thực thi khi một biến là một loại macro (được định nghĩa bằng =), so với một nhiệm vụ ngay lập tức (khởi tạo với: =). Đôi khi bạn muốn có thể sử dụng một biến trong phép gán riêng của nó và $ (eval ...) sẽ cho phép bạn làm điều đó. Điều quan trọng cần xem xét ở đây là tại thời điểm bạn chạy eval, biến được giải quyết và phần được giải quyết không còn được coi là macro. Nếu bạn biết những gì bạn đang làm và bạn đang cố gắng sử dụng một biến trên RHS của một bài tập cho chính nó, thì đây thường là những gì bạn muốn xảy ra.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Chúc mừng


20

Để hỗ trợ đa nền tảng, hãy phân tách lệnh (để thực thi nhiều lệnh trên cùng một dòng).

Ví dụ: nếu bạn đang sử dụng MinGW trên nền tảng Windows, thì dấu phân cách lệnh là &:

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

Điều này thực thi các lệnh được nối trong một dòng:

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

Như đã đề cập ở nơi khác, trên nền tảng * nix sử dụng CMDSEP = ;.


7

Đây thực sự không phải là một câu trả lời thuần túy cho câu hỏi, mà là một cách thông minh để giải quyết những vấn đề như vậy:

thay vì viết một tệp phức tạp, chỉ cần ủy quyền kiểm soát ví dụ tập lệnh bash như: makefile

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

và script.sh trông giống như:

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

Tôi đã bị kẹt ở một bước trong khi viết Makefile. Tôi có mã sau: set_var: @ NUM = 0; trong khi [[$$ NUM <1]]; làm \ echo "Tôi ở đây"; \ echo $$ NUM đổ $$ {NUM} .txt; \ var = "SSA_CORE $$ {NUM} _MAINEXEC"; \ echo $$ var; \ var1 =eval echo \$${$(var)} ; \ echo $$ var1; \ ((NUM = NUM ​​+ 1)); \ xong tất cả: set_var ở đây SSA_CORE0_MAINEXEC là một biến môi trường đã được đặt. Vì vậy, tôi muốn giá trị đó được đánh giá hoặc in bằng biến var1. Tôi đã thử nó như hình trên nhưng không hoạt động. xin vui lòng giúp đỡ.
XYZ_Linux

2
Đây thực sự là một cách giải quyết dễ dàng, tuy nhiên nó giúp bạn không sử dụng tùy chọn "make -j 4" đẹp mắt để có các quy trình chạy song song.
TabeaKischka

1
@TabeaKischka: thực sự. Đó không phải là cách "được đề xuất ", nhưng sẽ có ý nghĩa hơn nếu một người cần một số tính năng không được cung cấp bởi Makefile, sau đó người ta có thể dự phòng việc triển khai bashvà do đó sử dụng các tính năng. Vòng lặp là một trong những tính năng mà điều này có thể được chứng minh.
Willem Van Onsem

4

Có lẽ bạn có thể sử dụng:

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

3

Bạn có thể sử dụng set -elàm tiền tố cho vòng lặp for. Thí dụ:

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

makesẽ thoát ngay lập tức với mã thoát <> 0.


0

Mặc dù bộ công cụ bảng GNUmake có một whilevòng lặp thực (bất cứ điều gì có nghĩa là trong lập trình GNUmake với hai hoặc ba giai đoạn thực hiện của nó), nếu điều cần thiết là một danh sách lặp, có một giải pháp đơn giản với interval. Để giải trí, chúng tôi cũng chuyển đổi các số thành hex:

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

Đầu ra:

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

Một giải pháp vĩ mô đơn giản, độc lập với vỏ / nền tảng là ...

%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. 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.