Có thể tạo biến chuỗi nhiều dòng trong Makefile không


122

Tôi muốn tạo một biến makefile là một chuỗi nhiều dòng (ví dụ: phần nội dung của thông báo phát hành email). cái gì đó như

ANNOUNCE_BODY="
Version $(VERSION) of $(PACKAGE_NAME) has been released

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

Nhưng tôi dường như không thể tìm ra cách để làm điều này. Có khả thi không?

Câu trả lời:


172

Có, bạn có thể sử dụng từ khóa xác định để khai báo một biến nhiều dòng, như sau:

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Phần khó khăn là đưa biến nhiều dòng của bạn trở lại khỏi makefile. Nếu bạn chỉ làm điều hiển nhiên là sử dụng "echo $ (ANNOUNCE_BODY)", bạn sẽ thấy kết quả mà những người khác đã đăng ở đây - shell cố gắng xử lý các dòng thứ hai và tiếp theo của biến dưới dạng các lệnh.

Tuy nhiên, bạn có thể xuất giá trị biến nguyên trạng sang shell dưới dạng một biến môi trường, rồi tham chiếu nó từ shell dưới dạng một biến môi trường (KHÔNG PHẢI là biến make). Ví dụ:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Lưu ý việc sử dụng $$ANNOUNCE_BODY, chỉ ra một tham chiếu biến môi trường shell, thay vì $(ANNOUNCE_BODY), sẽ là một tham chiếu biến make thông thường. Ngoài ra, hãy đảm bảo sử dụng dấu ngoặc kép xung quanh tham chiếu biến của bạn, để đảm bảo rằng các dòng mới không được diễn giải bởi chính shell.

Tất nhiên, thủ thuật cụ thể này có thể nhạy cảm với nền tảng và vỏ. Tôi đã thử nghiệm nó trên Ubuntu Linux với GNU bash 3.2.13; YMMV.


1
export ANNOUNCE_BODYchỉ đặt biến bên trong các quy tắc - nó không cho phép tham chiếu $$ ANNOUNCE_BODY để xác định các biến khác.
anatoly techtonik

@techtonik nếu bạn muốn sử dụng giá trị của ANNOUNCE_BODYtrong các định nghĩa biến khác, chỉ cần tham chiếu nó giống như bất kỳ biến make nào khác. Ví dụ OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY),. Tất nhiên bạn vẫn cần exportthủ thuật nếu bạn muốn thoát OTHERra trong một lệnh.
Eric Melski

25

Một cách tiếp cận khác để 'đưa biến nhiều dòng của bạn trở lại makefile' (được Eric Melski lưu ý là 'phần khó'), là lập kế hoạch sử dụng substhàm để thay thế các dòng mới được giới thiệu definetrong chuỗi nhiều dòng của bạn với \n. Sau đó, sử dụng -e with echođể giải thích chúng. Bạn có thể cần đặt .SHELL = bash để nhận được tiếng vọng thực hiện điều này.

Một lợi thế của cách tiếp cận này là bạn cũng đưa các ký tự thoát khác như vậy vào văn bản của mình và được chúng tôn trọng.

Loại tổng hợp này tổng hợp tất cả các phương pháp được đề cập cho đến nay ...

Bạn kết thúc với:

define newline


endef

define ANNOUNCE_BODY=
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.  

It can be downloaded from $(DOWNLOAD_URL).  

endef

someTarget:
    echo -e '$(subst $(newline),\n,${ANNOUNCE_BODY})'

Lưu ý rằng các dấu ngoặc kép về tiếng vọng cuối cùng là rất quan trọng.


4
Lưu ý rằng "echo -e" không phải là di động. Bạn có thể thích printf (1) thay thế.
MadScientist

2
câu trả lời tuyệt vời, tuy nhiên, tôi phải xóa phần =sau define ANNOUNCE_BODYđể nó chạy.
mschilli

13

Giả sử bạn chỉ muốn in nội dung của biến của mình trên đầu ra chuẩn, có một giải pháp khác:

do-echo:
    $(info $(YOUR_MULTILINE_VAR))

