loops multiple - How to write loop in a Makefile?




directory each (9)

I want to execute the following commands:

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

How to write this thing as a loop in a Makefile?


Answers

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

You can use set -e as a prefix for the for-loop. Example:

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

make will exit immediately with an exit code <> 0.


For cross-platform support, make the command separator (for executing multiple commands on the same line) configurable.

If you're using MinGW on a Windows platform for example, the command separator is &:

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

This executes the concatenated commands in one line:

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

As mentioned elsewhere, on a *nix platform use CMDSEP = ;.


This is not really a pure answer to the question, but an intelligent way to work around such problems:

instead of writing a complex file, simply delegate control to for instance a bash script like: makefile

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

and script.sh looks like:

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

Maybe you can use:

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

The following will do it if, as I assume by your use of ./a.out, you're on a UNIX-type platform.

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

Test as follows:

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

produces:

1
2
3
4

For bigger ranges, use:

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

This outputs 1 through 10 inclusive, just change the while terminating condition from 10 to 1000 for a much larger range as indicated in your comment.

Nested loops can be done thus:

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

producing:

1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
4 1
4 2
4 3

If you're using GNU make, you could try

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

which will generate and execute

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

THE major reason to use make IMHO is the -j flag. make -j5 will run 5 shell commands at once. This is good if you have 4 CPUs say, and a good test of any makefile.

Basically, you want make to see something like:

.PHONY: all
all: job1 job2 job3

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

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

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

This is -j friendly (a good sign). Can you spot the boiler-plate? We could write:

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

for the same effect (yes, this is the same as the previous formulation as far as make is concerned, just a bit more compact).

A further bit of parameterisation so that you can specify a limit on the command-line (tedious as make does not have any good arithmetic macros, so I'll cheat here and use $(shell ...))

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

You run this with make -j5 LAST=550, with LAST defaulting to 1000.


You have several options to set up variables from outside your makefile:

  • From environment - each environment variable is transformed into a makefile variable with the same name and value.

    You may also want to set -e option (aka --environments-override) on, and your environment variables will override assignments made into makefile (unless these assignments themselves use the override directive . However, it's not recommended, and it's much better and flexible to use ?= assignment (the conditional variable assignment operator, it only has an effect if the variable is not yet defined):

    FOO?=default_value_if_not_set_in_environment
    

    Note that certain variables are not inherited from environment:

    • MAKE is gotten from name of the script
    • SHELL is either set within a makefile, or defaults to /bin/sh (rationale: commands are specified within the makefile, and they're shell-specific).
  • From command line - make can take variable assignments as part of his command line, mingled with targets:

    make target FOO=bar
    

    But then all assignments to FOO variable within the makefile will be ignored unless you use the override directive in assignment. (The effect is the same as with -e option for environment variables).

  • Exporting from the parent Make - if you call Make from a Makefile, you usually shouldn't explicitly write variable assignments like this:

    # Don't do this!
    target:
            $(MAKE) -C target CC=$(CC) CFLAGS=$(CFLAGS)
    

    Instead, better solution might be to export these variables. Exporting a variable makes it into the environment of every shell invocation, and Make calls from these commands pick these environment variable as specified above.

    # Do like this
    CFLAGS=-g
    export CFLAGS
    target:
            $(MAKE) -C target
    

    You can also export all variables by using export without arguments.





makefile loops