Jak używać poleceń powłoki w Makefile

99

Próbuję wykorzystać wynik lsw innych poleceniach (np. Echo, rsync):

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

Ale dostaję:

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

Próbowałem za pomocą echo $$FILES, echo ${FILES}i echo $(FILES), bez powodzenia.

Adam Matan
źródło

Odpowiedzi:

149

Z:

FILES = $(shell ls)

z takim wcięciem all, jest to polecenie kompilacji. Więc to się rozwija $(shell ls), a następnie próbuje uruchomić polecenie FILES ....

Jeśli FILESma to być makezmienna, zmienne te należy przypisać poza częścią receptury, np .:

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

Oczywiście oznacza to, że FILESzostanie ustawiony na „wyjście z lsprzed uruchomieniem któregokolwiek z poleceń tworzących pliki .tgz. (Chociaż, jak zauważa Kaz, zmienna jest ponownie rozszerzana za każdym razem, więc ostatecznie będzie zawierać pliki .tgz; niektóre warianty make muszą FILES := ...tego unikać, ze względu na wydajność i / lub poprawność. 1 )

Jeśli FILESma to być zmienna powłoki, możesz ją ustawić, ale musisz to zrobić w shell-ese, bez spacji i zacytować:

all:
        FILES="$(shell ls)"

Jednak każda linia jest prowadzona przez oddzielną powłokę, więc ta zmienna nie przetrwa do następnej linii, więc musisz ją natychmiast użyć:

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

To wszystko jest trochę głupie, ponieważ powłoka będzie się rozszerzać *(i inne wyrażenia glob powłoki) w pierwszej kolejności, więc możesz po prostu:

        echo *

jako polecenie powłoki.

Na koniec, jako ogólna zasada (nie do końca dotyczy tego przykładu): jak zauważa esperanto w komentarzach, użycie danych wyjściowych z lsnie jest całkowicie wiarygodne (niektóre szczegóły zależą od nazw plików, a czasem nawet wersji ls; niektóre wersje lspróby oczyszczenia wyjścia w niektórych przypadkach). Tak więc, jak l0b0 i ideliczna uwaga, jeśli używasz GNU, możesz użyć $(wildcard)i $(subst ...)osiągnąć wszystko w makesobie (unikając problemów z "dziwnymi znakami w nazwie pliku"). (W shskryptach, w tym w plikach makefile z recepturami, inną metodą jest find ... -print0 | xargs -0unikanie potknięcia się o spacje, znaki nowej linii, znaki sterujące itp.)


1 GNU Dokonywanie dokumentacji dalej zauważa, że ​​POSIX wprowadza dodatkowe ::=przypisanie w 2012 roku . Nie znalazłem szybkiego odnośnika do dokumentu POSIX dla tego, ani nie wiem od ręki, które makewarianty obsługują ::=przypisywanie, chociaż GNU robi to dzisiaj, z tym samym znaczeniem, co :=, tj. Wykonuje przypisanie teraz z rozszerzeniem.

Zauważ, że VAR := $(shell command args...)można to również zapisać VAR != command args...w kilku makewariantach, w tym we wszystkich nowoczesnych wariantach GNU i BSD, o ile wiem. Te inne warianty nie mają, $(shell)więc użycie VAR != command args...jest lepsze zarówno pod względem krótszego, jak i pracy w większej liczbie wariantów.

torek
źródło
Dzięki. Chcę użyć skomplikowanego polecenia ( na przykład lsz sedi wytnij), a następnie użyć wyników w rsync i innych poleceniach. Czy muszę w kółko powtarzać długie polecenie? Czy nie mogę przechowywać wyników w wewnętrznej zmiennej Make?
Adam Matan,
1
Gnu może mieć na to sposób, ale nigdy go nie używałem, a wszystkie strasznie skomplikowane pliki makefile, których używamy, używają po prostu zmiennych powłoki i olbrzymich jednowierszowych poleceń powłoki zbudowanych z "; \" na końcu każdej linii jako potrzebne. (nie można uzyskać kodowania kodu do pracy z sekwencją odwrotnego ukośnika tutaj, hmm)
torek Kwietnia
1
Może coś takiego jak: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris
2
@William: makemoże to zrobić bez użycia powłoki: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic
1
Chciałbym zwrócić uwagę, że chociaż w tym przypadku nie wydaje się to być bardzo ważne, nie należy automatycznie analizować wyjścia ls. ls ma pokazywać ludziom informacje, a nie być wiązanym w skryptach. Więcej informacji tutaj: mywiki.wooledge.org/ParsingLs Prawdopodobnie w przypadku, gdy 'make' nie oferuje odpowiedniego rozszerzenia za pomocą symboli wieloznacznych, "find" jest lepsze niż "ls".
Raúl Salinas-Monteagudo
53

Poza tym, oprócz odpowiedzi Torka: jedną rzeczą, która się wyróżnia, jest to, że używasz leniwie ocenianego przypisania makra.

Jeśli korzystasz z GNU Make, użyj :=przypisania zamiast =. To przypisanie powoduje natychmiastowe rozwinięcie prawej strony i zapisanie w zmiennej po lewej stronie.

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

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

Jeśli używasz =przypisania, oznacza to, że każde pojedyncze wystąpienie $(FILES)będzie rozszerzać $(shell ...)składnię, a tym samym wywoływać polecenie powłoki. Spowoduje to spowolnienie pracy, a nawet spowoduje zaskakujące konsekwencje.

Kaz
źródło
1
Teraz, gdy mamy listę, jak iterujemy po każdej pozycji na liście i wykonujemy na niej polecenie? Na przykład kompilacja czy test?
anon58192932
3
@ anon58192932 To specyficzny iteracja, aby wykonać polecenie, zwykle odbywa się we fragmencie składni powłoki w kompilacji receptury, dlatego występuje w powłoce, a nie marki: for x in $(FILES); do command $$x; done. Zwróć uwagę na podwojenie, $$które przekazuje jedynkę $do powłoki. Również fragmenty muszli są jednowarstwowe; aby napisać wielowierszowy kod powłoki, należy użyć kontynuacji z ukośnikiem odwrotnym, która jest przetwarzana makesamodzielnie i składana w jedną linię. Oznacza to, że średniki rozdzielające polecenia są obowiązkowe.
Kaz