1
Quy tắc không-op này tạo ra một thông điệp không mong muốn: make: 'do-echo' is up to date.. Bằng cách sử dụng một "không op" COMAND tôi đã có thể im lặng đó:@: $(info $(YOUR_MULTILINE_VAR))
Guillaume Papin

@GuillaumePapin Hơi muộn, nhưng bạn có thể sử dụng .PHONYđể nói với Makefile của mình rằng không có gì để kiểm tra quy tắc đó. Makefiles ban đầu dành cho các trình biên dịch, nếu tôi không nhầm, thì tôi makecũng đang làm một phép thuật nào đó mà tôi không hiểu để đoán trước rằng quy tắc sẽ không thay đổi bất cứ điều gì, và như vậy giả sử nó đã được 'hoàn thành'. Việc thêm .PHONY do-echovào tệp của bạn sẽ yêu makecầu bỏ qua điều này và vẫn chạy mã.
M3D

Bạn có thể đặt $(info ...)bên ngoài quy tắc thực hiện. Nó vẫn sẽ tạo ra đầu ra.
Daniel Stevens


3

Đúng. Bạn thoát khỏi dòng mới với \:

VARIABLE="\
THIS IS A VERY LONG\
TEXT STRING IN A MAKE VARIABLE"

cập nhật

Ah, bạn muốn dòng mới? Vậy thì không, tôi không nghĩ có cách nào trong vani Make. Tuy nhiên, bạn luôn có thể sử dụng tài liệu tại đây trong phần lệnh

[Điều này không hiệu quả, hãy xem bình luận từ MadScientist]

foo:
    echo <<EOF
    Here is a multiple line text
    with embedded newlines.
    EOF

Điều này đúng, nhưng nó không cung cấp cho tôi bất kỳ định dạng nào (dòng mới). Nó chỉ trở thành một dòng văn bản
jonner

Tài liệu nhiều dòng ở đây không hoạt động như được mô tả trong GNU Make.
Matt B.

3
Đa dòng ở đây các tài liệu bên trong công thức nấu ăn sẽ không hoạt động trong BẤT CỨ phiên bản tiêu chuẩn nào của món ăn hỗ trợ tiêu chuẩn POSIX: tiêu chuẩn làm món ăn yêu cầu mỗi dòng riêng biệt của công thức phải được chạy trong một lớp vỏ riêng biệt. Make không thực hiện bất kỳ phân tích cú pháp nào trên lệnh để cho biết đó là tài liệu tại đây hay không và xử lý nó theo cách khác. Nếu bạn biết về một số biến thể của make hỗ trợ điều này (tôi chưa bao giờ nghe nói về một biến thể này), bạn có thể nên nói rõ ràng.
MadScientist

2

Chỉ là một phần tái bút cho câu trả lời của Eric Melski: Bạn có thể bao gồm đầu ra của các lệnh trong văn bản, nhưng bạn phải sử dụng cú pháp Makefile "$ (shell foo)" thay vì cú pháp shell "$ (foo)". Ví dụ:

define ANNOUNCE_BODY  
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.  

It can be downloaded from $(DOWNLOAD_URL).  

endef

2

Điều này không cung cấp một tài liệu ở đây, nhưng nó hiển thị một thông báo nhiều dòng theo cách phù hợp với đường ống.

=====

MSG = this is a\\n\
multi-line\\n\
message

method1:
     @$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'

=====

Bạn cũng có thể sử dụng các macro có thể gọi của Gnu:

=====

MSG = this is a\\n\
multi-line\\n\
message

method1:
        @echo "Method 1:"
        @$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'
        @echo "---"

SHOW = $(SHELL) -c "echo '$1'" | sed -e 's/^ //'

method2:
        @echo "Method 2:"
        @$(call SHOW,$(MSG))
        @echo "---"

=====

Đây là kết quả:

=====

$ make method1 method2
Method 1:
this is a
multi-line
message
---
Method 2:
this is a
multi-line
message
---
$

=====


1

Tại sao bạn không sử dụng ký tự \ n trong chuỗi của mình để xác định phần cuối của dòng? Cũng thêm dấu gạch chéo ngược bổ sung để thêm nó trên nhiều dòng.

