Makefile에서 여러 줄 문자열 변수를 만들 수 있습니까?


122

여러 줄로 된 문자열 (예 : 이메일 릴리스 공지의 본문) 인 makefile 변수를 만들고 싶습니다. 뭔가

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

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

그러나 나는 이것을 할 방법을 찾지 못하는 것 같습니다. 가능할까요?

답변:


172

예, 다음과 같이 define 키워드를 사용하여 여러 줄 변수를 선언 할 수 있습니다.

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

까다로운 부분은 여러 줄 변수를 makefile에서 다시 가져 오는 것입니다. "echo $ (ANNOUNCE_BODY)"를 사용하는 명백한 작업을 수행하면 다른 사람들이 여기에 게시 한 결과를 볼 수 있습니다. 쉘은 변수의 두 번째 및 후속 줄을 명령 자체로 처리하려고합니다.

그러나 변수 값을있는 그대로 셸에 환경 변수로 내 보낸 다음 셸에서 환경 변수 (make 변수 아님)로 참조 할 수 있습니다. 예를 들면 :

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

일반 make 변수 참조가 $$ANNOUNCE_BODY아닌 셸 환경 변수 참조를 나타내는 의 사용에 유의하십시오 $(ANNOUNCE_BODY). 또한 변수 참조 주위에 따옴표를 사용하여 개행 문자가 쉘 자체에서 해석되지 않도록하십시오.

물론,이 특별한 트릭은 플랫폼과 쉘에 민감 할 수 있습니다. GNU bash 3.2.13을 사용하여 Ubuntu Linux에서 테스트했습니다. YMMV.


1
export ANNOUNCE_BODY규칙 내에서만 변수를 설정합니다. $$ ANNOUNCE_BODY를 참조하여 다른 변수를 정의 할 수 없습니다.
anatoly techtonik

@techtonik ANNOUNCE_BODY다른 변수 정의에서 의 값을 사용하려면 다른 make 변수처럼 참조하십시오. 예를 들면 OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY). 물론 명령 export을 내리려면 여전히 트릭이 필요합니다 OTHER.
에릭 Melski

25

'메이크 파일에서 여러 줄 변수를 다시 가져 오는 방법'(Eric Melski가 '까다로운 부분'으로 언급)에 대한 또 다른 접근 방식은 subst함수를 사용 define하여 여러 줄 문자열에 도입 된 줄 바꿈 을 \n. 그런 다음 -e와 함께 사용 echo하여 해석하십시오. 이를 수행하는 에코를 얻으려면 .SHELL = bash를 설정해야 할 수도 있습니다.

이 접근 방식의 장점은 다른 이스케이프 문자를 텍스트에 넣고 존중한다는 것입니다.

이런 종류의 방법은 지금까지 언급 한 모든 접근 방식을 종합합니다.

당신은 다음과 같이 끝납니다.

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})'

최종 에코의 작은 따옴표가 중요합니다.


4
"echo -e"는 이식 가능하지 않습니다. 대신 printf (1)을 선호해야합니다.
MadScientist

2
그러나 훌륭한 대답은 실행하기 위해 =애프터 define ANNOUNCE_BODY를 제거 해야했습니다.
mschilli 2014

13

표준 출력에 변수의 내용 만 인쇄한다고 가정하면 다른 해결책이 있습니다.

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

1
이 no-op 규칙은 원하지 않는 메시지를 생성했습니다 make: 'do-echo' is up to date... :는 "어떤 작전을"사용하지 않는함으로써 내가 침묵 할 수 있었다 COMAND@: $(info $(YOUR_MULTILINE_VAR))
기욤 Papin

@GuillaumePapin 조금 늦었지만 .PHONYMakefile에 해당 규칙을 확인할 것이 없다고 알리는 데 사용할 수 있습니다 . Makefile은 원래 컴파일러를위한 것이 었습니다. 제가 착각 make하지 않았다면, 규칙이 아무것도 변경하지 않을 것이라고 예상 할 수없는 마술을하고 있으며 , 따라서 '완료'라고 가정합니다. .PHONY do-echo파일에 추가하면 make이것을 무시하고 어쨌든 코드를 실행하도록 지시합니다.
M3D

