Wielowierszowe polecenia bash w pliku makefile

120

Mam bardzo wygodny sposób kompilacji projektu za pomocą kilku wierszy poleceń basha. Ale teraz muszę go skompilować za pomocą makefile. Biorąc pod uwagę, że każde polecenie jest uruchamiane we własnej powłoce, moje pytanie brzmi: jaki jest najlepszy sposób na uruchomienie wielowierszowego polecenia bash, zależnego od siebie, w makefile? Na przykład tak:

for i in `find`
do
    all="$all $i"
done
gcc $all

Czy ktoś może też wyjaśnić, dlaczego nawet jednoliniowe polecenie bash -c 'a=3; echo $a > file'działa poprawnie w terminalu, ale tworzy pusty plik w przypadku makefile?

Jofsey
źródło
4
Druga część powinna być osobnym pytaniem. Dodanie kolejnego pytania powoduje tylko hałas.
l0b0

Odpowiedzi:

136

Możesz użyć odwrotnego ukośnika do kontynuacji wiersza. Zwróć jednak uwagę, że powłoka otrzymuje całe polecenie połączone w jedną linię, więc musisz również zakończyć niektóre wiersze średnikiem:

foo:
    for i in `find`;     \
    do                   \
        all="$$all $$i"; \
    done;                \
    gcc $$all

Ale jeśli chcesz po prostu wziąć całą listę zwróconą przez findwywołanie i przekazać ją gcc, w rzeczywistości niekoniecznie potrzebujesz polecenia wielowierszowego:

foo:
    gcc `find`

Lub używając bardziej konwencjonalnego $(command)podejścia (zauważ $ucieczkę):

foo:
    gcc $$(find)
Eldar Abusalimov
źródło
2
W przypadku multilinii nadal musisz unikać znaków dolara, jak wskazano w komentarzach w innym miejscu.
tripleee
1
Tak, krótka odpowiedź 1. $: = $$ 2. Dołącz multiline z \ BTW bash faceci zazwyczaj zaleca się zastąpić `find` połączenia z $$ (find)
Yauhen Yakimovich
89

Jak wskazano w pytaniu, każde polecenie podrzędne jest uruchamiane we własnej powłoce . To sprawia, że ​​pisanie nietrywialnych skryptów powłoki jest trochę bałaganiarskie - ale jest to możliwe! Rozwiązaniem jest konsolidacja skryptu w to, co make rozważy pojedynczą komendę podrzędną (pojedynczą linię).

Wskazówki dotyczące pisania skryptów powłoki w plikach makefile:

  1. Uniknij używania skryptu, $zastępując go$$
  2. Przekonwertuj skrypt, aby działał jako pojedynczy wiersz, wstawiając ; między poleceniami
  3. Jeśli chcesz napisać skrypt w wielu wierszach, użyj znaku końca linii \
  4. Opcjonalnie zacznij od set -e aby dopasować przepis make do przerwania w przypadku niepowodzenia polecenia podrzędnego
  5. Jest to całkowicie opcjonalne, ale możesz nawiasować skrypt z ()lub w {}celu podkreślenia spójności sekwencji wielu linii - że nie jest to typowa sekwencja poleceń makefile

Oto przykład inspirowany OP:

mytarget:
    { \
    set -e ;\
    msg="header:" ;\
    for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
    msg="$$msg :footer" ;\
    echo msg=$$msg ;\
    }