ANNOUNCE_BODY=" \n\
Version $(VERSION) of $(PACKAGE_NAME) has been released \n\
\n\
It can be downloaded from $(DOWNLOAD_URL) \n\
\n\
etc, etc"

Tôi thích câu trả lời của Erik Melski hơn nhưng điều này có thể làm được thủ thuật cho bạn, tùy thuộc vào ứng dụng của bạn.
Roalt

Tôi có một câu hỏi về điều này. Điều này về cơ bản hoạt động tốt, ngoại trừ tôi thấy thêm một "khoảng trắng" ở đầu mỗi dòng (ngoại trừ dòng đầu tiên). Điều này có xảy ra với bạn không? Tôi có thể đặt tất cả văn bản trong một dòng, cách nhau bởi \ n để tạo hiệu quả đầu ra mà tôi thích. Vấn đề là nó trông rất xấu trong chính Makefile!
Shahbaz

Tôi đã tìm thấy một giải pháp. Tôi đặt văn bản thông qua $(subst \n ,\n,$(TEXT))mặc dù tôi ước có một cách tốt hơn!
Shahbaz


1

Bạn nên sử dụng "xác định / endef" Tạo cấu trúc:

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Sau đó, bạn nên chuyển giá trị của biến này vào lệnh shell. Tuy nhiên, nếu bạn làm điều này bằng cách sử dụng Thực hiện thay thế biến, nó sẽ khiến lệnh bị chia thành nhiều:

ANNOUNCE.txt:
  echo $(ANNOUNCE_BODY) > $@               # doesn't work

Qouting cũng không giúp được gì.

Cách tốt nhất để chuyển giá trị là chuyển nó qua biến môi trường:

ANNOUNCE.txt: export ANNOUNCE_BODY:=$(ANNOUNCE_BODY)
ANNOUNCE.txt:
  echo "$${ANNOUNCE_BODY}" > $@

Để ý:

  1. Biến được xuất cho mục tiêu cụ thể này, để bạn có thể tái sử dụng môi trường đó sẽ không bị ô nhiễm nhiều;
  2. Sử dụng biến môi trường (qoutes kép và dấu ngoặc nhọn xung quanh tên biến);
  3. Sử dụng các dấu ngoặc kép xung quanh biến. Nếu không có chúng, các dòng mới sẽ bị mất và tất cả văn bản sẽ xuất hiện trên một dòng.

1

Với tinh thần .ONESHELL, bạn có thể tiến khá gần trong các môi trường đầy thử thách .ONESHELL:

define _oneshell_newline_


endef

define oneshell
@eval "$$(printf '%s\n' '$(strip                            \
                         $(subst $(_oneshell_newline_),\n,  \
                         $(subst \,\/,                      \
                         $(subst /,//,                      \
                         $(subst ','"'"',$(1))))))' |       \
          sed -e 's,\\n,\n,g' -e 's,\\/,\\,g' -e 's,//,/,g')"
endef

Một ví dụ về việc sử dụng sẽ như thế này:

define TEST
printf '>\n%s\n' "Hello
World\n/$$$$/"
endef

all:
        $(call oneshell,$(TEST))

Điều đó cho thấy đầu ra (giả sử pid 27801):

>
Hello
World\n/27801/

Cách tiếp cận này cho phép một số chức năng bổ sung:

define oneshell
@eval "set -eux ; $$(printf '%s\n' '$(strip                            \
                                    $(subst $(_oneshell_newline_),\n,  \
                                    $(subst \,\/,                      \
                                    $(subst /,//,                      \
                                    $(subst ','"'"',$(1))))))' |       \
                     sed -e 's,\\n,\n,g' -e 's,\\/,\\,g' -e 's,//,/,g')"
endef

Các tùy chọn shell này sẽ:

  • In từng lệnh khi nó được thực thi
  • Thoát khỏi lệnh đầu tiên bị lỗi
  • Coi việc sử dụng các biến shell không xác định là một lỗi