$(info ...)make 규칙 외부에 배치 할 수 있습니다 . 여전히 출력을 생성합니다.
Daniel Stevens


3

예. 다음과 같이 줄 바꿈을 이스케이프합니다 \.

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

최신 정보

아, 개행을 원하십니까? 그렇다면 바닐라 메이크에는 방법이 없다고 생각합니다. 그러나 명령 부분에서 항상 here-document를 사용할 수 있습니다.

[작동하지 않습니다. MadScientist의 의견 참조]

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

이것은 사실이지만 어떤 서식 (줄 바꿈)도 제공하지 않습니다. 그것은 단지 한 줄의 텍스트가된다
jonner

여기에 여러 줄로 된 문서는 GNU Make에 설명 된대로 작동하지 않습니다.
Matt B.

3
레시피 내부의 여러 줄 문서는 POSIX 표준을 지원하는 모든 표준 버전의 make에서 작동하지 않습니다. make 표준에서는 레시피의 각 줄이 별도의 셸에서 실행되어야합니다. Make는 이것이 here-document인지 아닌지를 알리기 위해 명령에 대해 구문 분석을 수행하지 않으며 다르게 처리합니다. 이것을 지원하는 make의 변형을 알고 있다면 (나는 들어 본 적이 없음) 아마도 명시 적으로 언급해야 할 것입니다.
MadScientist 2012 년

2

Eric Melski의 답변에 대한 포스트 스크립트 일뿐입니다. 텍스트에 명령 출력을 포함 할 수 있지만 쉘 구문 "$ (foo)"대신 Makefile 구문 "$ (shell foo)"를 사용해야합니다. 예를 들면 :

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

It can be downloaded from $(DOWNLOAD_URL).  

endef

2

이것은 here 문서를 제공하지 않지만 파이프에 적합한 방식으로 여러 줄 메시지를 표시합니다.

=====

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

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

=====

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 "---"

=====

출력은 다음과 같습니다.

=====

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

=====


1

줄 끝을 정의하기 위해 문자열의 \ n 문자를 사용하지 않는 이유는 무엇입니까? 또한 백 슬래시를 추가하여 여러 줄에 추가합니다.

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

나는 Erik Melski의 대답을 선호하지만 이것은 응용 프로그램에 따라 이미 트릭을 수행 할 수 있습니다.
Roalt

이것에 대한 질문이 있습니다. 이것은 모든 줄의 시작 부분에 여분의 "공백"이 있다는 점을 제외하고는 주로 잘 작동합니다 (첫 번째 줄 제외). 당신에게 이런 일이 일어나나요? 모든 텍스트를 한 줄에 \ n으로 구분하여 효과적으로 출력 할 수 있습니다. 문제는 Makefile 자체에서 매우보기 흉해 보인다는 것입니다!
Shahbaz 2012

해결 방법을 찾았습니다. $(subst \n ,\n,$(TEXT))더 나은 방법이 있었으면 좋겠지 만 텍스트를 넣었습니다 !
Shahbaz 2012


1

"define / endef"Make 구문을 사용해야합니다.

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

그런 다음이 변수의 값을 쉘 명령에 전달해야합니다. 그러나 Make 변수 대체를 사용하여이 작업을 수행하면 명령이 여러 개로 분할됩니다.

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

Qouting도 도움이되지 않습니다.

값을 전달하는 가장 좋은 방법은 환경 변수를 통해 전달하는 것입니다.

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

주의:

  1. 이 특정 대상에 대해 변수를 내보내므로 해당 환경을 다시 사용할 수 있으므로 오염되지 않습니다.
  2. 환경 변수를 사용하십시오 (변수 이름 주위에 이중 큐트 및 중괄호).
  3. 변수 주위에 따옴표 사용. 그것들이 없으면 줄 바꿈이 손실되고 모든 텍스트가 한 줄에 나타납니다.

1

.ONESHELL의 정신에 따라 .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

