Cách gán đầu ra của lệnh cho biến Makefile


225

Tôi cần phải thực thi một số quy tắc tạo điều kiện, chỉ khi Python được cài đặt lớn hơn một phiên bản nhất định (giả sử 2.5).

Tôi nghĩ rằng tôi có thể làm một cái gì đó như thực thi:

python -c 'import sys; print int(sys.version_info >= (2,5))'

và sau đó sử dụng đầu ra ('1' nếu ok, '0' nếu không) trong ifeqcâu lệnh make.

Trong một kịch bản bash shell đơn giản, nó chỉ:

MY_VAR=`python -c 'import sys; print int(sys.version_info >= (2,5))'`

nhưng nó không hoạt động trong Makefile.

Bất kỳ đề xuất? Tôi có thể sử dụng bất kỳ cách giải quyết hợp lý khác để đạt được điều này.


Những dấu tích lạ lùng xung quanh lệnh hoạt động để thực thi các tập lệnh khác cho tôi trong Makefile. Có thể là một cái gì đó khác.
Leif Gruenwoldt

Câu trả lời:


346

Sử dụng phần shelldựng sẵn như trongMY_VAR=$(shell echo whatever)

me@Zack:~$make
MY_VAR IS whatever

me@Zack:~$ cat Makefile 
MY_VAR := $(shell echo whatever)

all:
    @echo MY_VAR IS $(MY_VAR)

34
shell không phải là lệnh Make dựng sẵn tiêu chuẩn. Đây là một GNU Make dựng sẵn.
Dereckson

12
stackoverflow.com/a/2373111/12916 thêm một lưu ý quan trọng về việc thoát $.
Jesse Glick

6
Ví dụ đơn giản này hoạt động. Nó cũng hoạt động với đường ống lệnh shell. Nhưng điều cần thiết là bạn nên sử dụng $$ để thể hiện $ trong lệnh shell
Sergey P. aka azure

28
Mặc dù câu hỏi đã cũ, nhưng tốt nhất là thực hiện MY_VAR: = $ (shell ...), nếu không, mỗi khi MY_VAR được đánh giá, nó sẽ thực hiện lại $ (shell ...).
Russ Schultz

Tôi đã có một khoảng
trắng

29

Kết thúc nhiệm vụ trong một evallà làm việc cho tôi.

# dependency on .PHONY prevents Make from 
# thinking there's `nothing to be done`
set_opts: .PHONY
  $(eval DOCKER_OPTS = -v $(shell mktemp -d -p /scratch):/output)

6
"Lưu ý: @true ở đây ngăn không cho Make nghĩ rằng không có gì để làm." Ừm, đó là những gì .PHONY always make these targetsdành cho.
gạch dưới

Cảm ơn bạn! Điều này giúp tôi bỏ qua "bash lạ" trong makefile
Nam G VU

19

Đây là một ví dụ phức tạp hơn một chút với đường ống và gán biến trong công thức:

getpodname:
    # Getting pod name
    @eval $$(minikube docker-env) ;\
    $(eval PODNAME=$(shell sh -c "kubectl get pods | grep profile-posts-api | grep Running" | awk '{print $$1}'))
    echo $(PODNAME)

2
Trong trường hợp nó trở nên tiện dụng, tôi đang sử dụng một cách tiếp cận tương tự để lấy PODNAMEtên triển khai:$(eval PODNAME=$(shell sh -c "kubectl get pod -l app=sqlproxy -o jsonpath='{.items[0].metadata.name}'"))
Jan Richter

Cú pháp làm tôi bối rối trong một giây cho đến khi tôi nhận ra bạn đang sử dụng cả shell dựng sẵn eval (trên dòng docker-env) và hàm make eval (trên dòng tiếp theo).
Brian Gordon

17

Tôi đang viết một câu trả lời để tăng khả năng hiển thị cho cú pháp thực tế giải quyết vấn đề. Thật không may, những gì ai đó có thể xem là tầm thường có thể trở thành một vấn đề đau đầu rất lớn đối với ai đó đang tìm kiếm một câu trả lời đơn giản cho một câu hỏi hợp lý.

Đặt phần sau vào tập tin "Makefile".

MY_VAR := $(shell python -c 'import sys; print int(sys.version_info >= (2,5))')

all:
    @echo MY_VAR IS $(MY_VAR)

Hành vi bạn muốn thấy là như sau (giả sử bạn đã cài đặt python gần đây).

make
MY_VAR IS 1

Nếu bạn sao chép và dán văn bản trên vào Makefile, bạn sẽ nhận được điều này chứ? Chắc là không. Bạn có thể sẽ gặp lỗi như những gì được báo cáo ở đây:

makefile: 4: *** thiếu dấu phân cách. Dừng lại

Tại sao: Bởi vì mặc dù cá nhân tôi đã sử dụng tab chính hãng, Stack Overflow (cố gắng hữu ích) chuyển đổi tab của tôi thành một số khoảng trắng. Bạn, công dân internet thất vọng, bây giờ sao chép này, nghĩ rằng bây giờ bạn có cùng một văn bản mà tôi đã sử dụng. Lệnh make, bây giờ đọc khoảng trắng và thấy rằng lệnh "all" được định dạng không chính xác. Vì vậy, sao chép văn bản trên, dán nó và sau đó chuyển đổi khoảng trắng trước khi "@echo" thành một tab và cuối cùng, ví dụ này sẽ hy vọng có hiệu quả với bạn.