Các khả năng thú vị khác có thể sẽ tự gợi ý.


1

Tôi thích câu trả lời của alhadis nhất. Nhưng để giữ định dạng cột, hãy thêm một điều nữa.

SYNOPSIS := :: Synopsis: Makefile\
| ::\
| :: Usage:\
| ::    make .......... : generates this message\
| ::    make synopsis . : generates this message\
| ::    make clean .... : eliminate unwanted intermediates and targets\
| ::    make all ...... : compile entire system from ground-up\
endef

Kết quả đầu ra:

:: Synopsis: Makefile 
:: 
:: Usage: 
:: make .......... : generates this message 
:: make synopsis . : generates this message 
:: make clean .... : eliminate unwanted intermediates and targets 
:: make all ...... : compile entire system from ground-up

Tóm tắt chương trình phải dễ dàng và rõ ràng để xác định vị trí. Tôi khuyên bạn nên thêm mức thông tin này vào readme và / hoặc manpage. Khi người dùng chạy make, họ thường làm như vậy với mong muốn bắt đầu quá trình xây dựng.

1
Tôi đã nhiều lần muốn chỉ xem một danh sách các mục tiêu thực hiện. Nhận xét của bạn không có ý nghĩa. Những gì người dùng mong đợi sẽ không liên quan nếu họ mất 3 giây để biết phải làm gì, trong khi thay vì bất kỳ thông tin nào như thế này, đôi khi có thể mất hàng giờ.
Xennex81

1
Sử dụng kỳ vọng như một lý do để làm điều gì đó cũng là một lập luận vòng tròn: bởi vì mọi người mong đợi nó, chúng ta phải làm nó, và bởi vì chúng ta làm nó, họ mong đợi nó.
Xennex81

1

Không hoàn toàn liên quan đến OP, nhưng hy vọng điều này sẽ giúp ích cho ai đó trong tương lai. (vì câu hỏi này là câu hỏi xuất hiện nhiều nhất trong các tìm kiếm của google).

Trong Makefile của mình, tôi muốn chuyển nội dung của một tệp đến lệnh xây dựng docker, sau nhiều lần băn khoăn, tôi quyết định:

 base64 encode the contents in the Makefile (so that I could have a single line and pass them as a docker build arg...)
 base64 decode the contents in the Dockerfile (and write them to a file)

xem ví dụ bên dưới.

nb: Trong trường hợp cụ thể của tôi, tôi muốn chuyển một khóa ssh, trong quá trình xây dựng hình ảnh, bằng cách sử dụng ví dụ từ https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (sử dụng một bản dựng docker nhiều giai đoạn để sao chép git repo, sau đó thả khóa ssh khỏi hình ảnh cuối cùng trong giai đoạn thứ 2 của bản dựng)

Makefile

...
MY_VAR_ENCODED=$(shell cat /path/to/my/file | base64)

my-build:
    @docker build \
      --build-arg MY_VAR_ENCODED="$(MY_VAR_ENCODED)" \
      --no-cache \
      -t my-docker:build .
...

Dockerfile

...
ARG MY_VAR_ENCODED

RUN mkdir /root/.ssh/  && \
    echo "${MY_VAR_ENCODED}" | base64 -d >  /path/to/my/file/in/container
... 

1

Với GNU Make 3.82 trở lên, .ONESHELLtùy chọn là người bạn của bạn khi nói đến các đoạn mã đa dòng. Tổng hợp các gợi ý từ các câu trả lời khác, tôi nhận được:

VERSION = 1.2.3
PACKAGE_NAME = foo-bar
DOWNLOAD_URL = $(PACKAGE_NAME).somewhere.net

define nwln

endef

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

.ONESHELL:

# mind the *leading* <tab> character
version:
    @printf "$(subst $(nwln),\n,$(ANNOUNCE_BODY))"

Đảm bảo rằng khi sao chép và dán ví dụ trên vào trình chỉnh sửa của bạn, mọi <tab>ký tự đều được giữ nguyên, nếu khôngversion mục tiêu sẽ bị hỏng!