사용 예는 다음과 같습니다.

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

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

출력을 보여줍니다 (pid 27801로 가정).

>
Hello
World\n/27801/

이 접근 방식은 몇 가지 추가 기능을 허용합니다.

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

이러한 셸 옵션은 다음과 같습니다.

  • 실행되는대로 각 명령을 인쇄합니다.
  • 실패한 첫 번째 명령에서 종료
  • 정의되지 않은 쉘 변수 사용을 오류로 처리

다른 흥미로운 가능성이 스스로를 제안 할 것입니다.


1

나는 alhadis의 대답을 가장 좋아합니다. 그러나 열 형식을 유지하려면 한 가지 더 추가하십시오.

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

출력 :

:: 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

프로그램의 개요는 찾기 쉽고 명확해야합니다. Readme 및 / 또는 맨 페이지에이 수준의 정보를 추가하는 것이 좋습니다. 사용자가를 실행하면 make일반적으로 빌드 프로세스를 시작할 것으로 예상합니다.

1
나는 여러 번 make target 목록을보고 싶었다. 귀하의 의견이 의미가 없습니다. 사용자가 무엇을해야하는지 아는 데 3 초가 걸리면 사용자가 기대하는 것은 관련이 없지만 이와 같은 정보 대신 몇 시간이 걸릴 수 있습니다.
Xennex81

1
기대를 무언가를하는 이유로 사용하는 것도 순환적인 주장입니다. 사람들이 기대하기 때문에 우리는해야하고, 우리가하기 때문에 기대합니다.
Xennex81 2017-06-02

1

OP와 완전히 관련이 없지만 앞으로 누군가에게 도움이되기를 바랍니다. (이 질문은 Google 검색에서 가장 많이 나오는 질문입니다).

내 Makefile에서 파일의 내용을 도커 빌드 명령에 전달하고 싶었는데 많은 놀라움 끝에 다음과 같이 결정했습니다.

 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)

아래 예를 참조하십시오.

주의 : 내 특정 경우에, 나는의 예를 사용하여 이미지 빌드하는 동안, SSH 키를 전달하고 싶었 https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/을 (사용 git repo를 복제하기위한 다중 단계 도커 빌드, 빌드의 두 번째 단계에서 최종 이미지에서 ssh 키를 드롭)

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

GNU Make 3.82 이상에서는 여러 .ONESHELL줄 셸 조각 과 관련하여 옵션이 친구가됩니다. 다른 답변의 힌트를 종합하면 다음과 같은 결과를 얻습니다.

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))"

위의 예를 복사하여 편집기에 붙여 넣을 때 모든 <tab>문자가 유지 되는지 확인하십시오. 그렇지 않으면 version대상이 손상됩니다!

참고 .ONESHELL메이크 모든 대상의 원인이됩니다은 모든 명령에 대한 하나의 쉘을 사용합니다.


안타깝게도 작동하지 않습니다 : 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 make 3,81)
blueyed

@blueyed, 방금 GNU Make 3.82 및 GNU bash 4.2.45 (1) -release로 테스트했습니다. 예상대로 작동합니다. 또한 @printf ...문 앞에 공백 대신 선행 TAB 문자가 있는지 확인하십시오. TAB 은 항상 4 개의 공백으로 렌더링되는 것 같습니다 ...
sphakka

.ONESHELLmake 3.82에서 새로운 것으로 보입니다 .
blueyed

btw : 탭 대신 공백을 사용할 때 오류가 발생합니다 *** missing separator. Stop..
blueyed

0

실제로 유용한 답변은 아니지만 '정의'가 Ax의 답변대로 작동하지 않는다는 것을 나타냅니다 (댓글에 맞지 않음).

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)

'It'명령을 찾을 수 없다는 오류가 발생하여 ANNOUNCE BODY의 두 번째 줄을 명령으로 해석하려고합니다.


0

그것은 나를 위해 일했습니다.

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

0

GNU Makefile은 다음과 같은 작업을 수행 할 수 있습니다. 추악하고 그렇게해야한다고 말하지는 않겠지 만 특정 상황에서는 그렇게합니다.

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 .profile 파일이없는 경우 생성합니다.

