Вопрос или проблема
У меня есть несколько каталогов, в которых находится Makefile.
Я написал этот скрипт, чтобы запустить эти Makefile, если они существуют:
find . -name "Makefile" -exec sh -c 'make re -C "$1" $(dirname "$0")' {} \;
Но он, похоже, не работает, я получаю эту ошибку:
make: the `-C' option requires a non-empty string argument
Usage: make [options] [target] ...
Options:
-b, -m Ignored for compatibility.
-B, --always-make Unconditionally make all targets.
-C DIRECTORY, --directory=DIRECTORY
[...]
Но когда я заменяю make re -C
на echo
или printf
, он работает нормально…
find . -name "Makefile" -exec sh -c 'printf "%s\n" "$1" $(dirname "$0")' {} \;
# или
find . -name "Makefile" -exec sh -c 'echo "$1" $(dirname "$0")' {} \;
Ваш оригинальный код использует два аргумента, $0
и $1
, но вы всегда передаете что-то только в $0
, оставляя $1
пустым. Это вызывает ошибку, которую вы показываете.
Вы можете исправить это, просто убрав "$1"
из вашей команды (и поставив кавычки вокруг замены команды dirname
), если хотите, предполагая, что ваша make
не против смешения операндов и опций в порядке, но я дам вам еще несколько идей.
Следующие две команды находят все обычные файлы с именем Makefile
в текущем каталоге или ниже и затем используют make -C
, чтобы создать цель re
в каждом каталоге, где такой файл был найден.
find . -name Makefile -type f -exec sh -c 'make -C "${1%/*}" re' sh {} \;
или
find . -name Makefile -type f -exec sh -c '
for pathname do
make -C "${pathname%/*}" re
done' sh {} +
Во второй команде я изменил скрипт sh -c
, чтобы сделать цикл по как можно большему числу найденных путей, а не только по одному имени.
Я заменил dirname
в обеих командах стандартным расширением параметра, которое удаляет последний элемент пути из $pathname
. Мы знаем, что в имени пути есть как минимум одна косая черта, даже если файл Makefile
был найден в текущем каталоге, что делает это безопасным; это в противном случае – пограничный случай, когда вы не хотите заменять dirname
на более быструю замену.
Еще более быстрая версия, которая не использует make
с опцией -C
, заключается в запуске make
с (нестандартным) предикатом -execdir
от find
:
find . -name Makefile -type f -execdir make re \;
Это заставляет find
менять рабочий каталог для make
на директорию, где был найден файл Makefile
. Преимущество этого варианта заключается в том, что он выполняет make
напрямую, не прибегая к скрипту sh -c
.
Вместо использования make -C
проще просто cd
зайти в каталог.
Эта реализация работает хорошо:
find . -name "Makefile" -exec sh -c 'cd "$(dirname "$0")" && make re' {} \;
Использование -exec
или -execdir
работает хорошо, пока не перестанет, потому что find
будет тихо игнорировать любые ошибки. Вы можете сделать следующее в bash, используя флаг -e
, чтобы корректно завершить выполнение при любой ошибке:
set -e
find . -name "Makefile" | while read makefile; do
dir=$(dirname "$makefile")
(
set -e
cd "$dir"
make
)
done
Ответ или решение
Для того чтобы корректно запускать команду make
во всех поддиректориях, содержащих файл Makefile, необходимо учитывать несколько ключевых моментов, чтобы избежать ошибок и обеспечить эффективное выполнение задач. Рассмотрим, как можно решить данную задачу правильно и эффективно.
Теория
Makefile
является инструментом автоматизации сборки программ, который описывает, какие цели (например, компиляция или установка) должны быть достигнуты и как они зависят друг от друга. Основной командой для работы с Makefiles является make
, которая по умолчанию ищет Makefile в текущем каталоге. Однако, если у вас несколько подкаталогов с Makefile, необходимо обрабатывать каждый из них отдельно.
Команда find
в Unix-подобных системах позволяет находить файлы и выполнять команды для каждого файла. В нашем контексте потребуется найти все файлы Makefile и выполнить команду make
в каждом из директорий, в которых они находятся.
Пример
Рассмотрим несколько подходов к решению этой задачи:
-
Использование команды
-exec
сfind
:find . -name "Makefile" -type f -exec sh -c 'make -C "${1%/*}" re' _ {} \;
Здесь команда
find
ищет все файлы с именемMakefile
, а для каждого найденного файла выполняется оболочкаsh -c
, которая запускаетmake
с опцией-C
, указывающей директорию для выполнения. -
Использование команды
-execdir
сfind
:find . -name "Makefile" -type f -execdir make re \;
Опция
-execdir
выполняет командуmake
в директории, где найден файл Makefile. Это удобный способ, поскольку он автоматически меняет текущую директорию, избавляя от необходимости вручную вычислять директорию файла. -
Использование цикла в Bash для надежности:
Иногда команды, выполненные через
find -exec
, могут не отображать ошибки корректно. Для более надежной обработки ошибок можно использовать скрипт на Bash:set -e find . -name "Makefile" | while read makefile; do dir=$(dirname "$makefile") ( set -e cd "$dir" make re ) done
В этом скрипте команда
find
выводит все пути к Makefile, а циклwhile
читает каждый путь по одному, переходит в соответствующий каталог и выполняетmake
. Использование подзадачи в скобках создает новый контекст выполнения, что изолирует команды и предотвращает неправильное изменение текущей директории для внешних команд. Это особенно важно, если ваш сценарий имеет сложные зависимости.
Применение
Каждый из подходов имеет свои достоинства. Опция -exec
с find
достаточно проста и эффективна для большинства случаев. Когда вы уверены, что ошибки выполнения make
должны прерывать весь процесс, подход с использованием Bash-сценария более подходящий, так как с помощью set -e
обеспечивается выход в случае любой ошибки.
Важно учитывать, что инструменты, подобные find
, могут различаться в зависимости от операционной системы, поэтому всегда следует проверять их поведение в ваших специфических условиях. Опция -execdir
, хотя и очень удобна, может быть доступна не во всех системах.
Также важно следить за производительностью таких решений, особенно при работе с большими кодовыми базами, содержащими множество Makefiles. При возможности оптимизируйте процесс, избегая ненужных операций и останавливая процесс при первых признаках ошибки, если это необходимо.
В заключение, важно выбрать такой подход, который обеспечит не только корректность выполнения, но и простоту отладки и возможность масштабирования решения.