Ist es möglich, eine mehrzeilige Zeichenfolgenvariable in einem Makefile zu erstellen?


121

Ich möchte eine Makefile-Variable erstellen, die eine mehrzeilige Zeichenfolge ist (z. B. der Text einer E-Mail-Release-Ankündigung). etwas wie

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

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

Aber ich kann anscheinend keinen Weg finden, dies zu tun. Ist es möglich?

Antworten:


171

Ja, Sie können das Schlüsselwort define verwenden, um eine mehrzeilige Variable wie folgt zu deklarieren:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Der schwierige Teil besteht darin, Ihre mehrzeilige Variable wieder aus dem Makefile zu entfernen. Wenn Sie nur "echo $ (ANNOUNCE_BODY)" verwenden, sehen Sie das Ergebnis, das andere hier gepostet haben - die Shell versucht, die zweite und die nachfolgenden Zeilen der Variablen selbst als Befehle zu behandeln.

Sie können den Variablenwert jedoch unverändert als Umgebungsvariable in die Shell exportieren und ihn dann als Umgebungsvariable (NICHT als make-Variable) aus der Shell referenzieren. Beispielsweise:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Beachten Sie die Verwendung von $$ANNOUNCE_BODYund geben Sie eine Shell-Umgebungsvariablenreferenz an, anstatt $(ANNOUNCE_BODY)eine reguläre Make-Variablenreferenz. Stellen Sie außerdem sicher, dass Sie Anführungszeichen um Ihre Variablenreferenz verwenden, um sicherzustellen, dass die Zeilenumbrüche nicht von der Shell selbst interpretiert werden.

Natürlich kann dieser spezielle Trick plattform- und Shell-empfindlich sein. Ich habe es unter Ubuntu Linux mit GNU Bash 3.2.13 getestet. YMMV.


1
export ANNOUNCE_BODYSetzt nur die Variable innerhalb von Regeln - es erlaubt nicht, auf $$ ANNOUNCE_BODY zu verweisen, um andere Variablen zu definieren.
Anatoly Techtonik

@techtonik Wenn Sie den Wert von ANNOUNCE_BODYin anderen Variablendefinitionen verwenden möchten , verweisen Sie einfach wie bei jeder anderen make-Variablen darauf. Zum Beispiel OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY). Natürlich brauchen Sie den exportTrick noch, wenn Sie OTHEReinen Befehl ausführen möchten .
Eric Melski

24

Ein anderer Ansatz, um Ihre mehrzeilige Variable wieder aus dem Makefile zu entfernen (von Eric Melski als "der knifflige Teil" bezeichnet), besteht darin, die substFunktion zu verwenden, um die definein Ihrer mehrzeiligen Zeichenfolge eingeführten Zeilenumbrüche durch zu ersetzen \n. Verwenden Sie dann -e mit echo, um sie zu interpretieren. Möglicherweise müssen Sie .SHELL = bash setzen, um ein Echo zu erhalten, das dies tut.

Ein Vorteil dieses Ansatzes besteht darin, dass Sie auch andere Escapezeichen in Ihren Text einfügen und diese respektieren lassen.

Diese Art der Synthese aller bisher genannten Ansätze ...

Sie landen mit:

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

Beachten Sie, dass die einfachen Anführungszeichen im letzten Echo von entscheidender Bedeutung sind.


4
Beachten Sie, dass "echo -e" nicht portierbar ist. Sie sollten stattdessen wahrscheinlich printf (1) bevorzugen.
MadScientist

2
Tolle Antwort, ich musste jedoch das =After entfernen define ANNOUNCE_BODY, um es zum Laufen zu bringen.
Mschilli

13

Angenommen, Sie möchten den Inhalt Ihrer Variablen nur in der Standardausgabe drucken, gibt es eine andere Lösung:

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

1
Diese No-Op-Regel erzeugte eine unerwünschte Nachricht : make: 'do-echo' is up to date.. Mit einem "no op" -Kommand konnte ich es zum Schweigen bringen:@: $(info $(YOUR_MULTILINE_VAR))
Guillaume Papin

@GuillaumePapin Etwas spät, aber Sie können .PHONYIhrem Makefile mitteilen, dass für diese Regel nichts zu überprüfen ist. Makefiles waren ursprünglich für Compiler gedacht, wenn ich mich nicht irre, also makemache ich etwas Magie, die ich nicht verstehe, um vorauszusehen, dass die Regel nichts ändert, und als solche davon ausgeht, dass sie "erledigt" ist. Wenn Sie .PHONY do-echoIhre Datei hinzufügen make, wird dies angewiesen, dies zu ignorieren und den Code trotzdem auszuführen.
M3D