Lưu ý rằng điều đó .ONESHELLsẽ khiến tất cả các mục tiêu trong Makefile sử dụng một trình bao duy nhất cho tất cả các lệnh của chúng.


Thật không may là điều đó không hoạt động: make version printf "Version 1.2.3 of foo-bar has been released. /bin/sh: 1: Syntax error: Unterminated quoted string make: *** [version] Error 2(GNU tạo 3,81)
blueyed

@blueyed, tôi vừa thử nghiệm nó với GNU Make 3.82 và GNU bash 4.2.45 (1) -Xin vui lòng: nó hoạt động như mong đợi. Ngoài ra, vui lòng kiểm tra sự hiện diện của ký tự TAB đứng đầu, thay vì khoảng trống, trước @printf ...câu lệnh - có vẻ như các TAB luôn được hiển thị dưới dạng 4 dấu cách ...
sphakka

Nó có vẻ .ONESHELLlà mới trong thực hiện 3.82.
blueyed

btw: lỗi khi sử dụng dấu cách thay vì tab sẽ là *** missing separator. Stop..
blueyed

0

Không thực sự là một câu trả lời hữu ích, nhưng chỉ để chỉ ra rằng 'xác định' không hoạt động như câu trả lời của Axe (không phù hợp với một nhận xét):

VERSION=4.3.1
PACKAGE_NAME=foobar
DOWNLOAD_URL=www.foobar.com

define ANNOUNCE_BODY
    Version $(VERSION) of $(PACKAGE_NAME) has been released
    It can be downloaded from $(DOWNLOAD_URL)
    etc, etc
endef

all:
    @echo $(ANNOUNCE_BODY)

Nó đưa ra lỗi không thể tìm thấy lệnh 'Nó', vì vậy nó cố gắng diễn giải dòng thứ hai của ANNOUNCE BODY dưới dạng một lệnh.


0

Nó đã làm việc cho tôi:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

0

GNU Makefile có thể làm những việc như sau. Nó xấu xí, và tôi sẽ không nói bạn nên làm điều đó, nhưng tôi làm trong một số tình huống nhất định.

PROFILE = \
\#!/bin/sh.exe\n\
\#\n\
\# A MinGW equivalent for .bash_profile on Linux.  In MinGW/MSYS, the file\n\
\# is actually named .profile, not .bash_profile.\n\
\#\n\
\# Get the aliases and functions\n\
\#\n\
if [ -f \$${HOME}/.bashrc ]\n\
then\n\
  . \$${HOME}/.bashrc\n\
fi\n\
\n\
export CVS_RSH="ssh"\n  
#
.profile:
        echo -e "$(PROFILE)" | sed -e 's/^[ ]//' >.profile

make .profile tạo tệp .profile nếu tệp không tồn tại.

Giải pháp này đã được sử dụng khi ứng dụng sẽ chỉ sử dụng GNU Makefile trong môi trường shell POSIX. Dự án không phải là một dự án mã nguồn mở mà tính tương thích của nền tảng là một vấn đề.

Mục tiêu là tạo một Makefile hỗ trợ cả việc thiết lập và sử dụng một loại không gian làm việc cụ thể. Makefile mang đến cho nó nhiều tài nguyên đơn giản khác nhau mà không yêu cầu những thứ như một kho lưu trữ đặc biệt khác, v.v. Theo một nghĩa nào đó, nó là một kho lưu trữ shell. Sau đó, một thủ tục có thể nói những điều như thả Makefile này vào thư mục để làm việc. Thiết lập vùng làm việc của bạn enter make workspace, sau đó để làm blah, hãy entermake blah , v.v.

Điều có thể trở nên khó khăn là tìm ra những gì để bao gồm báo giá. Ở trên thực hiện công việc và gần với ý tưởng chỉ định một tài liệu tại đây trong Makefile. Cho dù đó là một ý tưởng tốt để sử dụng chung là một vấn đề hoàn toàn khác.


0

