Cách sử dụng các lệnh shell trong Makefile


99

Tôi đang cố gắng sử dụng kết quả của lstrong các lệnh khác (ví dụ: echo, rsync):

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

Nhưng tôi nhận được:

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

Tôi đã thử sử dụng echo $$FILES, echo ${FILES}echo $(FILES)không may mắn.

Câu trả lời:


149

Với:

FILES = $(shell ls)

thụt vào bên dưới allnhư vậy, đó là lệnh xây dựng. Vì vậy, điều này mở rộng $(shell ls), sau đó cố gắng chạy lệnh FILES ....

Nếu FILESđược cho là một makebiến, các biến này cần được chỉ định bên ngoài phần công thức, ví dụ:

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

Tất nhiên, điều đó có nghĩa là nó FILESsẽ được đặt thành "đầu ra từ ls" trước khi chạy bất kỳ lệnh nào tạo tệp .tgz. (Mặc dù Kaz lưu ý rằng biến được mở rộng lại mỗi lần, vì vậy cuối cùng nó sẽ bao gồm các tệp .tgz; một số biến thể phải FILES := ...tránh điều này, vì hiệu quả và / hoặc tính đúng đắn. 1 )

Nếu FILESđược cho là một biến shell, bạn có thể đặt nó nhưng bạn cần thực hiện trong shell-ese, không có khoảng trắng và được trích dẫn:

all:
        FILES="$(shell ls)"

Tuy nhiên, mỗi dòng được chạy bởi một shell riêng biệt, do đó, biến này sẽ không tồn tại đến dòng tiếp theo, vì vậy bạn phải sử dụng nó ngay lập tức:

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

Điều này hơi ngớ ngẩn vì shell sẽ mở rộng *(và các biểu thức shell khác) cho bạn ngay từ đầu, vì vậy bạn có thể chỉ cần:

        echo *

như lệnh shell của bạn.

Cuối cùng, như một quy tắc chung (không thực sự áp dụng cho ví dụ này): như ghi chú của esperanto trong nhận xét, việc sử dụng đầu ra từ lskhông hoàn toàn đáng tin cậy (một số chi tiết phụ thuộc vào tên tệp và đôi khi thậm chí là phiên bản của ls; một số phiên bản lscố gắng làm sạch đầu ra trong vài trường hợp). Vì vậy, như l0b0 và lưu ý idelic , nếu bạn đang sử dụng GNU, bạn có thể sử dụng $(wildcard)$(subst ...)hoàn thành mọi thứ bên trong makechính nó (tránh mọi vấn đề "ký tự lạ trong tên tệp"). (Trong shtập lệnh, bao gồm cả phần công thức của các tệp trang điểm, một phương pháp khác là sử dụng find ... -print0 | xargs -0để tránh vấp phải khoảng trống, dòng mới, ký tự điều khiển, v.v.)


1 GNU Ghi chú thêm tài liệu rằng POSIX thực hiện thêm ::=nhiệm vụ vào năm 2012 . Tôi không tìm thấy liên kết tham chiếu nhanh đến tài liệu POSIX cho việc này, cũng như không biết makebiến thể nào hỗ trợ ::=việc gán, mặc dù GNU thực hiện ngày nay, với ý nghĩa tương tự như :=thực hiện việc gán ngay bây giờ với mở rộng.

Lưu ý rằng VAR := $(shell command args...)cũng có thể được đánh vần VAR != command args...trong một số makebiến thể, bao gồm tất cả các biến thể GNU và BSD hiện đại theo như tôi biết. Các biến thể khác không có $(shell)vì vậy việc sử dụng VAR != command args...vượt trội hơn cả về thời gian ngắn hơn và hoạt động ở nhiều biến thể hơn.


Cảm ơn. Tôi muốn sử dụng một lệnh phức tạp ( ví dụ: lsvới sedvà cắt) và sau đó sử dụng kết quả trong rsync và các lệnh khác. Tôi có phải lặp đi lặp lại lệnh dài dòng không? Tôi không thể lưu trữ kết quả trong biến Make nội bộ?
Adam Matan

1
Gnu make có thể có một cách để làm điều đó, nhưng tôi chưa bao giờ sử dụng nó và tất cả các trang điểm phức tạp khủng khiếp mà chúng tôi sử dụng chỉ sử dụng các biến shell và các lệnh shell một lớp khổng lồ được xây dựng bằng "; \" ở cuối mỗi dòng như cần thiết. (không thể có được mã hóa mã để làm việc với các chuỗi dấu chéo ngược ở đây, hmm)
torek

1
Có lẽ một cái gì đó giống như: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris

2
@William: makecó thể làm điều đó mà không cần sử dụng shell : FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic

1
Tôi muốn chỉ ra rằng mặc dù trong trường hợp này, nó có vẻ không quan trọng lắm, nhưng bạn không nên tự động phân tích cú pháp đầu ra của ls. ls có nghĩa là hiển thị thông tin cho con người, không bị xâu chuỗi trong các tập lệnh. Thông tin thêm tại đây: mywiki.wooledge.org/ParsingLs Có thể, trong trường hợp 'make' không cung cấp cho bạn khả năng mở rộng ký tự đại diện phù hợp, thì "find" sẽ tốt hơn "ls".
Raúl Salinas-Monteagudo

53

Ngoài ra, ngoài câu trả lời của torek: một điều nổi bật là bạn đang sử dụng một nhiệm vụ macro được đánh giá một cách lười biếng.

Nếu bạn đang sử dụng GNU Make, hãy sử dụng :=bài tập thay vì =. Phép gán này làm cho phía bên phải được mở rộng ngay lập tức và được lưu trữ trong biến bên trái.

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

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

Nếu bạn sử dụng =phép gán, điều đó có nghĩa là mỗi lần xuất hiện của $(FILES)sẽ mở rộng $(shell ...)cú pháp và do đó gọi lệnh shell. Điều này sẽ làm cho công việc của bạn chạy chậm hơn, hoặc thậm chí gây ra một số hậu quả đáng ngạc nhiên.


1
Bây giờ chúng ta đã có danh sách, làm thế nào để chúng ta lặp lại từng mục trong danh sách và thực hiện một lệnh trên đó? Chẳng hạn như xây dựng hoặc thử nghiệm?
anon58192932

3
@ anon58192932 Đó cụ thể lặp, để thực hiện một lệnh, thường được thực hiện trong một đoạn cú pháp shell trong xây dựng công thức, vì vậy nó xảy ra trong vỏ, chứ không phải trong thực hiện: for x in $(FILES); do command $$x; done. Lưu ý phần nhân đôi lên $$sẽ chuyển một phần duy nhất $vào vỏ. Ngoài ra, các mảnh vỏ là một lớp lót; để viết mã shell nhiều dòng, bạn sử dụng dấu gạch chéo ngược tiếp nối được xử lý bởi makechính nó và được gấp lại thành một dòng. Có nghĩa là dấu chấm phẩy phân tách lệnh shell là bắt buộc.
Kaz
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.