Phụ thuộc vào những gì biên tập viên bạn đang dán vào. Tôi vừa sao chép và dán vào một trình soạn thảo Makefile của Eclipse và có một tab hàng đầu (theo yêu cầu).
Technophile

Oh tôi đã không nghĩ về điều đó. Nguyên tử đây.
AlanSE

11

Coi chừng những công thức nấu ăn như thế này

target:
    MY_ID=$(GENERATE_ID);
    echo $MY_ID;

Nó làm hai điều sai. Dòng đầu tiên trong công thức được thực thi trong một thể hiện shell riêng biệt từ dòng thứ hai. Các biến bị mất trong khi chờ đợi. Điều thứ hai sai $là không thoát được.

target:
    MY_ID=$(GENERATE_ID); \
    echo $$MY_ID;

Cả hai vấn đề đã được sửa chữa và biến có thể sử dụng được. Dấu gạch chéo ngược kết hợp cả hai dòng để chạy trong một shell duy nhất, do đó cài đặt biến và đọc các từ khóa biến, hoạt động.

Tôi nhận ra bài viết gốc cho biết làm thế nào để đưa kết quả của lệnh shell vào biến MAKE và câu trả lời này cho thấy cách đưa nó vào biến shell. Nhưng độc giả khác có thể có lợi.

Một cải tiến cuối cùng, nếu người tiêu dùng mong đợi một "biến môi trường" được đặt, thì bạn phải xuất nó.

my_shell_script
    echo $MY_ID

sẽ cần điều này trong tệp thực hiện

target:
    export MY_ID=$(GENERATE_ID); \
    ./my_shell_script;

Hy vọng rằng sẽ giúp được ai đó. Nói chung, mọi người nên tránh thực hiện bất kỳ công việc thực tế nào ngoài công thức nấu ăn, bởi vì nếu ai đó sử dụng tệp thực hiện với tùy chọn '--dry-run', chỉ để XEM những gì nó sẽ làm, nó sẽ không có tác dụng phụ không mong muốn. Mỗi $(shell)cuộc gọi được đánh giá tại thời gian biên dịch và một số công việc thực sự có thể vô tình được thực hiện. Tốt hơn là để công việc thực sự, như tạo id, vào bên trong công thức nấu ăn khi có thể.


9

Với GNU Make, bạn có thể sử dụng shellevallưu trữ, chạy và gán đầu ra từ các lệnh gọi dòng lệnh tùy ý. Sự khác biệt giữa ví dụ dưới đây và những người sử dụng :=là sự :=phân công xảy ra một lần (khi nó gặp phải) và cho tất cả. Các biến được mở rộng đệ quy được thiết lập với =một chút "lười biếng" hơn; các tham chiếu đến các biến khác vẫn còn cho đến khi chính biến đó được tham chiếu và việc mở rộng đệ quy tiếp theo diễn ra mỗi khi biến được tham chiếu , điều này là mong muốn để tạo ra "đoạn mã nhất quán, có thể gọi được,". Xem hướng dẫn về cài đặt các biến để biết thêm.

# Generate a random number.
# This is not run initially.
GENERATE_ID = $(shell od -vAn -N2 -tu2 < /dev/urandom)

# Generate a random number, and assign it to MY_ID
# This is not run initially.
SET_ID = $(eval MY_ID=$(GENERATE_ID))

# You can use .PHONY to tell make that we aren't building a target output file
.PHONY: mytarget
mytarget:
# This is empty when we begin
    @echo $(MY_ID)
# This recursively expands SET_ID, which calls the shell command and sets MY_ID
    $(SET_ID)
# This will now be a random number
    @echo $(MY_ID)
# Recursively expand SET_ID again, which calls the shell command (again) and sets MY_ID (again)
    $(SET_ID)
# This will now be a different random number
    @echo $(MY_ID)

Bạn có lời giải thích tốt đẹp đầu tiên tôi từng gặp. Cảm ơn bạn. Mặc dù có một điều cần nói thêm là nếu $(SET_ID)cuộc sống bên trong ifmệnh đề sai thì nó vẫn được gọi .
La Mã

Nó vẫn được gọi bởi vì các stmts if được đánh giá tại thời gian biên dịch makefile không phải lúc chạy. Đối với hướng dẫn cụ thể thời gian chạy, đưa chúng vào công thức nấu ăn. Nếu chúng có điều kiện, hãy thêm nếu stmts, được viết bằng bash / shell, như một phần của công thức.
Juraj

0

Trong ví dụ dưới đây, tôi đã lưu trữ đường dẫn thư mục Makefile LOCAL_PKG_DIRvà sau đó sử dụng LOCAL_PKG_DIRbiến trong các mục tiêu.

Makefile:

LOCAL_PKG_DIR := $(shell eval pwd)

.PHONY: print
print:
    @echo $(LOCAL_PKG_DIR)

Đầu ra thiết bị đầu cuối:

$ make print
/home/amrit/folder
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.