Programing

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

crosscheck 2020. 8. 3. 17:41
반응형

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


여러 줄로 된 makefile 변수를 만들고 싶습니다 (예 : 전자 메일 릴리스 알림 본문). 같은

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

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

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


예, 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)"를 사용하는 명백한 일을하면 다른 사람들이 여기에 게시 한 결과를 볼 수 있습니다. 쉘은 변수의 두 번째 및 그 이후의 행을 명령으로 처리하려고합니다.

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

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

의 사용에주의 $$ANNOUNCE_BODY가 아니라, 쉘 환경 변수 참조를 나타내는 $(ANNOUNCE_BODY)일반 메이크업 변수 참조가 될 것이다. 또한 줄 바꿈이 쉘 자체에서 해석되지 않도록 변수 참조 주위에 따옴표를 사용해야합니다.

물론,이 특정 트릭은 플랫폼 및 셸에 따라 다를 수 있습니다. GNU bash 3.2.13으로 Ubuntu Linux에서 테스트했습니다. YMMV.


'Makefile에서 여러 줄 변수를 다시 가져 오는'(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})'

최종 반향의 작은 따옴표는 매우 중요합니다.


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

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

예. 다음과 같이 개행을 피하십시오 \.

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

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

줄 끝을 정의하기 위해 문자열에서 \ 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"

GNU`make '매뉴얼, 6.8 : 다중 라인 변수 정의


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

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

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

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

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

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

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

주의:

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

GNU Make를 사용하면 여러 .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대상이 중단됩니다!


이것은 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
---
$

=====


.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

이 쉘 옵션은 다음과 같습니다.

  • 명령이 실행될 때마다 인쇄
  • 첫 번째 실패한 명령에서 종료
  • Treat use of undefined shell variables as an error

Other interesting possibilities will likely suggest themselves.


I like alhadis's answer best. But to keep columnar formatting, add one more thing.

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

Outputs:

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

Not completely related to the OP, but hopefully this will help someone in future. (as this question is the one that comes up most in google searches).

In my Makefile, I wanted to pass the contents of a file, to a docker build command, after much consternation, I decided to:

 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)

see example below.

nb: In my particular case, I wanted to pass an ssh key, during the image build, using the example from https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (using a multi stage docker build to clone a git repo, then drop the ssh key from the final image in the 2nd stage of the build)

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

Not really a helpful answer, but just to indicate that 'define' does not work as answered by Ax (did not fit in a comment):

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 gives an error that the command 'It' cannot be found, so it tries to interpret the second line of ANNOUNCE BODY as a command.


It worked for me:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

GNU Makefile can do things like the following. It is ugly, and I won't say you should do it, but I do in certain situations.

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 creates a .profile file if one does not exist.

This solution was used where the application will only use GNU Makefile in a POSIX shell environment. The project is not an open source project where platform compatibility is an issue.

The goal was to create a Makefile that facilitates both setup and use of a particular kind of workspace. The Makefile brings along with it various simple resources without requiring things like another special archive, etc. It is, in a sense, a shell archive. A procedure can then say things like drop this Makefile in the folder to work in. Set up your workspace enter make workspace, then to do blah, enter make blah, etc.

What can get tricky is figuring out what to shell quote. The above does the job and is close to the idea of specifying a here document in the Makefile. Whether it is a good idea for general use is a whole other issue.


I believe the safest answer for cross-platform use would be to use one echo per line:

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

This avoids making any assumptions of on the version of echo available.


Use string substitution:

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

Then in your recipe, put

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

This works because Make is substituting all occurrences of (note the space) and swapping it with a newline character ($$'\n'). You can think of the equivalent shell-script invocations as being something like this:

Before:

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

After:

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

I'm not sure if $'\n' is available on non-POSIX systems, but if you can gain access to a single newline character (even by reading a string from an external file), the underlying principle is the same.

If you have many messages like this, you can reduce noise by using a macro:

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

Where you'd invoke it like this:

@$(call print,$(ANNOUNCE_BODY))

Hope this helps somebody. =)


As an alternative you can use the printf command. This is helpful on OSX or other platforms with less features.

To simply output a multiline message:

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

If you are trying to pass the string as an arg to another program, you can do so like this:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"

참고URL : https://stackoverflow.com/questions/649246/is-it-possible-to-create-a-multi-line-string-variable-in-a-makefile

반응형