Przekazywanie argumentów w celu „uruchomienia”

354

Używam Makefiles.

Mam cel o nazwie, runktóry uruchamia cel kompilacji. Uproszczony wygląda następująco:

prog: ....
  ...

run: prog
  ./prog

Czy jest jakiś sposób na przekazanie argumentów? Po to aby

make run asdf --> ./prog asdf
make run the dog kicked the cat --> ./prog the dog kicked the cat

Dzięki!

zaraz
źródło

Odpowiedzi:

264

Nie znam sposobu na zrobienie dokładnie tego, co chcesz, ale obejście może być następujące:

run: ./prog
    ./prog $(ARGS)

Następnie:

make ARGS="asdf" run
# or
make run ARGS="asdf"
Jakob Borg
źródło
27
@Rob: $ () jest bardziej przenośny, działa zarówno w Nmake, jak i make.
John Knoeller,
7
@Rob: Nmake nigdy nie obsługiwał $ {} dla ekspansji makr i wydaje się, że jest to obecnie archaiczna forma. $ () jest zalecane w każdym tutorialu online, na który patrzyłem. $ () jest również bardziej spójny z innymi narzędziami, takimi jak bash.
John Knoeller,
10
Może to archaiczne. Zawsze używałem $ {}, ale instrukcja GNU Make stwierdza: „Aby podstawić wartość zmiennej, napisz znak dolara, a następnie nazwę zmiennej w nawiasach lub nawiasach klamrowych: albo $(foo)' or $ {foo} 'jest poprawnym odniesieniem do zmienna „foo”. ” i podaje przykłady, w których użyto tylko $ (). Ach tak.
Jakob Borg,
7
kibicuje John i calmh, wróciłem i zobaczyłem, że sugestia pochodzi z mojego pierwszego wydania książki OReilly „Managing Projects with Make”. Autor określa zasadę zastępowania archiwów za pomocą () i makr zdolnych do wykonania obu, ale sugeruje użycie {} do rozróżnienia. Ale .... Nowa edycja zmieniła nazwę na „Managing Projects with GNU Make” używa () w całym tekście. Idź rysunek .... Chyba będę musiał zmodernizować! (-: Nadal jestem zdumiony, że MS NMake barfs w {} 's.
Rob Wells
1
@xealits Z pewnością jest - istnieje pytanie na pytanie tutaj
helvete
198

To pytanie ma prawie trzy lata, ale w każdym razie ...

Jeśli używasz GNU make, jest to łatwe. Jedynym problemem jest to, że makezinterpretuje argumenty nie będące opcjami w wierszu poleceń jako cele. Rozwiązaniem jest przekształcenie ich w cele bezczynności, aby makenie narzekać:

# If the first argument is "run"...
ifeq (run,$(firstword $(MAKECMDGOALS)))
  # use the rest as arguments for "run"
  RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
  # ...and turn them into do-nothing targets
  $(eval $(RUN_ARGS):;@:)
endif

prog: # ...
    # ...

.PHONY: run
run : prog
    @echo prog $(RUN_ARGS)

Uruchomienie tego daje:

$ make run foo bar baz
prog foo bar baz
Ideliczny
źródło
2
Jest to świetne, ale wydaje się, że nie działa w przypadku argumentów rozpoczynających się od myślnika:prog foo bar --baz
ingydotnet
22
To nie działa w tym przypadku również, ale trzeba powiedzieć make, aby nie interpretować --bazjako opcja wiersza poleceń: make -- prog foo bar --baz. --Oznacza „wszystko po to jest argument, nie jest rozwiązaniem”.
Idelic,
Jak zdefiniowałbym wartość domyślną do RUN_ARGSkorzystania z tego?
Bouke,
Może dodać elsegałąź ifeqi ustawić RUN_ARGStam?
Idelic
1
Dobry punkt niebieskawy! Ale jest na to rozwiązanie: zamień linię „eval” na $(eval $(RUN_ARGS):dummy;@:), bez zdefiniowanego fałszywego celu.
Lucas Cimon
52

dla standardowego make możesz przekazać argumenty, definiując takie makra

make run arg1=asdf

następnie użyj ich w ten sposób

run: ./prog $(arg1)
   etc

Referencje dla make Microsoft's NMake

John Knoeller
źródło
34

Możesz przekazać zmienną do pliku Makefile, jak poniżej:

run:
    @echo ./prog $$FOO

Stosowanie:

$ make run FOO="the dog kicked the cat"
./prog the dog kicked the cat

lub:

$ FOO="the dog kicked the cat" make run
./prog the dog kicked the cat

Alternatywnie skorzystaj z rozwiązania dostarczonego przez Beta :

run:
    @echo ./prog $(filter-out $@,$(MAKECMDGOALS))
%:
    @:

%:- reguła, która pasuje do dowolnej nazwy zadania; @:- pusty przepis = nic nie rób

Stosowanie:

$ make run the dog kicked the cat
./prog the dog kicked the cat
kenorb
źródło
22

TL; DR nie próbuj tego robić

$ make run arg

zamiast tego utwórz skrypt:

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

i zrób to:

$ ./buildandrunprog.sh arg

odpowiedz na zadane pytanie:

możesz użyć zmiennej w recepturze

run: prog
    ./prog $(var)

następnie przekaż przypisanie zmiennej jako argument do wykonania

$ make run var=arg

to się wykona ./prog arg.

ale strzeżcie się pułapek. omówię pułapki tej metody i innych metod w dalszej części.


odpowiedz na zakładaną intencję pytania:

założenie: chcesz uruchomić progz kilkoma argumentami, ale w razie potrzeby przebuduj go przed uruchomieniem.

odpowiedź: utwórz skrypt, który w razie potrzeby odbuduje, a następnie uruchomi prog z args

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

ten skrypt wyraźnie wyjaśnia intencję. używa make do robienia tego, co jest dobre: ​​budowania. używa skryptu powłoki do robienia tego, co jest dobre dla: przetwarzania wsadowego.

a ponadto możesz robić wszystko, czego potrzebujesz, z pełną elastycznością i ekspresyjnością skryptu powłoki, bez wszystkich zastrzeżeń makefile.

również składnia wywołująca jest teraz praktycznie identyczna:

$ ./buildandrunprog.sh foo "bar baz"

porównać do:

$ ./prog foo "bar baz"

w przeciwieństwie do

$ make run var="foo bar\ baz"

tło:

make nie jest przeznaczony do przekazywania argumentów do celu. wszystkie argumenty w wierszu poleceń są interpretowane jako cel (aka target), jako opcja lub jako przypisanie zmiennej.

więc jeśli uruchomisz to:

$ make run foo --wat var=arg

Marka będzie interpretować runi foojako cele (cele) aktualizować zgodnie z ich przepisami. --watjako opcja dla make. i var=argjako zmienne przypisanie.

Więcej informacji: https://www.gnu.org/software/make/manual/html_node/Goals.html#Goals

terminologia patrz: https://www.gnu.org/software/make/manual/html_node/Rule-Introduction.html#Rule-Introduction


na temat metody przypisywania zmiennych i dlaczego odradzam

$ make run var=arg

i zmienna w recepturze

run: prog
    ./prog $(var)

jest to najbardziej „poprawny” i najprostszy sposób przekazywania argumentów do przepisu. ale chociaż można go używać do uruchamiania programu z argumentami, to z pewnością nie jest przeznaczony do użycia w ten sposób. patrz https://www.gnu.org/software/make/manual/html_node/Overriding.html#Overriding

moim zdaniem ma to jedną wielką wadę: to, co chcesz zrobić, to uruchomić progargument arg. ale zamiast pisać:

$ ./prog arg

piszesz:

$ make run var=arg

staje się to jeszcze bardziej niezręczne przy próbie przekazania wielu argumentów lub argumentów zawierających spacje:

$ make run var="foo bar\ baz"
./prog foo bar\ baz
argcount: 2
arg: foo
arg: bar baz

porównać do:

$ ./prog foo "bar baz"
argcount: 2
arg: foo
arg: bar baz

dla przypomnienia progwygląda to tak:

#! /bin/sh
echo "argcount: $#"
for arg in "$@"; do
  echo "arg: $arg"
done

zauważ także, że nie powinieneś umieszczać $(var)cudzysłowów w pliku makefile:

run: prog
    ./prog "$(var)"

ponieważ wtedy progzawsze otrzyma tylko jeden argument:

$ make run var="foo bar\ baz"
./prog "foo bar\ baz"
argcount: 1
arg: foo bar\ baz

wszystko dlatego polecam przeciwko tej trasie.


dla kompletności oto kilka innych metod „przekazywania argumentów do uruchomienia”.

metoda 1:

run: prog
    ./prog $(filter-out $@, $(MAKECMDGOALS))

%:
    @true

super krótkie wyjaśnienie: odfiltruj aktualny cel z listy celów. create catch all target ( %), który nie robi nic, aby po cichu zignorować pozostałe cele.

metoda 2:

ifeq (run, $(firstword $(MAKECMDGOALS)))
  runargs := $(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))
  $(eval $(runargs):;@true)
