Bagaimana cara menulis loop di Makefile?

217

Saya ingin menjalankan perintah berikut:

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

Bagaimana cara menulis hal ini sebagai loop dalam Makefile?

rata-rata
sumber
Serupa, tetapi sedikit lebih umum: Perintah bash multiline di makefile
nobar

Jawaban:

273

Berikut ini akan melakukannya jika, seperti yang saya asumsikan dengan penggunaan ./a.outAnda, Anda berada pada platform tipe UNIX.

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

Tes sebagai berikut:

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

menghasilkan:

1
2
3
4

Untuk rentang yang lebih besar, gunakan:

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

Ini menghasilkan 1 hingga 10 inklusif, hanya mengubah whilekondisi terminating dari 10 menjadi 1000 untuk rentang yang jauh lebih besar seperti yang ditunjukkan dalam komentar Anda.

Loop bersarang dapat dilakukan sebagai berikut:

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

memproduksi:

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
sumber
1
qwert hanyalah nama target yang tidak mungkin berupa file asli. Aturan makefile membutuhkan target. Adapun kesalahan sintaksis, Anda kehilangan beberapa hal - lihat pembaruan.
paxdiablo
2
Karena Anda mengasumsikan shellnya mengenali ((...)), mengapa tidak menggunakan yang lebih sederhana untuk ((i = 0; i <WHATEVER; ++ i)); lakukan ...; selesai?
Idelic
1
Perhatikan bahwa seqperintah yang menghasilkan urutan angka ada pada sebagian besar (semua?) Sistem unix, sehingga Anda dapat menulis for number in ``seq 1 1000``; do echo $$number; done(Letakkan backtick tunggal di setiap sisi perintah seq, bukan dua, saya tidak tahu cara memformat ini dengan benar menggunakan sintaks stackoverflow)
Suzanne Dupéron
3
Terima kasih, saya kehilangan $$ ganda untuk referensi variabel loop.
Leif Gruenwoldt
1
@ Jonz: karakter kelanjutan garis karena makebiasanya memperlakukan setiap baris sebagai sesuatu untuk dijalankan dalam sub-shell yang terpisah . Tanpa kelanjutan, ia akan mencoba menjalankan subshell dengan just (misalnya) for number in 1 2 3 4 ; do, tanpa sisa dari loop. Dengan itu, ia secara efektif menjadi satu baris bentuk while something ; do something ; done, yang merupakan pernyataan lengkap. The $$Pertanyaan dijawab di sini: stackoverflow.com/questions/26564825/... , dengan kode yang tampaknya diambil dari sangat jawaban ini :-)
paxdiablo
265

Jika Anda menggunakan GNU make, Anda bisa mencoba

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

yang akan menghasilkan dan mengeksekusi

./a.out 1; ./a.out 2; ./a.out 3; ./a.out 4;
Idelic
sumber
27
Jawaban ini IMHO lebih baik, karena tidak perlu menggunakan shell, itu makefile murni (bahkan jika itu GNU-spesifik).
Jocelyn delalande
7
titik koma sangat penting jika tidak hanya iterasi pertama yang akan dieksekusi
Jeremy Leipzig
7
Solusi ini menyembunyikan kode keluar dari ./a.out 1. ./a.out 2akan dieksekusi bagaimanapun juga.
bobbogo
1
@Alexander: bisakah Anda menguraikan batasan apa yang Anda maksud?
Idelic
1
@ Idelic, ya. Untuk makefile dan shellscript berikut, shellscript berfungsi (meneruskan argumen ke ls), sedangkan Makefile memberikan kesalahan: make: execvp: / bin / sh: Daftar argumen terlalu panjang Makefile: ix.io/d5L Shellscript: ix.io / d5M Ini adalah masalah dalam fungsi foreach yang saya temui di tempat kerja, ketika daftar panjang nama file (dengan jalur panjang juga) diteruskan ke sebuah perintah. Kami harus mencari solusinya.
Alexander
107

THE alasan utama untuk menggunakan make IMHO adalah -jbendera. make -j5akan menjalankan 5 perintah shell sekaligus. Ini bagus jika Anda memiliki 4 CPU katakan, dan tes yang baik dari makefile apa pun.

Pada dasarnya, Anda ingin melihat sesuatu seperti:

.PHONY: all
all: job1 job2 job3

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

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

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

Ini -jramah (pertanda baik). Bisakah Anda melihat pelat-ketel? Kita bisa menulis:

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

