Jak napisać polecenie „cd” w pliku makefile?


354

Na przykład mam coś takiego w moim makefile:

all:
     cd some_directory

Ale kiedy pisałem make, widziałem tylko „cd katalog_dowolny”, jak w echopoleceniu.


5
Nie jest jasne, co chcesz zrobić, ale z mojego doświadczenia makenigdy nie chciałem zmieniać katalogu w ten sposób. Może powinieneś wypróbować inne podejście do swojego rozwiązania?
P Shved

2
Powszechnym błędem początkującego jest przekonanie, że Twój katalog jest ważny. W większości przypadków tak nie jest; cd dir; cmd fileprawie zawsze można bardziej użytecznie wyrazić jako cmd dir/file.
tripleee

34
Powszechnym błędem początkującego jest przekonanie, że twój obecny katalog roboczy jest nieistotny. Wiele programów, zwłaszcza skrypty powłoki, jest napisanych z myślą o określonej wartości .. Prawdą jest, że większość narzędzi jest zaprojektowana w taki sposób, że nie trzeba za to zmieniać pwd. Ale to nie zawsze jest prawdą i nie sądzę, że dobrym pomysłem jest nazywanie tego „błędem”, aby wierzyć, że twój katalog może być ważny.
Evelyn Kokemoor

Wskazówka: Jeśli komenda cd mówi „nie ma takiego pliku lub katalogu”, nawet wtedy, gdy (względna) katalog nie istnieje, sprawdzić, czy zmienna środowiskowa CDPATH jest albo pusta lub zawiera „”. Make wykonuje polecenia za pomocą „sh”, który znajdzie ścieżkę względną tylko przez CDPATH, jeśli jest ustawiony. Kontrastuje to z bash, który spróbuje. przed skonsultowaniem się z CDPATH.
Denis Howe,

Odpowiedzi:


620

W rzeczywistości wykonuje polecenie, zmieniając katalog na some_directory, jednak jest to wykonywane w powłoce podprocesu i nie wpływa ani na markę, ani na powłokę, z której pracujesz.

Jeśli chcesz wykonać więcej zadań w środku some_directory, musisz dodać średnik i dołączyć również inne polecenia. Pamiętaj, że nie możesz używać znaków nowej linii, ponieważ są one interpretowane przez make jako koniec reguły, więc wszelkie nowe linie, których używasz dla zachowania przejrzystości, muszą być poprzedzone odwrotnym ukośnikiem.

Na przykład:

all:
        cd some_dir; echo "I'm in some_dir"; \
          gcc -Wall -o myTest myTest.c

Zauważ też, że średnik jest konieczny między każdym poleceniem, nawet jeśli dodajesz odwrotny ukośnik i nowy wiersz. Wynika to z faktu, że cały ciąg jest analizowany przez powłokę jako pojedynczy wiersz. Jak zauważono w komentarzach, do łączenia poleceń należy używać „&&”, co oznacza, że ​​są one wykonywane tylko wtedy, gdy poprzednie polecenie zakończyło się powodzeniem.

all:
        cd some_dir && echo "I'm in some_dir" && \
          gcc -Wall -o myTest myTest.c

Jest to szczególnie ważne, gdy wykonujesz niszczycielskie prace, takie jak sprzątanie, ponieważ w przeciwnym razie zniszczysz niewłaściwe rzeczy, w przypadku cdniepowodzenia z jakiegokolwiek powodu.

Typowym zastosowaniem jest jednak wywoływanie make w podkatalogu, w którym warto zajrzeć. Jest do tego opcja wiersza poleceń, więc nie musisz dzwonić do cdsiebie, więc twoja reguła wyglądałaby tak

all:
        $(MAKE) -C some_dir all

które zmienią się some_diri wykonają Makefiletam z celem „wszystko”. Najlepszą praktyką jest używanie $(MAKE)zamiast makebezpośredniego wywoływania , ponieważ zadba o to, aby wywołać odpowiednią instancję make (jeśli na przykład używasz specjalnej wersji make dla środowiska kompilacji), a także zapewnić nieco inne zachowanie podczas uruchamiania za pomocą niektórych przełączników, takich jak -t.

Dla przypomnienia, zawsze wykonuj echo polecenia, które wykonuje (chyba że jest wyraźnie wyłączone), nawet jeśli nie ma danych wyjściowych, co właśnie widzisz.


8
Cóż, nie zawsze . Aby stłumić echo, po prostu umieść @ na początku wiersza.
Beta

2
@Beta: no tak, a przedrostek myślnika również ignoruje status błędu. Może trochę mnie poniosło, chciałem zwrócić uwagę na fakt, że make robi echo polecenia, niezależnie od tego, jakie to polecenie. I w tym przypadku jest to polecenie bez wyjścia, co sprawia, że ​​echo wydaje się jeszcze dziwniejsze dla kogoś, kto nie zna marki.
falstro

46
Dwie gnidy: 1. Do komend należy naprawdę dołączyć &&, ponieważ ;jeśli katalog nie istnieje, a cdawaria się nie powiedzie, powłoka będzie nadal uruchamiać pozostałe komendy w bieżącym katalogu, co może powodować takie tajemnicze „brak pliku” znalezione ”komunikaty dla kompilacji, nieskończone pętle podczas wywoływania make lub katastrofa dla reguł takich jak clean:: cd dir; rm -rf *. 2. Podczas wywoływania podwykonawców, zadzwoń $(MAKE)zamiast make, aby opcje były przekazywane poprawnie .
andrewdotn

