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?


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)

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 ;\
    }

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)

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)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.