Brent Bradburn
źródło
4
Możesz także chcieć SHELL := /bin/bashw swoim pliku makefile włączyć funkcje specyficzne dla BASH, takie jak zastępowanie procesów .
Brent Bradburn
8
Subtelna uwaga: odstęp po nim {ma kluczowe znaczenie, aby zapobiec interpretacji {setjako nieznanego polecenia.
Brent Bradburn,
3
Subtelna uwaga # 2: W pliku makefile prawdopodobnie nie ma to znaczenia, ale różnica między pakowaniem za pomocą {}i ()robi dużą różnicę, jeśli czasami chcesz skopiować skrypt i uruchomić go bezpośrednio z zachęty powłoki. Możesz siać spustoszenie w swojej instancji powłoki, deklarując zmienne, a zwłaszcza modyfikując stan za pomocą set, wewnątrz {}. ()zapobiega modyfikowaniu środowiska przez skrypt, co jest prawdopodobnie preferowane. Przykład ( będzie to zakończyć sesję powłoki ) { set -e ; } ; false.
Brent Bradburn,
Komentarze można dołączać przy użyciu następującego formularza command ; ## my comment \` (the comment is between :; `i` \ `). Wydaje się, że działa dobrze, z wyjątkiem tego , że jeśli uruchomisz polecenie ręcznie (przez kopiowanie i wklejanie), historia polecenia będzie zawierać komentarz w sposób, który łamie polecenie (jeśli spróbujesz go ponownie użyć). [Uwaga: podświetlanie składni w tym komentarzu jest zepsute ze względu na użycie odwrotnego ukośnika wewnątrz odwrotnego znaku]
Brent Bradburn
Jeśli chcesz przerwać ciąg w bash / perl / script wewnątrz makefile, zamknij cytat ciągu, ukośnik odwrotny, znak nowej linii, (bez wcięcia) otwarty cytat. Przykład; perl -e QUOTE print 1; CYTAT BACKSLASH NEWLINE CYTAT drukuj 2 CYTAT
mosh
2

Co jest złego w zwykłym wywoływaniu poleceń?

foo:
       echo line1
       echo line2
       ....

A jeśli chodzi o drugie pytanie, musisz uciec $od $$znaku, używając zamiast tego, tjbash -c '... echo $$a ...' .

EDYCJA: Twój przykład można przepisać na skrypt jednowierszowy w następujący sposób:

gcc $(for i in `find`; do echo $i; done)
JesperE
źródło
5
Ponieważ „każde polecenie jest uruchamiane we własnej powłoce”, podczas gdy muszę użyć wyniku polecenie1 w poleceniu2. Jak w przykładzie.
Jofsey
Jeśli potrzebujesz propagować informacje między wywołaniami powłoki, musisz użyć jakiejś formy pamięci zewnętrznej, na przykład pliku tymczasowego. Ale zawsze możesz przepisać kod, aby wykonać wiele poleceń w tej samej powłoce.
JesperE
+1, zwłaszcza w wersji uproszczonej. I czy nie jest to to samo, co wykonanie po prostu `` gcc `find ''?
Eldar Abusalimov
1
Tak to jest. Ale możesz oczywiście robić bardziej złożone rzeczy niż tylko „echo”.
JesperE
2

Oczywiście właściwym sposobem napisania Makefile jest faktyczne udokumentowanie, które cele zależą od źródeł. W trywialnym przypadku proponowane rozwiązanie będzie foozależało od siebie, ale oczywiściemake jest na tyle inteligentne, aby porzucić zależność cykliczną. Ale jeśli dodasz plik tymczasowy do swojego katalogu, stanie się on „magicznie” częścią łańcucha zależności. Lepiej jest raz na zawsze utworzyć jawną listę zależności, na przykład za pomocą skryptu.

GNU make wie, jak uruchomić, gccaby stworzyć plik wykonywalny z zestawu plików.c.h plików i , więc może wszystko, czego naprawdę potrzebujesz, to

foo: $(wildcard *.h) $(wildcard *.c)
tripleee
źródło
1

Dyrektywa ONESHELL pozwala na napisanie wielu receptur wierszy do wykonania w tym samym wywołaniu powłoki.

SOURCE_FILES = $(shell find . -name '*.c')

.ONESHELL:
foo: ${SOURCE_FILES}
    for F in $^; do
        gcc -c $$F
    done

Jest jednak pewna wada: specjalne prefiksy („@”, „-” i „+”) są interpretowane inaczej.

https://www.gnu.org/software/make/manual/html_node/One-Shell.html

Jean-Baptiste Poittevin
źródło