Як використовувати команди оболонки у Makefile


99

Я намагаюся використовувати результат lsв інших командах (наприклад, echo, rsync):

all:
    <Building, creating some .tgz files - removed for clarity>
    FILES = $(shell ls)
    echo $(FILES)

Але я отримую:

make
FILES = Makefile file1.tgz file2.tgz file3.tgz
make: FILES: No such file or directory
make: *** [all] Error 1

Я пробував використовувати echo $$FILES, echo ${FILES}і echo $(FILES), не маючи удачі.

Відповіді:


149

З:

FILES = $(shell ls)

відступивши внизу allтак, це команда збірки. Отже, це розширюється $(shell ls), а потім намагається запустити команду FILES ....

Якщо FILESпередбачається, що це makeзмінна, ці змінні повинні бути призначені поза частиною рецепта, наприклад:

FILES = $(shell ls)
all:
        echo $(FILES)

Звичайно, це означає, що перед запуском будь-якої з команд, що створюють файли .tgz , FILESбуде встановлено значення "output from ls" . (Хоча, як зазначає Kaz, змінна щоразу повторно розширюється, тому врешті-решт вона включатиме файли .tgz; деякі варіанти make повинні уникати цього для ефективності та / або коректності. 1 )FILES := ...

Якщо FILESпередбачається, що це змінна оболонки, ви можете встановити її, але це потрібно робити в оболонці ese, без пробілів, і в цитатах:

all:
        FILES="$(shell ls)"

Однак кожен рядок запускається окремою оболонкою, тому ця змінна не збережеться до наступного рядка, тому її потрібно негайно використовувати:

        FILES="$(shell ls)"; echo $$FILES

Це все трохи безглуздо, оскільки оболонка розшириться *(і інші вирази глобусів оболонки) для вас, в першу чергу, тому ви можете просто:

        echo *

як ваша команда оболонки.

Нарешті, як загальне правило (насправді не застосовується до цього прикладу): як зазначає есперанто в коментарях, використання вихідних даних lsне є повністю надійним (деякі деталі залежать від назв файлів, а іноді навіть версії ls; деякі версії lsспроби дезінфікувати вихідні дані В деяких випадках). Таким чином, як l0b0 та ідеальна примітка, якщо ви використовуєте GNU, ви можете використовувати $(wildcard)і $(subst ...)виконувати все, що знаходиться всередині makeсебе (уникаючи будь-яких "дивних символів у назві файлу"). (У shсценаріях, включаючи частину рецептурних файлів, інший метод полягає у використанні, find ... -print0 | xargs -0щоб уникнути спотикання пробілів, нових рядків, контрольних символів тощо).


1 Документація GNU Make також зазначає, що POSIX робить додаткове ::=призначення в 2012 році . Я не знайшов для цього короткого посилання на документ POSIX, і я не знаю, які makeваріанти підтримують ::=призначення, хоча GNU make робить сьогодні, з тим самим значенням, що :=, наприклад, виконує призначення зараз із розширенням.

Зауважте, що VAR := $(shell command args...)також можна писати VAR != command args...у декількох makeваріантах, включаючи всі сучасні варіанти GNU та BSD, наскільки мені відомо. Ці інші варіанти не мають, $(shell)тому використання VAR != command args...перевершує як коротший, так і роботу в більшій кількості варіантів.


Дякую. Я хочу використати складну команду (наприклад, lsіз sedі вирізати), а потім використовувати результати в rsync та інших командах. Чи потрібно повторювати довгу команду знову і знову? Чи не можу я зберегти результати у внутрішній змінній Make?
Адам Матан,

1
Gnu make міг би це зробити, але я ніколи цього не використовував, і всі жахливо складні файли make-файлів, які ми використовуємо, просто використовують змінні оболонки та гігантські однорядкові команди оболонки, побудовані з "; \" в кінці кожного рядка як потрібні. (не можу отримати кодування коду для роботи з послідовністю зворотної риски тут, хм)
Торек,

1
Можливо щось на кшталт: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
Вільям Морріс,

2
@William: makeможе зробити це без використання оболонки: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic

1
Я хотів би зазначити, що хоча в цьому випадку це здається не дуже важливим, ви не повинні автоматично аналізувати вихідні дані ls. ls призначений для того, щоб показувати інформацію людям, а не затискати в сценарії. Більше інформації тут: mywiki.wooledge.org/ParsingLs Можливо, у випадку, якщо 'make' не пропонує вам відповідне розширення підстановок, "знайти" краще, ніж "ls".
Рауль Салінас-Монтеагудо,

53

Крім того, на додаток до відповіді Торека: одне, що виділяється, це те, що ви використовуєте ліниво оцінене призначення макросів.

Якщо ви використовуєте GNU Make, використовуйте :=призначення замість =. Це призначення призводить до негайного розширення правої сторони та збереження її у змінній лівої руки.

FILES := $(shell ...)  # expand now; FILES is now the result of $(shell ...)

FILES = $(shell ...)   # expand later: FILES holds the syntax $(shell ...)

Якщо ви використовуєте =призначення, це означає, що кожен окремий випадок $(FILES)буде розширювати $(shell ...)синтаксис і, таким чином, викликати команду оболонки. Це змусить вашу роботу працювати повільніше або навіть мати деякі дивовижні наслідки.


1
Тепер, коли у нас є список, як ми перебираємо кожен елемент у списку та виконуємо над ним команду? Такі як збірка чи тестування?
anon58192932

3
@ Anon58192932 Це конкретні ітерації, щоб виконати команду, як правило , робиться у вигляді фрагмента синтаксису оболонки в рецепті збірки, так що відбувається в оболонці, а не в зробити: for x in $(FILES); do command $$x; done. Зверніть увагу на подвоєне збільшення, $$яке передає сингл $оболонці. Крім того, уламки оболонки є однокласниками; для написання багаторядкового коду оболонки ви використовуєте продовження зворотної риски, яке обробляється makeсамостійно і складається в один рядок. Що означає, що крапки з комою, що розділяють команди, обов’язкові.
Каз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.