untuk efek yang sama (ya, ini adalah sama dengan formulasi sebelumnya sejauh make yang bersangkutan, hanya sedikit lebih kompak).

Sedikit parameterisasi lebih lanjut sehingga Anda dapat menentukan batas pada command-line (membosankan karena maketidak memiliki makro aritmatika yang baik, jadi saya akan menipu di sini dan menggunakan $(shell ...))

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

Anda menjalankan ini dengan make -j5 LAST=550, dengan LASTdefault ke 1000.

bobbogo
sumber
7
Ini jelas merupakan jawaban terbaik, untuk alasan yang disebutkan bobbogo ( -j ).
dbn
Adakah alasan khusus ini menggunakan akhiran .PHONY? Apakah mereka diharuskan untuk apa saja?
Seb
6
@seb: Tanpa .PHONY: all, make akan mencari file bernama all. Jika ada file seperti itu, buat lalu periksa file itu terakhir kali berubah, dan makefile hampir pasti tidak akan melakukan apa yang Anda inginkan. The .PHONYdeklarasi mengatakan make yang allmerupakan target simbolis. Karenanya, Make akan mempertimbangkan alltarget untuk selalu ketinggalan zaman. Sempurna. Lihat manualnya.
bobbogo
4
Bagaimana cara kerja baris ini: $ {JOBS}: job%: Untuk apa titik koma kedua? Saya tidak melihat apa pun di gnu.org/software/make/manual/make.htm
Joe
2
@ Joe gnu.org/software/make/manual/make.html#Static-Pattern (saya pikir maksud Anda adalah apa * * titik dua * untuk ). Jangan membingungkan mereka dengan Aturan Pola (IMHO) yang tidak baik . Aturan pola statis benar-benar berguna setiap kali daftar target dapat dicocokkan dengan salah satu pola noddy make (sama berlaku untuk dependensi). Di sini saya menggunakan satu hanya untuk kenyamanan bahwa apa pun yang cocok %dengan aturan tersebut tersedia seperti $*dalam resep.
bobbogo
22

Saya menyadari pertanyaannya sudah berumur beberapa tahun, tetapi postingan ini mungkin masih bermanfaat bagi seseorang karena menunjukkan pendekatan yang berbeda dari yang di atas, dan tidak bergantung pada operasi shell maupun kebutuhan pengembang untuk membuat hardcoded. string nilai numerik.

makro builtin $ (eval ....) adalah teman Anda. Atau setidaknya bisa.

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)

Itu contoh sederhana. Ini tidak akan menskala cantik untuk nilai besar - itu berfungsi, tetapi karena string ITERATE_COUNT akan meningkat sebesar 2 karakter (spasi dan titik) untuk setiap iterasi, saat Anda mencapai ribuan, semakin lama semakin dibutuhkan untuk menghitung kata-kata. Seperti yang ditulis, itu tidak menangani iterasi bersarang (Anda akan memerlukan fungsi iterasi terpisah dan counter untuk melakukannya). Ini murni gnu make, tidak ada persyaratan shell (meskipun jelas OP sedang mencari untuk menjalankan program setiap kali - di sini, saya hanya menampilkan pesan). Jika dalam ITERATE dimaksudkan untuk menangkap nilai 0, karena $ (kata ...) akan kesalahan keluar sebaliknya.

Perhatikan bahwa string yang berkembang untuk berfungsi sebagai penghitung digunakan karena $ (kata-kata ...) builtin dapat memberikan hitungan arab, tetapi yang membuatnya tidak mendukung operasi matematika (Anda tidak dapat menetapkan 1 +1 ke sesuatu dan mendapatkan 2, kecuali jika Anda meminta sesuatu dari shell untuk menyelesaikannya untuk Anda, atau menggunakan operasi makro yang sama berbelit-belit). Ini bekerja sangat baik untuk penghitung INCREMENTAL, namun tidak begitu baik untuk penghitungan DECREMENT.

Saya tidak menggunakan ini sendiri, tetapi baru-baru ini, saya harus menulis fungsi rekursif untuk mengevaluasi dependensi perpustakaan di multi-biner, multi-perpustakaan membangun lingkungan di mana Anda perlu tahu untuk membawa perpustakaan LAIN ketika Anda memasukkan beberapa perpustakaan yang itu sendiri memiliki dependensi lain (beberapa di antaranya bervariasi tergantung pada parameter build), dan saya menggunakan $ (eval) dan metode penghitung yang mirip dengan di atas (dalam kasus saya, penghitung digunakan untuk memastikan kita tidak entah bagaimana masuk ke yang tak berujung loop, dan juga sebagai diagnostik untuk melaporkan berapa banyak iterasi diperlukan).

