I have a Makefile which, among other things, installs a Bash completion script:

install [...] /etc/bash_completion.d/

This works fine in Ubuntu, but in Arch Bash completions are installed to /usr/share/bash-completion/completions.

What would be the idiomatic GNU or POSIX make way to handle this? I expect a nice solution would have something like these features:

  1. If the install command fails, the target must fail.
  2. Traverse the list of directories in sequence as written. At the first directory which already exists (if any):
    1. Install the file if it does not exist,
    2. overwrite the file if it exists and is not up to date (according to make semantics),
    3. skip the file if it exists and is up to date, and
    4. stop processing directories.
  3. It is not an error if the file is not installed anywhere.
  4. It is not an error (nor should the Makefile attempt to handle this case) if the file has already been installed in one directory, and subsequent runs installs it into another directory. That is, given a directory list /a /b, if someone

    1. creates /b,
    2. puts the file in /b,
    3. then creates /a and
    4. runs make

    the Makefile should not detect the presence of /b at all.

  5. It is not an error if none of the directories exist.
  6. Feedback about which directories were attempted would be nice, but not mandatory.
  • POSIX make or GNU make? By “installed only if it's not up to date”, do you mean that you want to overwrite an existing file only if it's older? If the directory /firstattempt exists but not /firstattempt/myscript, and there is also a /secondattempt/myscript, do you want to update the file in /secondattempt, or install into /firstattempt and ignore /secondattempt? What feedback do you want about which directories are attempted? – Gilles 'SO- stop being evil' Aug 7 '13 at 23:18

The obvious, script-style solution would be to do this:

for dir in /etc/bash_completion.d /usr/share/bash-completion/completions; \
do \
    if [ -d "$$dir" ]; \
    then \
        install [...] "$$dir" || exit 1; \
        break; \
    fi; \

However, this isn't very nice:

  • Lots of escape characters
  • Need to use ugly || exit 1 to check for errors

And it doesn't check whether the file is up to date (points 2.2, 2.3).


A simple way is to try each installation in turn and keep going until one works.

for d in /etc/bash_completion.d /usr/share/bash-completion/completions; do \
    install $(INSTALL_OPTIONS) $(COMPLETION_SCRIPT) $$d; } && exit; \
done; \
exit 1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.