¿Cómo escribir un bucle en un Makefile?

Respuestas:

273

Lo siguiente lo hará si, como supongo por su uso de ./a.out, está en una plataforma de tipo UNIX.

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

Prueba de la siguiente manera:

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

produce:

1
2
3
4

Para rangos más grandes, use:

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

Esto genera del 1 al 10 inclusive, solo cambie el while condición de terminación de 10 a 1000 para un rango mucho mayor como se indica en su comentario.

Los bucles anidados se pueden hacer así:

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

productor:

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
fuente
1
qwert es solo un nombre de destino que es poco probable que sea un archivo real. Las reglas de Makefile necesitan un objetivo. En cuanto al error de sintaxis, te faltan algunas cosas, consulta la actualización.
paxdiablo
2
Dado que está asumiendo que su shell reconoce ((...)), ¿por qué no usar el mucho más simple para ((i = 0; i <WHATEVER; ++ i)); hacer ...; hecho ?
Ideal
1
Tenga en cuenta que el seqcomando que genera una secuencia de números existe en la mayoría (¿todos?) De los sistemas Unix, por lo que puede escribir for number in ``seq 1 1000``; do echo $$number; done(Ponga una sola tecla de retroceso a cada lado del comando seq, no dos, no sé cómo formatear esto correctamente usando la sintaxis de stackoverflow)
Suzanne Dupéron
3
Gracias, me faltaba el doble $$ para hacer referencia a la variable de bucle for.
Leif Gruenwoldt
1
@Jonz: el carácter de continuación de línea se debe a que makenormalmente trata a cada línea como una cosa que se ejecuta en un subconjunto separado . Sin continuación, intentaría ejecutar una subshell con solo (por ejemplo) for number in 1 2 3 4 ; do, sin el resto del bucle. Con él, se convierte efectivamente en una sola línea del formulario while something ; do something ; done, que es una declaración completa. La $$pregunta se responde aquí: stackoverflow.com/questions/26564825/… , con el código aparentemente extraído de esta misma respuesta :-)
paxdiablo
265

Si está utilizando GNU make, podría intentar

NÚMEROS = 1 2 3 4
hazlo:
        $ (foreach var, $ (NÚMEROS),. / a.out $ (var);)

que generará y ejecutará

./a.out 1; ./a.out 2; ./a.out 3; ./a.out 4;
Idelic
fuente
27
Esta respuesta es en mi humilde opinión mejor, porque no requiere el uso de ningún shell, es puro makefile (incluso si es específico de GNU).
Jocelyn delalande
77
el punto y coma es crucial, de lo contrario solo se ejecutará la primera iteración
Jeremy Leipzig
77
Esta solución oculta el código de salida de ./a.out 1. ./a.out 2se ejecutará independientemente
bobbogo
1
@Alexander: ¿podría explicar a qué limitación se refiere?
Ideal
1
@Idelic, sí. Para el siguiente makefile y shellscript, el shellscript funciona (pasa el argumento a ls), mientras que el Makefile da un error: make: execvp: / bin / sh: La lista de argumentos es demasiado larga Makefile: ix.io/d5L Shellscript: ix.io / d5M Este es un problema en la función foreach que encontré en el trabajo, cuando una lista inusualmente larga de nombres de archivo (con rutas largas también) se pasó a un comando. Tuvimos que encontrar una solución alternativa.
Alexander
107

La razón principal para usar make IMHO es la-j bandera. make -j5ejecutará 5 comandos de shell a la vez. Esto es bueno si tiene 4 CPU, por ejemplo, y una buena prueba de cualquier archivo MAKE.

Básicamente, quieres hacer ver algo como:

.PHONY: all
all: job1 job2 job3

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

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

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

Esto es -jamigable (una buena señal). ¿Puedes ver la placa de la caldera? Podríamos escribir:

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

para el mismo efecto (sí, esto es lo mismo que la formulación anterior en lo que respecta a la marca , solo un poco más compacto).

Un poco más de parametrización para que pueda especificar un límite en la línea de comandos (tedioso como make que no tiene buenas macros aritméticas, por lo que haré trampa aquí y usaré $(shell ...))

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

Ejecutas esto make -j5 LAST=550con un valor LASTpredeterminado de 1000.

bobbogo
fuente
77
Esta es definitivamente la mejor respuesta, por la razón que mencionó bobbogo ( -j ).
dbn
¿Alguna razón particular por la que esto use sufijos .PHONY? ¿Son necesarios para algo?
Seb
66
@seb: Sin .PHONY: all, make buscará un archivo llamado all. Si existe un archivo de este tipo, haga entonces compruebe el último tiempo de cambio de ese archivo, y el archivo MAKE casi seguramente no hará lo que desea. La .PHONYdeclaración le dice a make que alles un objetivo simbólico. Por lo tanto, Make considerará que el allobjetivo siempre está desactualizado. Perfecto. Ver el manual.
bobbogo
44
¿Cómo funciona esta línea: $ {JOBS}: job%: ¿Para qué es el segundo punto y coma? No vi nada en gnu.org/software/make/manual/make.htm
Joe
2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (creo que querías decir para qué es el segundo * colon * ). No los confunda con las Reglas de patrones desagradables (en mi humilde opinión) . Las reglas de patrones estáticos son realmente útiles siempre que la lista de objetivos pueda coincidir con uno de los patrones de cabeceo de make (lo mismo se aplica a las dependencias). Aquí uso uno solo por la conveniencia de que todo lo que coincida con %la regla está disponible como $*en la receta.
bobbogo
22

