How to write loop in a Makefile?


Translate

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?


Všechny odpovědi
  • Translate

    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
    

  • Translate

    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;
    

  • Translate

    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 "$@ success"
    ${JOBS}: job%: ; ./a.out $*
    

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


  • Translate

    I realize the question is several years old, but this post may still be of use to someone as it demonstrates an approach which differs from the above, and isn't reliant upon either shell operations nor a need for the developer to schpeel out a hardcoded string of numeric values.

    the $(eval ....) builtin macro is your friend. Or can be at least.

    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)
    

    That's a simplistic example. It won't scale pretty for large values -- it works, but as the ITERATE_COUNT string will increase by 2 characters (space and dot) for each iteration, as you get up into the thousands, it takes progressively longer to count the words. As written, it doesn't handle nested iteration (you'd need a separate iteration function and counter to do so). This is purely gnu make, no shell requirement (though obviously the OP was looking to run a program each time -- here, I'm merely displaying a message). The if within ITERATE is intended to catch the value 0, because $(word...) will error out otherwise.

    Note that the growing string to serve as a counter is employed because the $(words...) builtin can provide an arabic count, but that make does not otherwise support math operations (You cannot assign 1+1 to something and get 2, unless you're invoking something from the shell to accomplish it for you, or using an equally convoluted macro operation). This works great for an INCREMENTAL counter, not so well for a DECREMENT one however.

    I don't use this myself, but recently, I had need to write a recursive function to evaluate library dependencies across a multi-binary, multi-library build environment where you need to know to bring in OTHER libraries when you include some library which itself has other dependencies (some of which vary depending on build parameters), and I use an $(eval) and counter method similar to the above (in my case, the counter is used to ensure we don't somehow go into an endless loop, and also as a diagnostic to report how much iteration was necessary).

    Something else worth nothing, though not significant to the OP's Q: $(eval...) provides a method to circumvent make's internal abhorrence to circular references, which is all good and fine to enforce when a variable is a macro type (intialized with =), versus an immediate assignment (initialized with :=). There are times you want to be able to use a variable within its own assignment, and $(eval...) will enable you to do that. The important thing to consider here is that at the time you run the eval, the variable gets resolved, and that part which is resolved is no longer treated as a macro. If you know what you're doing and you're trying to use a variable on the RHS of an assignment to itself, this is generally what you want to happen anyway.

      SOMESTRING = foo
    
      # will error.  Comment out and re-run
      SOMESTRING = pre-${SOMESTRING}
    
      # works
      $(eval SOMESTRING = pre${SOMESTRING}
    
    default:
      @echo ${SOMESTRING}
    

    Happy make'ing.


  • Translate

    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 = ;.


  • Translate

    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
    

  • Translate

    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.


  • Translate

    Maybe you can use:

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

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