2
@perreal, zwykle definiuję regułę wzoru w ten sposób: %-recursive:z body: @T="$@";$(MAKE) -C some_dir $${T%-*}(zwykle mam też pętlę for, aby przeglądać listę podkatalogów, $${T%-*}jest to rozszerzenie bash, które usuwa -recursiveczęść nazwy docelowej), a następnie określić wyraźny krótki ręki (i .PHONY) cele dla siebie, jak all: all-recursive, check: check-recursive, clean: clean-recursive.
falstro

1
@ChristianStewart, prawda, jak wspomniano w komentarzach 2 i 3.
falstro,

94

Począwszy od GNU make 3.82 (lipiec 2010 r.), Możesz użyć .ONESHELLspecjalnego celu, aby uruchomić wszystkie wiersze receptury w jednej instancji powłoki (moja pogrubiona czcionka):

  • Nowy cel specjalny: .ONESHELLinstruuje make, aby wywoływał pojedyncze wystąpienie powłoki i zapewniał jej całą recepturę , bez względu na to, ile wierszy zawiera. [...]
.ONESHELL: # Only applies to all target
all:
    cd ~/some_dir
    pwd # Prints ~/some_dir if cd succeeded

6
Zauważ, że pwdsamo to działa, podobnie jak `pwd`(z backtickami), ale $(shell pwd)i $(PWD)tak zwróci katalog przed wykonaniem cdpolecenia, więc nie możesz użyć ich bezpośrednio.
anol

3
Tak, ponieważ rozszerzanie zmiennych i funkcji jest wykonywane przed wykonaniem poleceń przez make, natomiast pwdi `pwd`jest wykonywane przez samą powłokę.
Chnossos

2
Nie każdy działa na starszych makefile, a nawet wtedy ta odpowiedź jest o wiedząc, taka możliwość istnieje.
Chnossos,

1
Może to być denerwująca / niebezpieczna opcja, ponieważ tylko ostatnie polecenie dla celu może spowodować awarię (wszelkie wcześniejsze niepowodzenia komend zostaną zignorowane) i prawdopodobnie nikt z tobą nie będzie się tego spodziewał.
tobii

1
Ustaw SHELLFLAGSna, -e -ca powłoka zakończy działanie przy pierwszym poleceniu, które się nie powiedzie.
Timothy Baldwin,

21

Oto urocza sztuczka do radzenia sobie z katalogami i tworzenia. Zamiast używać ciągów wielowierszowych lub „cd;” dla każdej komendy zdefiniuj prostą funkcję chdir jako:

CHDIR_SHELL := $(SHELL)
define chdir
   $(eval _D=$(firstword $(1) $(@D)))
   $(info $(MAKE): cd $(_D)) $(eval SHELL = cd $(_D); $(CHDIR_SHELL))
endef

Następnie wystarczy tylko nazwać to w regule:

all:
          $(call chdir,some_dir)
          echo "I'm now always in some_dir"
          gcc -Wall -o myTest myTest.c

Możesz nawet wykonać następujące czynności:

some_dir/myTest:
          $(call chdir)
          echo "I'm now always in some_dir"
          gcc -Wall -o myTest myTest.c

41
"Uroczy"? Bardziej jak lina wystarczająca do wystrzelenia się w stopę.
tripleee

1
Czy ten bieżący katalog jest ustawiony dla poleceń tylko w tej regule, czy dla wszystkich później wykonywanych reguł? Czy niektóre odmiany tej funkcji będą działać w systemie Windows?
user117529

8
To oczywiście psuje się przy równoległym wykonywaniu ( -jn), co jest prawdziwym celem make .
bobbogo

5
To zły hack. Jeśli kiedykolwiek będziesz musiał uciekać się do takich rzeczy, nie używasz Makefiles do tego, do czego służą.
gatopeich

4
Nie będę się zgadzać, że to z pewnością zły hack. Ale pokazuje niektóre niegodziwe rzeczy, które możesz zrobić.
JoeS,

9

Co chcesz, żeby to zrobiło, kiedy się tam dostanie? Każde polecenie jest wykonywane w podpowłoce, więc podpowłoka zmienia katalog, ale końcowy wynik jest taki, że następne polecenie nadal znajduje się w bieżącym katalogu.

Z GNU make możesz zrobić coś takiego:

BIN=/bin
foo:
    $(shell cd $(BIN); ls)

5
Dlaczego $(shell ...)gdy cd $(BIN); lsalbo cd $(BIN) && ls(jak @andrewdotn wskazał) byłby wystarczający.
vapace

1

Zmienić reż

foo: 
    $(MAKE) -C mydir

multi:
    $(MAKE) -C / -C my-custom-dir   ## Equivalent to /my-custom-dir

-6

Lubię to:

target:
    $(shell cd ....); \
    # ... commands execution in this directory
    # ... no need to go back (using "cd -" or so)
    # ... next target will be automatically in prev dir

Powodzenia!


1
Nie, to źle. W szczególności $(shell cd ....)jest on wykonywany, gdy plik Makefile jest wstępnie analizowany, a nie podczas uruchamiania tej konkretnej receptury.
tripleee,

@triplee Niezupełnie - $(shell)jest rozwijany tylko wtedy, gdy podejmuje decyzję o budowie celu . Jeśli marka nigdy nie potrzebuje przepisu, nie rozszerzy go.
bobbogo,
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.