Me doy cuenta de que la pregunta tiene varios años, pero esta publicación aún puede ser útil para alguien, ya que demuestra un enfoque que difiere del anterior y no depende de las operaciones de shell ni de la necesidad de que el desarrollador elimine un código rígido cadena de valores numéricos.

la macro incorporada $ (eval ....) es tu amiga. O puede ser al menos.

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)

Ese es un ejemplo simplista. No se escalará bastante para valores grandes, funciona, pero como la cadena ITERATE_COUNT aumentará en 2 caracteres (espacio y punto) para cada iteración, a medida que avanza en los miles, lleva progresivamente más tiempo contar las palabras. Tal como está escrito, no maneja la iteración anidada (necesitaría una función de iteración y un contador separados para hacerlo). Esto es puramente gnu make, no es un requisito de shell (aunque obviamente el OP estaba buscando ejecutar un programa cada vez; aquí, solo estoy mostrando un mensaje). El if dentro de ITERATE está destinado a capturar el valor 0, porque $ (palabra ...) generará un error de lo contrario.

Tenga en cuenta que la cadena creciente para servir como contador se emplea porque el valor incorporado $ (palabras ...) puede proporcionar un recuento árabe, pero esa marca no admite operaciones matemáticas (no puede asignar 1 + 1 a algo y obtener 2, a menos que esté invocando algo desde el shell para lograrlo por usted, o utilizando una operación de macro igualmente complicada). Esto funciona muy bien para un contador INCREMENTAL, pero no tan bien para un contador DECREMENT.

No lo uso yo mismo, pero recientemente, tuve que escribir una función recursiva para evaluar las dependencias de la biblioteca en un entorno de compilación multibinario y multibiblioteca donde necesita saber para incorporar OTRAS bibliotecas cuando incluye alguna biblioteca que en sí tiene otras dependencias (algunas de las cuales varían según los parámetros de compilación), y uso un método $ (eval) y contador similar al anterior (en mi caso, el contador se usa para garantizar que de alguna manera no entremos en un interminable bucle, y también como un diagnóstico para informar cuánta iteración fue necesaria).

Algo más que no vale nada, aunque no es significativo para el OP. Q: $ (eval ...) proporciona un método para eludir el aborrecimiento interno de make a las referencias circulares, lo cual es bueno y está bien aplicar cuando una variable es un tipo macro (inicializado con =), versus una asignación inmediata (inicializada con: =). Hay veces que desea poder usar una variable dentro de su propia asignación, y $ (eval ...) le permitirá hacerlo. Lo importante a tener en cuenta aquí es que, en el momento en que ejecuta la evaluación, la variable se resuelve y la parte que se resuelve ya no se trata como una macro. Si sabe lo que está haciendo y está tratando de usar una variable en el RHS de una asignación para sí mismo, esto es generalmente lo que quiere que suceda de todos modos.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Feliz haciendo

en la paja
fuente
20

Para soporte multiplataforma, haga que el separador de comandos (para ejecutar múltiples comandos en la misma línea) sea configurable.

Si está utilizando MinGW en una plataforma Windows, por ejemplo, el separador de comandos es &:

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

Esto ejecuta los comandos concatenados en una línea:

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

Como se mencionó en otra parte, en el uso de una plataforma * nix CMDSEP = ;.

Cherno
fuente
7

Esta no es realmente una respuesta pura a la pregunta, sino una forma inteligente de solucionar estos problemas:

en lugar de escribir un archivo complejo, simplemente delegue el control a, por ejemplo, un script bash como: makefile

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

y script.sh se ve así:

for number in 1 2 3 4
do
    ./a.out $number
done
Willem Van Onsem
fuente
Me he quedado atascado en un paso mientras escribía un Makefile. Tengo el siguiente código: set_var: @ NUM = 0; mientras que [[$$ NUM <1]]; do \ echo "Estoy aquí"; \ 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 here SSA_CORE0_MAINEXEC es una variable de entorno que ya está configurada, así que quiero que ese valor se evalúe o imprima utilizando la variable var1. Lo intenté como se muestra arriba pero no funciona. por favor ayuda.
XYZ_Linux
2
De hecho, esta es una solución fácil, sin embargo, le impide usar la buena opción "make -j 4" para que los procesos se ejecuten en paralelo.
TabeaKischka
1
@TabeaKischka: de hecho. No era la forma " recomendada ", pero significa más si uno necesita algunas características que no son ofrecidas por un Makefile, entonces uno puede recurrir a una implementación bashy así usar las características. El bucle es una de las características por las cuales esto se puede demostrar.
Willem Van Onsem
4

Quizás puedas usar:

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

Puede usarlo set -ecomo prefijo para el ciclo for. Ejemplo:

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

makesaldrá inmediatamente con un código de salida <> 0.

Frederik Teichert
fuente
0

Aunque el kit de herramientas de la tabla GNUmake tiene un whilebucle verdadero (lo que sea que eso signifique en la programación GNUmake con sus dos o tres fases de ejecución), si lo que se necesita es una lista iterativa, hay una solución simple interval. Por diversión, también convertimos los números a hexadecimal:

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

Salida:

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

Una solución macro pura, simple, independiente de la plataforma / plataforma es ...

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

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