Các lệnh bash đa dòng trong makefile


120

Tôi có một cách rất thoải mái để biên dịch dự án của mình thông qua một vài dòng lệnh bash. Nhưng bây giờ tôi cần biên dịch nó qua makefile. Xem xét, mọi lệnh đều được chạy trong shell của riêng nó, câu hỏi của tôi là cách tốt nhất để chạy lệnh bash nhiều dòng, phụ thuộc vào nhau, trong makefile là gì? Ví dụ, như thế này:

for i in `find`
do
    all="$all $i"
done
gcc $all

Ngoài ra, ai đó có thể giải thích tại sao ngay cả lệnh một dòng bash -c 'a=3; echo $a > file'hoạt động chính xác trong terminal, nhưng lại tạo tệp trống trong trường hợp makefile?


4
Phần thứ hai nên là một câu hỏi riêng biệt. Thêm một câu hỏi khác chỉ tạo ra tiếng ồn.
l0b0

Câu trả lời:


136

Bạn có thể sử dụng dấu gạch chéo ngược để tiếp tục dòng. Tuy nhiên, lưu ý rằng shell nhận toàn bộ lệnh được nối thành một dòng, vì vậy bạn cũng cần kết thúc một số dòng bằng dấu chấm phẩy:

foo:
    for i in `find`;     \
    do                   \
        all="$$all $$i"; \
    done;                \
    gcc $$all

Nhưng nếu bạn chỉ muốn lấy toàn bộ danh sách được trả về bởi findlời gọi và chuyển nó đến gcc, bạn thực sự không nhất thiết cần một lệnh nhiều dòng:

foo:
    gcc `find`

Hoặc, sử dụng cách $(command)tiếp cận shell-thông thường hơn (hãy lưu ý đến việc $thoát):

foo:
    gcc $$(find)

2
Đối với đa dòng, bạn vẫn cần phải thoát khỏi các dấu hiệu đô la, như được chỉ ra trong các nhận xét ở nơi khác.
tripleee

1
Vâng, một câu trả lời ngắn 1. $: = $$ 2. append multiline với \ kẻ BTW bash thường khuyên bạn nên để thay thế `gọi find` với $$ (tìm)
Yauhen Yakimovich

89

Như đã chỉ ra trong câu hỏi, mọi lệnh con được chạy trong trình bao của chính nó . Điều này làm cho việc viết các kịch bản shell không tầm thường trở nên hơi lộn xộn - nhưng hoàn toàn có thể! Giải pháp là hợp nhất tập lệnh của bạn thành những gì thực hiện sẽ xem xét một lệnh con duy nhất (một dòng duy nhất).

Mẹo để viết script shell trong makefiles:

  1. Thoát khỏi việc sử dụng script $bằng cách thay thế bằng$$
  2. Chuyển đổi tập lệnh thành một dòng duy nhất bằng cách chèn ;giữa các lệnh
  3. Nếu bạn muốn viết kịch bản trên nhiều dòng, hãy thoát cuối dòng bằng \
  4. Bắt đầu theo tùy chọn set -eđể phù hợp với điều khoản của nhà sản xuất để hủy bỏ khi lệnh phụ bị lỗi
  5. Điều này là hoàn toàn tùy chọn, nhưng bạn có thể đặt dấu ngoặc cho tập lệnh với ()hoặc {}để nhấn mạnh tính liên kết của một chuỗi nhiều dòng - rằng đây không phải là chuỗi lệnh makefile điển hình

Đây là một ví dụ lấy cảm hứng từ OP:

mytarget:
    { \
    set -e ;\
    msg="header:" ;\
    for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
    msg="$$msg :footer" ;\
    echo msg=$$msg ;\
    }

4
Bạn cũng có thể muốn SHELL := /bin/bashtrong makefile của mình bật các tính năng dành riêng cho BASH như thay thế quy trình .
Brent Bradburn