Sie können $(info ...)außerhalb einer Make-Regel platzieren. Es wird weiterhin eine Ausgabe generiert.
Daniel Stevens


3

Ja. Sie entkommen den Zeilenumbrüchen mit \:

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

aktualisieren

Ah, du willst die Zeilenumbrüche? Dann nein, ich glaube nicht, dass es in Vanille Make einen Weg gibt. Sie können jedoch immer ein Here-Dokument im Befehlsteil verwenden

[Dies funktioniert nicht, siehe Kommentar von MadScientist]

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

Dies ist wahr, aber es gibt mir keine Formatierung (Zeilenumbrüche). Es wird nur eine einzige Textzeile
Jonner

Mehrzeilige Here-Dokumente funktionieren nicht wie in GNU Make beschrieben.
Matt B.

3
Mehrzeilige Dokumente in Rezepten funktionieren in keiner Standardversion von make, die den POSIX-Standard unterstützt: Der make-Standard erfordert, dass jede einzelne Zeile des Rezepts in einer separaten Shell ausgeführt wird. Make analysiert den Befehl nicht, um festzustellen, ob es sich um ein Here-Dokument handelt oder nicht, und behandelt es anders. Wenn Sie eine Variante von make kennen, die dies unterstützt (ich habe noch nie von einer gehört), sollten Sie dies wahrscheinlich explizit angeben.
MadScientist

2

Nur ein Nachtrag zu Eric Melskis Antwort: Sie können die Ausgabe von Befehlen in den Text aufnehmen, müssen jedoch die Makefile-Syntax "$ (shell foo)" anstelle der Shell-Syntax "$ (foo)" verwenden. Beispielsweise:

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

It can be downloaded from $(DOWNLOAD_URL).  

endef

2

Dies gibt hier kein Dokument an, zeigt jedoch eine mehrzeilige Nachricht auf eine Weise an, die für Pipes geeignet ist.

=====

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

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

=====

Sie können auch Gnus aufrufbare Makros verwenden:

=====

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

=====

Hier ist die Ausgabe:

=====

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

=====


1

Warum verwenden Sie nicht das Zeichen \ n in Ihrer Zeichenfolge, um das Zeilenende zu definieren? Fügen Sie außerdem den zusätzlichen Backslash hinzu, um ihn über mehrere Zeilen hinzuzufügen.

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

Ich bevorzuge die Antwort von Erik Melski, aber dies könnte je nach Ihrer Bewerbung bereits den Trick für Sie tun.
Roalt

Ich habe eine Frage dazu. Dies funktioniert grundsätzlich gut, außer dass ich am Anfang jeder Zeile (außer der ersten) ein zusätzliches "Leerzeichen" sehe. Passiert dir das? Ich kann den gesamten Text in eine Zeile setzen, die durch \ n getrennt ist, um so effektiv die Ausgabe zu erstellen, die mir gefällt. Das Problem ist, dass es im Makefile selbst sehr hässlich aussieht!
Shahbaz

Ich habe eine Problemumgehung gefunden. Ich habe den Text durchgestellt, $(subst \n ,\n,$(TEXT))obwohl ich mir wünschte, es gäbe einen besseren Weg!
Shahbaz


1

Sie sollten "define / endef" verwenden. Konstrukt erstellen:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Dann sollten Sie den Wert dieser Variablen an den Shell-Befehl übergeben. Wenn Sie dies jedoch mit "Variable ersetzen" ausführen, wird der Befehl in mehrere Teile aufgeteilt:

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

Qouting hilft auch nicht.

Der beste Weg, einen Wert zu übergeben, besteht darin, ihn über die Umgebungsvariable zu übergeben:

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

Beachten:

  1. Die Variable wird für dieses bestimmte Ziel exportiert, sodass Sie diese Umgebung wiederverwenden können, da sie nicht stark verschmutzt wird.
  2. Umgebungsvariable verwenden (doppelte Qouten und geschweifte Klammern um den Variablennamen);
  3. Verwendung von Anführungszeichen um Variable. Ohne sie gehen neue Zeilen verloren und der gesamte Text wird in einer Zeile angezeigt.

1