이 솔루션은 응용 프로그램이 POSIX 셸 환경에서 GNU Makefile 만 사용하는 경우에 사용되었습니다. 이 프로젝트는 플랫폼 호환성이 문제가되는 오픈 소스 프로젝트가 아닙니다.

목표는 특정 작업 공간의 설정과 사용을 모두 용이하게하는 Makefile을 만드는 것이 었습니다. Makefile은 다른 특별한 아카이브 등을 요구하지 않고 다양한 간단한 리소스를 함께 제공합니다. 어떤 의미에서는 쉘 아카이브입니다. 그런 다음 절차는이 Makefile을 작업 할 폴더에 놓는 것과 같은 명령을 내릴 수 있습니다. 작업 공간을 설정하고를 입력 make workspace한 다음, blah를 입력하십시오.make blah .

까다로울 수있는 것은 셸 인용문을 찾는 것입니다. 위의 내용은 작업을 수행하며 Makefile에서 here 문서를 지정하는 아이디어에 가깝습니다. 일반적인 사용에 좋은 아이디어인지 여부는 완전히 다른 문제입니다.


0

크로스 플랫폼 사용에 대한 가장 안전한 대답은 한 줄에 하나의 에코를 사용하는 것입니다.

  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" >> $@

이렇게하면 에코 버전에 대한 가정을 사용할 수 없습니다.


0

문자열 대체 사용 :

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

그런 다음 레시피에

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

Make가 모든 항목 (공백에주의 )을 대체 하고 개행 문자 ( $$'\n') 로 교체하기 때문에 작동합니다 . 동등한 쉘 스크립트 호출을 다음과 같이 생각할 수 있습니다.

전에:

$ 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"

후:

$ 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"

나는 확실하지 않다 $'\n'POSIX가 아닌 시스템에서를 사용할 수 않지만 단일 개행 문자에 액세스 할 수 있다면 (외부 파일에서 문자열을 읽어도) 기본 원칙은 동일합니다.

이와 같은 메시지가 많은 경우 매크로 를 사용하여 노이즈를 줄일 수 있습니다 .

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

다음과 같이 호출 할 위치 :

@$(call print,$(ANNOUNCE_BODY))

이것이 누군가에게 도움이되기를 바랍니다. =)


나는 이것이 가장 좋다. 그러나 열 형식을 유지하려면 한 가지 더 추가하십시오. `시놉시스 : = :: 시놉시스 : Makefile \ | :: \ | :: 사용법 : \ | :: make .......... :이 메시지를 생성합니다. \ | :: 시놉시스를 만드십시오. :이 메시지를 생성합니다. \ | :: make clean .... : 원치 않는 중간체 및 표적 제거 \ | :: make all ...... : 처음부터 전체 시스템 컴파일 \ endef
jlettvin

주석은 코드를 허용하지 않습니다. 답변으로 보내드립니다. 나는 이것이 가장 좋다. 그러나 열 형식을 유지하려면 한 가지 더 추가하십시오. `시놉시스 : = :: 시놉시스 : Makefile``| ::``| :: 사용법 :``| :: make .......... :이 메시지를 생성합니다 .``| :: 시놉시스를 만드십시오. :이 메시지 생성``| :: make clean .... : 불필요한 중간 물질과 표적 제거``| :: make all ...... : 처음부터 전체 시스템 컴파일``endef`
jlettvin

@jlettvin 귀하의 답변에 대한 내 응답을 참조하십시오. 프로그램의 개요는 확실히해야 하지 , 특히하지의 기본 작업으로, 메이크 파일 내부에 내장 될 수있다.

0

대안으로 printf 명령을 사용할 수 있습니다. 이는 OSX 또는 기능이 적은 다른 플랫폼에서 유용합니다.

여러 줄 메시지를 간단히 출력하려면 :

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

문자열을 인수로 다른 프로그램에 전달하려는 경우 다음과 같이 할 수 있습니다.

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.