That behaviour is not a bug. It is a feature. A feature and a possible user error to be precise.
The feature in question is one of the implicit rules of Make. In your case the implicit rule to "build" *.sh
files. The user error, your error, is not changing the working directory before invoking the makefile in the subdirectories.
TL; DR: to fix this you can do one or more of the following:
Fix the shell script to change the working directory:
#!/bin/bash
for f in *; do
if [[ -d $f && -f $f/makefile ]]; then
echo "Making clean in $f..."
(cd $f; make clean)
fi
done
Make the empty rules explicit:
clean: ;
Make the clean
targets phony:
.PHONY: clean
Detailed explanation:
Make has a bunch of implicit rules. This allows one to invoke make on simple projects without even writing a makefile.
Try this for a demonstration:
- create an empty directory and change in to the directory
- create a file named
clean.sh
.
- run
make clean
Output:
$ make clean
cat clean.sh >clean
chmod a+x clean
BAM! That is the power of implict rules of make. See the make manual about implicit rules for more information.
I will try to answer the remaining open question:
Why does it not invoke the implicit rule for the first makefile? Because you overwrote the implicit rule with your explicit clean
rule.
Why does the clean
rule in the second makefile not overwrite the implicit rule? Because it had no recipe. Rules with no recipe do not overwrite the implicit rules, instead they just append prerequisites. See the make manual about multiple rules for more information. See also the make manual about rules with explicit empty recipes.
Why is it an error to not change the working directory before invoking a makefile in a subdirectory? Because make does not change the working directory. Make will work in the inherited working directory. Well technically this is not necessarily an error, but most of the time it is. Do you want the makefiles in the subdirectories to work in the subdirectories? Or do you want them to work in the parent directory?
Why does make ignore the explicit clean
rule from the first makefile in the second invocation of clean.sh
? Because now the target file clean
already exists. Since the rule clean
has no prerequisites there is no need to rebuild the target. See the make manual about phony targets which describes exactly this problem.
Why does make search for the target three/makefile
in the third invocation? Because make always tries to remake the makefiles before doing anything else. This is especially true if the makefile is explicitly requested using -f
but it does not exists. See the make manual about remaking makefiles for more information.
This is happening because cut
is outputting NULL characters in the output. You can't pass a program arguments which contain a null character (see this).
In bash this works because bash can't handle NULL characters in strings, and it strips them out. Zsh is a bit more powerful, and it can handle NULL characters. However when it comes time to pass the string to the program, it still contains the null, which signals the end of the argument.
Let's look at this in detail.
$ echo 'English/Flavors/AU/stuff1/filename.txt' | cut -d'/' --output-delimiter '' -f1,3 | xxd
0000000: 456e 676c 6973 6800 4155 0a English.AU.
Here we simulated one of your files, passing the path through cut
. Notice the xxd
output which has a NULL character between English
and AU
.
Now lets run through and simulate the rest of the script.
$ l=$(echo 'English/Flavors/AU/stuff1/filename.txt' | cut -d'/' --output-delimiter '' -f1,3)
$ dest=/stuff2/${l}.utf8.txt
$ echo "$dest" | xxd
0000000: 2f73 7475 6666 322f 456e 676c 6973 6800 /stuff2/English.
0000010: 4155 2e75 7466 382e 7478 740a AU.utf8.txt.
Notice the NULL after the English
. The echo
displays it properly because echo
is a shell built-in. If we use an external echo
, it also exhibits the issue.
$ /bin/echo "$dest" | xxd
0000000: 2f73 7475 6666 322f 456e 676c 6973 680a /stuff2/English.
P.S. You really should be quoting too :-)
The solution is to not use cut
, use awk
instead.
$ echo 'English/Flavors/AU/stuff1/filename.txt' | awk -F/ '{ print $1$3 }' | xxd
0000000: 456e 676c 6973 6841 550a EnglishAU.
Best Answer
You need to realize that the {...} is a bash construct whilst Makefiles generally launch the sh which does not support the brace expansion feature.
However all's not lost; if you are with GNU Make then you can tell Make to use bash by putting this little line
SHELL := bash
at the very top of your Makefile.