8
Điểm tinh tế: Khoảng trắng sau {là rất quan trọng để ngăn việc diễn giải {setnhư một lệnh không xác định.
Brent Bradburn

3
Điểm tinh tế # 2: Trong makefile thì điều đó có thể không thành vấn đề, nhưng sự khác biệt giữa gói {}()tạo sự khác biệt lớn nếu đôi khi bạn muốn sao chép tập lệnh và chạy trực tiếp từ dấu nhắc trình bao. Bạn có thể tàn phá cá thể shell của mình bằng cách khai báo các biến, và đặc biệt là sửa đổi trạng thái với set, bên trong {}. ()ngăn không cho tập lệnh sửa đổi môi trường của bạn, điều này có lẽ được ưu tiên hơn. Ví dụ ( điều này sẽ kết thúc phiên shell của bạn ): { set -e ; } ; false.
Brent Bradburn

Bạn có thể bao gồm các nhận xét bằng cách sử dụng biểu mẫu sau command ; ## my comment \` (the comment is between :; `và` \ '). Điều này có vẻ hoạt động tốt ngoại trừ việc nếu bạn chạy lệnh theo cách thủ công (bằng cách sao chép và dán), lịch sử lệnh sẽ bao gồm nhận xét theo cách phá vỡ lệnh (nếu bạn cố gắng sử dụng lại). [Lưu ý: Đánh dấu cú pháp bị hỏng đối với nhận xét này do sử dụng dấu gạch chéo ngược bên trong dấu gạch ngược.]
Brent Bradburn

Nếu bạn muốn ngắt một chuỗi trong bash / perl / script bên trong makefile, hãy đóng dấu ngoặc kép, dấu gạch chéo ngược, dòng mới, (không thụt lề) trích dẫn chuỗi mở. Thí dụ; perl -e QUOTE print 1; BÁO GIÁ BACKSLASH NEWLINE BÁO GIÁ in 2 BÁO GIÁ
mosh

2

Có gì sai khi chỉ gọi các lệnh?

foo:
       echo line1
       echo line2
       ....

Và đối với câu hỏi thứ hai của bạn, bạn cần phải thoát $bằng cách sử dụng $$thay thế, tức là bash -c '... echo $$a ...'.

CHỈNH SỬA: Ví dụ của bạn có thể được viết lại thành một tập lệnh dòng đơn như thế này:

gcc $(for i in `find`; do echo $i; done)

5
Nguyên nhân "mọi lệnh được chạy trong trình bao của riêng nó", trong khi tôi cần sử dụng kết quả của command1 trong command2. Như trong ví dụ.
Jofsey

Nếu bạn cần truyền thông tin giữa các lần gọi trình bao, bạn cần sử dụng một số dạng lưu trữ bên ngoài, chẳng hạn như tệp tạm thời. Nhưng bạn luôn có thể viết lại mã của mình để thực thi nhiều lệnh trong cùng một trình bao.
JesperE

+1, đặc biệt đối với phiên bản đơn giản hóa. Và nó không giống với việc chỉ thực hiện `` gcc `find ''?
Eldar Abusalimov

1
Vâng, đúng vậy. Nhưng tất nhiên bạn có thể làm những việc phức tạp hơn là chỉ 'tiếng vọng'.
JesperE

2

Tất nhiên, cách thích hợp để viết Makefile là thực sự ghi lại các mục tiêu phụ thuộc vào nguồn nào. Trong trường hợp nhỏ, giải pháp được đề xuất sẽ foophụ thuộc vào chính nó, nhưng tất nhiên, makeđủ thông minh để loại bỏ sự phụ thuộc vòng tròn. Nhưng nếu bạn thêm một tệp tạm thời vào thư mục của mình, nó sẽ trở thành một phần của chuỗi phụ thuộc một cách "kỳ diệu". Tốt hơn là tạo một danh sách rõ ràng các phụ thuộc một lần và mãi mãi, có lẽ thông qua một tập lệnh.

GNU make biết cách chạy gccđể tạo tệp thực thi từ một tập hợp .c.htệp, vì vậy có thể tất cả những gì bạn thực sự cần

foo: $(wildcard *.h) $(wildcard *.c)

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.