Tôi tin rằng câu trả lời an toàn nhất cho việc sử dụng đa nền tảng là sử dụng một tiếng vọng trên mỗi dòng:

  ANNOUNCE.txt:
    rm -f $@
    echo "Version $(VERSION) of $(PACKAGE_NAME) has been released" > $@
    echo "" >> $@
    echo "It can be downloaded from $(DOWNLOAD_URL)" >> $@
    echo >> $@
    echo etc, etc" >> $@

Điều này tránh đưa ra bất kỳ giả định nào về phiên bản tiếng vọng có sẵn.


0

Sử dụng thay thế chuỗi :

VERSION := 1.1.1
PACKAGE_NAME := Foo Bar
DOWNLOAD_URL := https://go.get/some/thing.tar.gz

ANNOUNCE_BODY := Version $(VERSION) of $(PACKAGE_NAME) has been released. \
    | \
    | It can be downloaded from $(DOWNLOAD_URL) \
    | \
    | etc, etc

Sau đó, trong công thức của bạn, hãy đặt

    @echo $(subst | ,$$'\n',$(ANNOUNCE_BODY))

Điều này hoạt động vì Make đang thay thế tất cả các lần xuất hiện của (lưu ý dấu cách) và hoán đổi nó bằng một ký tự dòng mới ( $$'\n'). Bạn có thể nghĩ về các lời gọi shell-script tương đương như sau:

Trước:

$ echo "Version 1.1.1 of Foo Bar has been released. | | It can be downloaded from https://go.get/some/thing.tar.gz | | etc, etc"

Sau:

$ echo "Version 1.1.1 of Foo Bar has been released.
>
> It can be downloaded from https://go.get/some/thing.tar.gz
> 
> etc, etc"

Tôi không chắc liệu $'\n'có sẵn trên các hệ thống không phải POSIX hay không, nhưng nếu bạn có thể có quyền truy cập vào một ký tự dòng mới (thậm chí bằng cách đọc một chuỗi từ tệp bên ngoài), nguyên tắc cơ bản là giống nhau.

Nếu bạn có nhiều thông báo như thế này, bạn có thể giảm nhiễu bằng cách sử dụng macro :

print = $(subst | ,$$'\n',$(1))

Nơi bạn sẽ gọi nó như thế này:

@$(call print,$(ANNOUNCE_BODY))

Hy vọng điều này sẽ giúp ai đó. =)


Tôi thích cái này nhất. Nhưng để giữ định dạng cột, hãy thêm một điều nữa. `SYNOPSIS: = :: Tóm tắt nội dung: Makefile \ | :: \ | :: Cách sử dụng: \ | :: make ..........: tạo thông báo này \ | :: làm tóm tắt. : tạo thông báo này \ | :: làm sạch ....: loại bỏ các chất trung gian và mục tiêu không mong muốn \ | :: make all ......: biên dịch toàn bộ hệ thống từ ground-up \ endef
jlettvin

Nhận xét không cho phép mã. Sẽ gửi như một câu trả lời. Tôi thích cái này nhất. Nhưng để giữ định dạng cột, hãy thêm một điều nữa. `SYNOPSIS: = :: Tóm tắt nội dung: Makefile '' | :: `` | :: Cách sử dụng: `` | :: make ..........: tạo thông báo này '' | :: làm tóm tắt. : tạo thông báo này '' | :: làm sạch ....: loại bỏ các chất trung gian và mục tiêu không mong muốn '' | :: make all ......: biên dịch toàn bộ hệ thống từ `` endef` '
jlettvin

@jlettvin Xem phản hồi của tôi cho câu trả lời của bạn. Tóm tắt của chương trình chắc chắn không nên được nhúng vào bên trong Makefile, đặc biệt là không phải là một tác vụ mặc định.

0

Thay vào đó, bạn có thể sử dụng lệnh printf. Điều này hữu ích trên OSX hoặc các nền tảng khác có ít tính năng hơn.

Để đơn giản xuất một tin nhắn nhiều dòng:

all:
        @printf '%s\n' \
            'Version $(VERSION) has been released' \
            '' \
            'You can download from URL $(URL)'

Nếu bạn đang cố gắng chuyển chuỗi dưới dạng đối số cho một chương trình khác, bạn có thể làm như sau:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"
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.