Sesuatu yang lain tidak bernilai apa pun, meskipun tidak signifikan untuk OP's Q: $ (eval ...) menyediakan metode untuk menghindari kebencian internal make terhadap referensi melingkar, yang semuanya baik dan baik untuk ditegakkan ketika variabel adalah tipe makro (diinternalisasi dengan =), versus penugasan langsung (diinisialisasi dengan: =). Ada saat-saat Anda ingin dapat menggunakan variabel dalam tugasnya sendiri, dan $ (eval ...) akan memungkinkan Anda untuk melakukan itu. Hal penting yang perlu dipertimbangkan di sini adalah bahwa pada saat Anda menjalankan eval, variabel diselesaikan, dan bagian yang diselesaikan tidak lagi diperlakukan sebagai makro. Jika Anda tahu apa yang Anda lakukan dan Anda mencoba menggunakan variabel pada RHS dari sebuah tugas untuk dirinya sendiri, ini umumnya adalah apa yang ingin Anda lakukan.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Selamat bercinta.

s. mulai
sumber
20

Untuk dukungan lintas-platform, buat pemisah perintah (untuk menjalankan beberapa perintah pada baris yang sama) dapat dikonfigurasi.

Misalnya, jika Anda menggunakan MinGW pada platform Windows, pemisah perintah adalah &:

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

Ini mengeksekusi perintah gabungan dalam satu baris:

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

Seperti disebutkan di tempat lain, pada platform * nix digunakan CMDSEP = ;.

cherno
sumber
7

Ini sebenarnya bukan jawaban murni untuk pertanyaan, tetapi cara cerdas untuk mengatasi masalah seperti:

alih-alih menulis file yang kompleks, cukup mendelegasikan kontrol misalnya skrip bash seperti: makefile

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

dan script.sh terlihat seperti:

for number in 1 2 3 4
do
    ./a.out $number
done
Willem Van Onsem
sumber
Saya terjebak pada langkah saat menulis Makefile. Saya memiliki kode berikut: set_var: @ NUM = 0; sementara [[$$ NUM <1]]; do \ echo "I am here"; \ echo $$ NUM dump $$ {NUM} .txt; \ var = "SSA_CORE $$ {NUM} _MAINEXEC"; \ echo $$ var; \ var1 = eval echo \$${$(var)}; \ echo $$ var1; \ ((NUM = NUM ​​+ 1)); \ selesai semua: set_var di sini SSA_CORE0_MAINEXEC adalah variabel lingkungan yang sudah ditetapkan. Jadi saya ingin nilai itu dievaluasi atau dicetak menggunakan variabel var1. Saya mencobanya seperti yang ditunjukkan di atas tetapi tidak berhasil. tolong bantu.
XYZ_Linux
2
Ini memang solusi yang mudah, namun hal ini membuat Anda tidak menggunakan opsi "make -j 4" yang bagus untuk menjalankan proses secara paralel.
TabeaKischka
1
@ TabeaKischka: memang. Itu bukan cara yang " direkomendasikan ", tetapi lebih dimaksudkan jika seseorang membutuhkan beberapa fitur yang tidak ditawarkan oleh Makefile, maka seseorang dapat mundur ke implementasi bashdan dengan demikian menggunakan fitur-fiturnya. Loop adalah salah satu fitur yang dapat ditunjukkan.
Willem Van Onsem
4

Mungkin Anda bisa menggunakan:

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

Anda dapat menggunakan set -eawalan untuk for-loop. Contoh:

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

makeakan segera keluar dengan kode keluar <> 0.

Frederik Teichert
sumber
0

Meskipun toolkit tabel GNUmake memiliki whileloop yang benar (apa pun artinya dalam pemrograman GNUmake dengan dua atau tiga fase eksekusi), jika hal yang diperlukan adalah daftar berulang, ada solusi sederhana dengan interval. Untuk bersenang-senang, kami mengonversi angka menjadi hex juga:

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

Keluaran:

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

Solusi makro murni, shell / platform-independent, murni adalah ...

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

$(foreach i,$(call %sequence,10),$(info ./a.out ${i}))
saingan
sumber
-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. 
Leslie Satenstein
sumber