In this month's post-summer holiday column I cover two important topics for Makefile builders with a round up of techniques for sorting and searching inside Makefiles and on the file system.
Sorting a list
Sorting in a Makefile is relatively simple: GNU Make provides a $(sort) function to sort a list. But there are a few of gotchas:
1. The $(sort) both sorts into order and removes duplicates from a list. There's no option to just sort while retaining duplicates.
For example, sorting a b a a c with $(sort) returns a list with three elements (and just one a):
$(warning $(sort a b a a c))
Makefile:1: a b c
More on this in the next section.
2. The actual sort order is defined by the return value from the strcmp C function, which in turn will depend on your operating system and locale collation order. This means that you can't rely on the sort order being identical if the Makefile is used on different operating systems (or even different instances of the same operating system).
3. The sort order is always lexical and there are no options to ignore case, or define another ordering (such as numeric).
If GNU Make's built-in $(sort) function isn't up to the list sorting task that you need then the solution is to use the shell's sort program and call it through $(shell). To use the shell's sort program the list first needs to be broken up into a sequence of lines, then it is passed to sort with the appropriate options, and finally the list is reconstructed. Happily $(shell) will automatically do the list reconstruction for us because the output of sort will be flattened by replacing the end of every line with a space.
To split the list into a sequence of lines you can construct a single echo statement with all the spaces in the list replaced by the newline escape sequence.
The function my-sort below performs a sort using the shell sort program:
sp += # add space
my-sort = $(shell echo -e $(subst $(sp),'\n',$2) | sort $1 --key=1,1 -)
my-sort has two arguments: the first are any arguments to pass to the shell's sort program; the second is the list to be sorted.
my-sort works by splitting the list by converting each space to a newline. This is done using the GNU Make $(subst) function that will convert spaces (from the $(sp) variable; since it's hard to pass a space as a function argument in GNU Make, the best way is to create a variable containing just a space and use it instead of a space literal) to newline (note that the newline character is single-quoted and that echo has the -e option: the quotes are there because GNU Make will actually escape the backslash with another backslash and the -e means that the newline escape character will be expanded to a literal newline).
Naturally, there are some problems with this: using the shell is probably slower than using a built-in function because of the need to spawn the shell, and you could hit a command-line length limit if the list to be sorted was long.
Since my-sort uses the shell to sort the list there are a number of options that can come in handy (and are not available by the built-in $(sort)):
-n Perform a numeric rather than alphabetic sort
-M Perform a 'month' sort
-r Reverse the sort order
-f Ignore case
So, for example, you can sort the list 1 12 4 6 8 3 4 5