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

トリッキーな部分は、メイクファイルから複数行の変数を戻すことです。"echo $(ANNOUNCE_BODY)"を使用するという明らかなことを行うと、他のユーザーがここに投稿した結果が表示されます。シェルは、変数の2行目以降をコマンドとして処理しようとします。

ただし、変数値をそのまま環境変数としてシェルにエクスポートし、それをシェルから環境変数(make変数ではない)として参照できます。例えば:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

$$ANNOUNCE_BODYではなく、シェル環境変数の参照を示すの使用に注意してください$(ANNOUNCE_BODY)通常のmake変数参照であるでは。また、変数参照を引用符で囲み、改行がシェル自体によって解釈されないようにしてください。

もちろん、この特定のトリックはプラットフォームとシェルに依存する可能性があります。私はそれをGNU bash 3.2.13を備えたUbuntu Linuxでテストしました。YMMV。


1
export ANNOUNCE_BODYルール内の変数のみを設定します。$$ ANNOUNCE_BODYを参照して他の変数を定義することはできません。
anatly techtonik 2013年

@techtonikの値をANNOUNCE_BODY他の変数定義で使用する場合は、他のmake変数と同じように参照してください。たとえば、OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY)。もちろん、コマンドで抜け出しexportたい場合は、まだトリックが必要ですOTHER
Eric Melski 2013年

25

「Makefileから複数行の変数を取り戻す」別のアプローチ(Eric Melskiによって「注意が必要な部分」と記されています)は、subst関数を使用しdefineて、複数行の文字列で導入された改行をで置き換えることを計画すること\nです。次に、-e with 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.。「何のOPを」使用しないことで、私はそれを沈黙することができましたCOMAND:@: $(info $(YOUR_MULTILINE_VAR))
ギヨームパパン

@GuillaumePapin少し遅れますが.PHONY、Makefileにそのルールをチェックするものがないことを伝えるために使用できます。Makefileはもともとコンパイラー用でしたが、誤解makeしない限り、ルールが何も変更されないことを予期していないため、理解できない魔法をかけているため、ルールは「完了」していると見なされます。.PHONY do-echoファイルに追加すると、makeこれを無視してとにかくコードを実行するように指示されます。
M3D 2017年

$(info ...)makeルールの外に配置できます。それでも出力は生成されます。
Daniel Stevens

ドキュメンテーション:制御機能の作成
Daniel Stevens

3

はい。あなたは改行をエスケープし\ます:

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

更新

ああ、あなたは改行が欲しいですか?では、いいえ、バニラメイクには方法がないと思います。ただし、コマンド部分では常にヒアドキュメントを使用できます

[これは機能しません。MadScientistからのコメントを参照してください]

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

これは本当ですが、書式設定(改行)はありません。テキストの1行になる
jonner 2009年

複数行のヒアドキュメントは、GNU Makeで説明されているように機能しません。
Matt B.

3
複数行のhere docs内のレシピは、POSIX標準をサポートするmakeの標準バージョンでは機能しません。make標準では、レシピの各行を別々のシェルで実行する必要があります。Makeは、ヒアドキュメントであるかどうかを判断するコマンドの解析を行わず、別の方法で処理します。これをサポートするmakeの変種を知っている場合(私はこれを聞いたことがありません)、おそらく明示的に述べる必要があります。
MadScientist

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

これはヒアドキュメントを提供しませんが、パイプに適した方法で複数行のメッセージを表示します。

=====

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 2009年

これについて質問を受けました。これは、各行の先頭(最初の行を除く)に余分な "スペース"があることを除いて、基本的に問題なく機能します。これはあなたに起こりますか?すべてのテキストを\ nで区切って1行に入れることができるので、好きな出力を効果的に作成できます。問題は、Makefile自体が非常に醜く見えることです。
Shahbaz

回避策を見つけました。私$(subst \n ,\n,$(TEXT))はもっと良い方法があればいいのにと思っていますが、私はテキストを通しました
Shahbaz


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

次に、この変数の値をシェルコマンドに渡す必要があります。ただし、変数置換を使用してこれを行うと、コマンドが複数に分割されます。

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行に表示されます。

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

アラディスの答えが一番好きです。ただし、列の書式を維持するには、もう1つ追加します。

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ターゲットのリストだけを見たいと何度も思っていました。コメントは意味がありません。ユーザーが何をすべきかを知るのに3秒かかる場合、ユーザーが期待することは無関係ですが、このような情報の代わりに、数時間かかる場合があります。
Xennex81 2017年

1
何かをする理由として期待を使用することも循環論法です:人々はそれを期待するので、私たちはそれをしなければならず、私たちがそうするので、彼らはそれを期待します。
Xennex81 2017年

1

OPとは完全には関係ありませんが、これが将来誰かを助けることを願っています。(この質問はグーグル検索で最もよく出てくる質問なので)。

Makefileで、ファイルの内容をdocker buildコマンドに渡したいと思いました。

 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)

以下の例を参照してください。

nb:私の特定のケースでは、https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/( git repoのクローンを作成し、ビルドの第2ステージの最終イメージからsshキーをドロップするマルチステージDockerビルド)

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により、Makefile内のすべてのターゲットがすべてのコマンドに対して単一のシェルを使用するようになります。


残念ながら、それは機能しません: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

と思わ.ONESHELLメイク3.82の新機能です。
2014年

ところで、タブの代わりにスペースを使用すると、エラーが発生します*** missing separator. Stop.
14年

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の2行目をコマンドとして解釈しようとします。


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は、別の特別なアーカイブなどを必要とせずに、さまざまな単純なリソースをもたらします。ある意味では、シェルアーカイブです。手順は、ドロップのようなものを言うことができるで動作するようにフォルダ内のこのメイクファイル。ワークスペースを設定入力しmake workspace、その後、何とかを行うには、入力make blah、など

トリッキーになることができるのは、何を引用するかを理解することです。上記は仕事をし、Makefileでヒアドキュメントを指定するという考えに近いです。一般的な使用に適しているかどうかは、まったく別の問題です。


0

クロスプラットフォームで使用する場合の最も安全な答えは、1行に1つのエコーを使用することだと思います。

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

これが誰かを助けることを願っています。=)


私はこれが一番好きです。ただし、列の書式を維持するには、もう1つ追加します。`構文:= ::概要:Makefile \ | :: \ | ::使用法:\ | :: make ..........:このメッセージを生成します\ | ::概要を作成します。:このメッセージを生成します\ | ::クリーンにする....:不要な中間体とターゲットを削除します\ | ::すべて作成......:システム全体をゼロからコンパイルします\ endef
jlettvin

コメントはコードを許可しません。回答としてお送りします。私はこれが一番好きです。ただし、列の書式を維持するには、もう1つ追加します。`構文:= ::概要:Makefile`` | :: `` | ::使用法: `` | :: make ..........:このメッセージを生成します `` | ::概要を作成します。:このメッセージを生成します `` | :: make clean ....:不要な中間物とターゲットを削除します `` | ::すべて作成......:システム全体を一からコンパイルします `` endef`
jlettvin

@jlettvinあなたの答えに対する私の返事を見てください。プログラムの概要は、特にデフォルトタスクとしてではなく、Makefile内に埋め込まないでください。

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.