endif

run:
    ./prog $(runargs)

super krótkie wyjaśnienie: jeśli celem jest runusunięcie pierwszego celu i utworzenie nic nie rób dla pozostałych celów eval.

oba pozwolą ci napisać coś takiego

$ make run arg1 arg2

dla głębszego wyjaśnienia zapoznaj się z instrukcją make: https://www.gnu.org/software/make/manual/html_node/index.html

problemy metody 1:

  • argumenty rozpoczynające się od myślnika będą interpretowane przez make, a nie przekazywane jako cel.

    $ make run --foo --bar
    

    obejście

    $ make run -- --foo --bar
    
  • argumenty ze znakiem równości będą interpretowane przez make, a nie przekazywane

    $ make run foo=bar
    

    bez obejścia

  • kłótnie ze spacjami są niezręczne

    $ make run foo "bar\ baz"
    

    bez obejścia

  • jeśli argument stanie się run(równy celowi), zostanie również usunięty

    $ make run foo bar run
    

    uruchomi się ./prog foo barzamiast./prog foo bar run

    obejście możliwe dzięki metodzie 2

  • jeśli argument jest uzasadnionym celem, zostanie również uruchomiony.

    $ make run foo bar clean
    

    uruchomi się, ./prog foo bar cleanale także przepis na cel clean(zakładając, że istnieje).

    obejście możliwe dzięki metodzie 2

  • gdy błędnie wpiszesz prawidłowy cel, zostanie on po cichu zignorowany z powodu złapania wszystkich celów.

    $ make celan
    

    po prostu po cichu zignoruje celan.

    obejście polega na tym, aby wszystko było pełne. więc widzisz, co się dzieje. ale to powoduje dużo hałasu dla legalnej wydajności.