Im Geiste von .ONESHELL ist es möglich, in .ONESHELL-herausfordernden Umgebungen ziemlich nahe zu kommen:

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

Ein Anwendungsbeispiel wäre etwa so:

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

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

Das zeigt die Ausgabe (unter der Annahme von PID 27801):

>
Hello
World\n/27801/

Dieser Ansatz bietet einige zusätzliche Funktionen:

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

Diese Shell-Optionen werden:

  • Drucken Sie jeden Befehl so aus, wie er ausgeführt wird
  • Beenden Sie mit dem ersten fehlgeschlagenen Befehl
  • Behandeln Sie die Verwendung undefinierter Shell-Variablen als Fehler

Andere interessante Möglichkeiten werden sich wahrscheinlich bieten.


1

Ich mag Alhadis Antwort am liebsten. Fügen Sie jedoch noch eine weitere Sache hinzu, um die Spaltenformatierung beizubehalten.

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

Ausgänge:

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

Die Zusammenfassung eines Programms sollte leicht und offensichtlich zu finden sein. Ich würde empfehlen, diese Informationsebene in eine Readme- und / oder Manpage einzufügen. Wenn ein Benutzer ausgeführt wird make, erwartet er im Allgemeinen, dass ein Erstellungsprozess gestartet wird.

1
Ich wollte schon oft nur eine Liste von Make-Zielen sehen. Ihr Kommentar macht keinen Sinn. Was Benutzer erwarten, ist irrelevant, wenn sie 3 Sekunden brauchen, um zu wissen, was zu tun ist, während es anstelle solcher Informationen manchmal Stunden dauern kann.
Xennex81

1
Erwartungen als Grund zu verwenden, um etwas zu tun, ist auch ein zirkuläres Argument: Weil die Leute es erwarten, müssen wir es tun, und weil wir es tun, erwarten sie es.
Xennex81

1

Nicht vollständig mit dem OP verbunden, aber hoffentlich hilft dies jemandem in Zukunft. (da diese Frage bei Google-Suchanfragen am häufigsten auftaucht).

In meinem Makefile wollte ich den Inhalt einer Datei an einen Docker-Build-Befehl übergeben. Nach viel Bestürzung entschied ich mich für:

 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)

siehe Beispiel unten.

nb: In meinem speziellen Fall wollte ich während des Image-Builds einen SSH-Schlüssel übergeben, indem ich das Beispiel von https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (using) verwendete ein mehrstufiger Docker-Build, um ein Git-Repo zu klonen und dann den SSH-Schlüssel aus dem endgültigen Image in der 2. Phase des Builds zu entfernen)

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

Mit GNU Make 3.82 und höher ist die .ONESHELLOption Ihr Freund, wenn es um mehrzeilige Shell-Snippets geht. Wenn ich Hinweise aus anderen Antworten zusammenstelle, bekomme ich:

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

Stellen Sie beim Kopieren und Einfügen des obigen Beispiels in Ihren Editor sicher, dass alle <tab>Zeichen erhalten bleiben, da sonst das versionZiel beschädigt wird!

Beachten Sie, .ONESHELLdass alle Ziele im Makefile eine einzige Shell für alle ihre Befehle verwenden.


Leider funktioniert das nicht: 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 machen 3,81)
blau

@blueyed, ich habe es gerade mit GNU Make 3.82 und GNU Bash 4.2.45 (1) getestet. Release: Es funktioniert wie erwartet. Überprüfen Sie auch das Vorhandensein des führenden TAB-Zeichens anstelle von Leerzeichen vor der @printf ...Anweisung - es sieht so aus, als würden TABs immer als 4 Leerzeichen gerendert ...
sphakka

Es scheint, dass .ONESHELLes neu in Marke 3.82 ist.
Blueyed

Übrigens: Der Fehler bei der Verwendung von Leerzeichen anstelle einer Registerkarte wäre *** missing separator. Stop..
Blueyed

0

Keine wirklich hilfreiche Antwort, aber nur um anzuzeigen, dass 'define' nicht wie von Axe beantwortet funktioniert (passte nicht in einen Kommentar):

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)

Es gibt einen Fehler, dass der Befehl 'It' nicht gefunden werden kann, daher wird versucht, die zweite Zeile von ANNOUNCE BODY als Befehl zu interpretieren.


0

Es hat bei mir funktioniert:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

0