problemy metody 2:

  • jeśli argument ma taką samą nazwę jak istniejący cel, wtedy make wypisze ostrzeżenie, że jest zastępowane.

    brak obejścia, o którym wiem

  • argumenty ze znakiem równości będą nadal interpretowane przez make, a nie przekazywane

    bez obejścia

  • spory ze spacjami są wciąż niezręczne

    bez obejścia

  • argumenty z przerwami w przestrzeni evalpróbujące stworzyć nic nie rób celów.

    obejście: utwórz globalny cel catch all, nie robiąc nic tak jak powyżej. z powyższym problemem, że ponownie po cichu zignoruje błędnie wpisane uzasadnione cele.

  • używa evaldo modyfikowania pliku makefile w czasie wykonywania. o ile gorzej możesz pójść pod względem czytelności i debugowania oraz zasady najmniejszego zdziwienia .

    obejście: nie rób tego !! 1 zamiast tego napisz skrypt powłoki, który uruchamia make, a następnie uruchamia prog.

testowałem tylko przy użyciu gnu make. inne marki mogą mieć różne zachowania.


TL; DR nie próbuj tego robić

$ make run arg

zamiast tego utwórz skrypt:

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

i zrób to:

$ ./buildandrunprog.sh arg
lesmana
źródło
12

Oto inne rozwiązanie, które może pomóc w niektórych z tych przypadków użycia:

test-%:
    $(PYTHON) run-tests.py $@

Innymi słowy, wybierz jakiś prefiks ( test-w tym przypadku), a następnie przekaż nazwę docelową bezpośrednio do programu / programu uruchamiającego. Wydaje mi się, że jest to szczególnie przydatne, jeśli zaangażowany jest jakiś skrypt uruchamiający, który może rozpakować docelową nazwę na coś przydatnego dla programu bazowego.

djc
źródło
2
Możesz także użyć, $*aby przekazać tylko część celu, która pasowała do %.
Malvineous,
9

Nie. Patrząc na składnię ze strony podręcznika dla GNU make

make [-f makefile] [opcje] ... [cele] ...

możesz określić wiele celów, a więc „nie” (przynajmniej nie w dokładnie taki sposób, jaki określiłeś).

ziya
źródło
4

Możesz jawnie wyodrębnić każdy n-ty argument z wiersza poleceń. Aby to zrobić, możesz użyć zmiennej MAKECMDGOALS, która zawiera listę argumentów wiersza poleceń podanych dla „make”, co interpretuje jako listę celów. Jeśli chcesz wyodrębnić n-ty argument, możesz użyć tej zmiennej w połączeniu z funkcją „słowo”, na przykład, jeśli chcesz drugi argument, możesz zapisać go w zmiennej w następujący sposób:

second_argument := $(word 2, $(MAKECMDGOALS) )
użytkownik5122888
źródło
Spowoduje to również uruchomienie komendy make dla tego argumentu. make: *** No rule to make target 'arg'. Stop.
ThomasReggi,
2

anon , run: ./progwygląda nieco dziwnie, jak prawa część powinna być celem, takrun: prog wygląda lepiej.

Sugerowałbym po prostu:

.PHONY: run

run:
        prog $(arg1)

i chciałbym dodać, że argumenty można przekazać:

  1. jako argument: make arg1="asdf" run
  2. lub być zdefiniowane jako środowisko: arg1="asdf" make run
dma_k
źródło
2

Oto mój przykład. Zauważ, że piszę pod Windows 7, używając mingw32-make.exe, który jest dostarczany z Dev-Cpp. (Mam c: \ Windows \ System32 \ make.bat, więc polecenie wciąż nazywa się „make”.)

clean:
    $(RM) $(OBJ) $(BIN) 
    @echo off
    if "${backup}" NEQ "" ( mkdir ${backup} 2> nul && copy * ${backup} )

Zastosowanie do regularnego czyszczenia:

make clean

Zastosowanie do czyszczenia i tworzenia kopii zapasowej w mydir /:

make clean backup=mydir
osa
źródło
2

Nie jestem z tego dumny, ale nie chciałem przekazywać zmiennych środowiskowych, więc odwróciłem sposób uruchamiania komendy w puszce:

run:
    @echo command-you-want

to wypisze polecenie, które chcesz uruchomić, więc po prostu oceń je w podpowłoce:

$(make run) args to my command
Conrad.Dean
źródło
4
patrząc wstecz na tę odpowiedź dwa lata później - dlaczego byłam tak uparta, że ​​nie chciałam używać zmiennych środowiskowych i dlaczego myślałam, że wbudowanie kolejnej komendy jest lepsze?
Conrad.Dean
0

Znalazłem sposób na uzyskanie argumentów znakiem równości (=)! Odpowiedź jest szczególnie dodatkiem do odpowiedzi @lesmana (ponieważ jest to najbardziej kompletna i wyjaśniona tutaj), ale byłoby zbyt duże, aby napisać ją jako komentarz. Ponownie powtarzam jego wiadomość: TL; DR nie próbuj tego robić!

Potrzebowałem sposobu na potraktowanie mojego argumentu --xyz-enabled=false(ponieważ domyślnie jest to prawda), o którym wszyscy wiemy, że nie jest to cel docelowy i dlatego nie jest częścią $(MAKECMDGOALS).

Przeglądając wszystkie zmienne make przez echo, $(.VARIABLES)otrzymałem te interesujące wyniki:

[...] -*-command-variables-*- --xyz-enabled [...]

To pozwala nam iść na dwa sposoby: albo zacząć wszystko od --(jeśli dotyczy to twojego przypadku), albo przyjrzeć się GNU, tworząc specyficzną (prawdopodobnie nie przeznaczoną dla nas) zmienną -*-command-variables-*-. ** Zobacz stopkę dla dodatkowych opcji ** W moim przypadku ta zmienna posiadała:

--xyz-enabled=false

Za pomocą tej zmiennej możemy połączyć ją z już istniejącym rozwiązaniem, $(MAKECMDGOALS)a tym samym definiując:

# the other technique to invalidate other targets is still required, see linked post
run:
    @echo ./prog $(-*-command-variables-*-) $(filter-out $@,$(MAKECMDGOALS))`

i używając go (jawnie mieszając kolejność argumentów):

make run -- config --xyz-enabled=false over=9000 --foo=bar show  isit=alwaysreversed? --help

zwrócony:

./prog isit=alwaysreversed? --foo=bar over=9000 --xyz-enabled=false config show --help

Jak widać, tracimy całkowitą kolejność argumentów. Część z „przypisaniem” -arg wydaje się być odwrócona, kolejność „docelowych” -arg jest zachowana. Umieściłem „przypisanie” na początku, mam nadzieję, że twój program nie dba o to, gdzie znajduje się argument.


Aktualizacja: dzięki nim zmienne również wyglądają obiecująco:

MAKEFLAGS =  -- isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
MAKEOVERRIDES = isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
Dionizy
źródło
-2

Inną sztuczką, której używam, jest -nflaga, która każe makewykonać próbę na sucho. Na przykład,

$ make install -n 
# Outputs the string: helm install stable/airflow --name airflow -f values.yaml
$ eval $(make install -n) --dry-run --debug
# Runs: helm install stable/airflow --name airflow -f values.yaml --dry-run --debug
Santon
źródło