GNU Makefile kann Folgendes tun. Es ist hässlich und ich werde nicht sagen, dass Sie es tun sollten, aber ich tue es in bestimmten Situationen.

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 Erstellt eine Profildatei, falls keine vorhanden ist.

Diese Lösung wurde verwendet, wenn die Anwendung GNU Makefile nur in einer POSIX-Shell-Umgebung verwendet. Das Projekt ist kein Open Source-Projekt, bei dem die Plattformkompatibilität ein Problem darstellt.

Ziel war es, ein Makefile zu erstellen, das sowohl die Einrichtung als auch die Verwendung eines bestimmten Arbeitsbereichs erleichtert. Das Makefile bringt verschiedene einfache Ressourcen mit sich, ohne dass Dinge wie ein anderes spezielles Archiv usw. erforderlich sind. Es ist gewissermaßen ein Shell-Archiv. Eine Prozedur kann dann beispielsweise sagen, dass Sie dieses Makefile in den Ordner legen, in dem Sie arbeiten möchten. Richten Sie Ihren Arbeitsbereich ein make workspace, geben Sie dann blah, enter make blahusw. ein.

Was schwierig werden kann, ist herauszufinden, was zu zitieren ist. Das Obige erledigt den Job und kommt der Idee nahe, hier ein Dokument im Makefile anzugeben. Ob es eine gute Idee für den allgemeinen Gebrauch ist, ist eine ganz andere Frage.


0

Ich glaube, die sicherste Antwort für die plattformübergreifende Verwendung wäre die Verwendung eines Echos pro Zeile:

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

Dadurch wird vermieden, dass Annahmen über die Version des Echos verfügbar gemacht werden.


0

Verwenden Sie die Zeichenfolgenersetzung :

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

Dann in dein Rezept setzen

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

Dies funktioniert, weil Make alle Vorkommen von (beachten Sie das Leerzeichen) ersetzt und durch ein Zeilenumbruchzeichen ( $$'\n') ersetzt. Sie können sich die entsprechenden Shell-Skript-Aufrufe wie folgt vorstellen:

Vor:

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

Nach dem:

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

Ich bin nicht sicher, ob $'\n'es auf Nicht-POSIX-Systemen verfügbar ist, aber wenn Sie Zugriff auf ein einzelnes Zeilenumbruchzeichen erhalten können (selbst wenn Sie eine Zeichenfolge aus einer externen Datei lesen), ist das zugrunde liegende Prinzip dasselbe.

Wenn Sie viele Nachrichten wie diese haben, können Sie das Rauschen mithilfe eines Makros reduzieren :

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

Wo Sie es so aufrufen würden:

@$(call print,$(ANNOUNCE_BODY))

Hoffe das hilft jemandem. =)


Ich mag dieses am besten. Fügen Sie jedoch noch eine weitere Sache hinzu, um die Spaltenformatierung beizubehalten. `SYNOPSIS: = :: Synopsis: Makefile \ | :: \ | :: Verwendung: \ | :: make ..........: generiert diese Nachricht \ | :: Synopse machen. : generiert diese Nachricht \ | :: sauber machen ....: unerwünschte Zwischenprodukte und Ziele beseitigen \ | :: make all ......: Kompiliere das gesamte System von Grund auf \ endef
jlettvin

Kommentare erlauben keinen Code. Wird als Antwort senden. Ich mag dieses am besten. Fügen Sie jedoch noch eine weitere Sache hinzu, um die Spaltenformatierung beizubehalten. `SYNOPSIS: = :: Synopsis: Makefile`` | :: `` | :: Verwendung: `` | :: make ..........: generiert diese Nachricht` `| :: Synopse machen. : generiert diese Nachricht` `| :: sauber machen ....: unerwünschte Zwischenprodukte und Ziele beseitigen` `| :: make all ......: Kompiliere das gesamte System von Grund auf `` endef`
jlettvin

@jlettvin Siehe meine Antwort auf Ihre Antwort. Die Inhaltsangabe eines Programms sollte definitiv nicht in ein Makefile eingebettet werden, insbesondere nicht als Standardaufgabe.

0

Alternativ können Sie den Befehl printf verwenden. Dies ist unter OSX oder anderen Plattformen mit weniger Funktionen hilfreich.

So geben Sie einfach eine mehrzeilige Nachricht aus:

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

Wenn Sie versuchen, die Zeichenfolge als Argument an ein anderes Programm zu übergeben, können Sie dies folgendermaßen tun:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.