Wie kann ich eine neue Zeile in einer Zeichenfolge in sh haben?

337

Diese

STR="Hello\nWorld"
echo $STR

erzeugt als Ausgabe

Hello\nWorld

anstatt

Hello
World

Was soll ich tun, um eine neue Zeile in einer Zeichenfolge zu haben?

Hinweis: Bei dieser Frage geht es nicht um Echo . Ich bin mir dessen bewusst echo -e, suche aber nach einer Lösung, mit der eine Zeichenfolge (die eine neue Zeile enthält) als Argument an andere Befehle übergeben werden kann, die keine ähnliche Option zum Interpretieren \nvon Zeilen als Zeilenumbrüche haben.

Juan A. Navarro
quelle

Antworten:

353

Die Lösung besteht zum $'string'Beispiel darin, Folgendes zu verwenden :

$ STR=$'Hello\nWorld'
$ echo "$STR" # quotes are required here!
Hello
World

Hier ist ein Auszug aus der Bash-Handbuchseite:

   Words of the form $'string' are treated specially.  The word expands to
   string, with backslash-escaped characters replaced as specified by  the
   ANSI  C  standard.  Backslash escape sequences, if present, are decoded
   as follows:
          \a     alert (bell)
          \b     backspace
          \e
          \E     an escape character
          \f     form feed
          \n     new line
          \r     carriage return
          \t     horizontal tab
          \v     vertical tab
          \\     backslash
          \'     single quote
          \"     double quote
          \nnn   the eight-bit character whose value is  the  octal  value
                 nnn (one to three digits)
          \xHH   the  eight-bit  character  whose value is the hexadecimal
                 value HH (one or two hex digits)
          \cx    a control-x character

   The expanded result is single-quoted, as if the  dollar  sign  had  not
   been present.

   A double-quoted string preceded by a dollar sign ($"string") will cause
   the string to be translated according to the current  locale.   If  the
   current  locale  is  C  or  POSIX,  the dollar sign is ignored.  If the
   string is translated and replaced, the replacement is double-quoted.
Amphetamachine
quelle
74
Das ist eine gültige Bash, aber nicht POSIX sh.
jmanning2k
7
+ 1 - nur eine Anmerkung: export varDynamic = "$ varAnother1 $ varAnother2" $ '\ n'
Yordan Georgiev
3
Dies funktioniert zwar mit Literal-Strings, wie vom OP gefordert, schlägt jedoch fehl, sobald Variablen ersetzt werden sollen: STR=$"Hello\nWorld"einfach drucken Hello\nWorld.
SSC
3
@ssc einfache Anführungszeichen, nicht doppelt
miken32
7
@ssc In Ihrem Beispiel gibt es keine Variablen. Diese Methode erfordert auch einfache Anführungszeichen. Um interpolierte Variablen einzuschließen, müssten Sie schließlich Zeichenfolgen in doppelten und speziellen einfachen Anführungszeichen miteinander verketten. zB H="Hello"; W="World"; STR="${H}"$'\n'"${W}"; echo "${STR}"wird "Hallo" und "Welt" in separaten Zeilen wiedergegeben
JDS
166

Echo ist so neunzig und voller Gefahren, dass seine Verwendung zu Core-Dumps von nicht weniger als 4 GB führen sollte. Im Ernst, die Probleme von echo waren der Grund, warum der Unix-Standardisierungsprozess schließlich das printfDienstprogramm erfand und alle Probleme beseitigte.

So erhalten Sie eine neue Zeile in einer Zeichenfolge:

FOO="hello
world"
BAR=$(printf "hello\nworld\n") # Alternative; note: final newline is deleted
printf '<%s>\n' "$FOO"
printf '<%s>\n' "$BAR"

Dort! Kein SYSV vs BSD Echo-Wahnsinn, alles wird ordentlich gedruckt und unterstützt vollständig C-Escape-Sequenzen. Jeder bitte printfjetzt benutzen und niemals zurückblicken.

Jens
quelle
3
Vielen Dank für diese Antwort, ich denke, sie bietet wirklich gute Ratschläge. Beachten Sie jedoch, dass meine ursprüngliche Frage nicht wirklich war, wie man Echo zum Drucken von Zeilenumbrüchen erhält, sondern wie man einen Zeilenumbruch in eine Shell-Variable bringt. Sie zeigen , wie auch , dass die Verwendung von printf zu tun, aber ich denke , dass die Lösung von amphetamachine mit $'string'noch sauberer ist.
Juan A. Navarro
11
Nur für eine mangelhafte Definition von "sauber". Die $'foo'Syntax ist ab 2013 keine gültige POSIX-Syntax. Viele Shells werden sich beschweren.
Jens
5
BAR=$(printf "hello\nworld\n")druckt das Trailing nicht \ n für mich
Jonny
3
@ Jonny Es sollte nicht; Die Shell-Spezifikation für die Befehlssubstitution besagt, dass eine oder mehrere Zeilenumbrüche am Ende der Ausgabe gelöscht werden. Ich erkenne, dass dies für mein Beispiel unerwartet ist, und habe es so bearbeitet, dass es eine neue Zeile in den Druck einfügt.
Jens
5
@ismael Nein, $()wird von POSIX als Entfernen von Sequenzen eines oder mehrerer <newline> -Zeichen am Ende der Ersetzung angegeben .
Jens
61

Was ich basierend auf den anderen Antworten getan habe, war

NEWLINE=$'\n'
my_var="__between eggs and bacon__"
echo "spam${NEWLINE}eggs${my_var}bacon${NEWLINE}knight"

# which outputs:
spam
eggs__between eggs and bacon__bacon
knight
zvezda
quelle
29

Das Problem liegt nicht bei der Shell. Das Problem liegt tatsächlich beim echoBefehl selbst und beim Fehlen doppelter Anführungszeichen um die Variableninterpolation. Sie können versuchen, dies zu verwenden echo -e, dies wird jedoch nicht auf allen Plattformen unterstützt. Einer der Gründe printfwird jetzt für die Portabilität empfohlen.

Sie können auch versuchen, die neue Zeile direkt in Ihr Shell-Skript einzufügen (wenn Sie ein Skript schreiben), damit es so aussieht ...

#!/bin/sh
echo "Hello
World"
#EOF

oder äquivalent

#!/bin/sh
string="Hello
World"
echo "$string"  # note double quotes!
Tempo
quelle
23

Ich finde die -eFlagge elegant und geradlinig

bash$ STR="Hello\nWorld"

bash$ echo -e $STR
Hello
World

Wenn die Zeichenfolge die Ausgabe eines anderen Befehls ist, verwende ich nur Anführungszeichen

indexes_diff=$(git diff index.yaml)
echo "$indexes_diff"
Simon Ndunda
quelle
2
Dies wurde bereits in anderen Antworten erwähnt. Obwohl es bereits vorhanden war, habe ich die Frage gerade bearbeitet, um die Tatsache hervorzuheben, dass es sich bei dieser Frage nicht um eine handelt echo.
Juan A. Navarro
Dies ist keine Antwort auf die Frage
Rohit Gupta
Dies beantwortet völlig die Frage, die ich gesucht habe! Vielen Dank!
Kieveli
1
Hat hier gut für mich funktioniert. Ich habe eine Datei basierend auf diesem Echo erstellt und sie hat neue Zeilen in der Ausgabe korrekt generiert.
Mateus Leon
echo -eist bekannt für seine Portabilitätsprobleme. Dies ist derzeit weniger eine Situation im Wilden Westen als vor POSIX, aber es gibt immer noch keinen Grund, dies zu bevorzugen echo -e. Siehe auch stackoverflow.com/questions/11530203/…
Tripleee
21
  1. Die einzige einfache Alternative besteht darin, tatsächlich eine neue Zeile in die Variable einzugeben:

    $ STR='new
    line'
    $ printf '%s' "$STR"
    new
    line

    Ja, das bedeutet, Enterwo nötig in den Code zu schreiben .

  2. Es gibt mehrere Entsprechungen zu einem new lineCharakter.

    \n           ### A common way to represent a new line character.
    \012         ### Octal value of a new line character.
    \x0A         ### Hexadecimal value of a new line character.

    Aber all diese erfordern "eine Interpretation" durch ein Werkzeug ( POSIX printf ):

    echo -e "new\nline"           ### on POSIX echo, `-e` is not required.
    printf 'new\nline'            ### Understood by POSIX printf.
    printf 'new\012line'          ### Valid in POSIX printf.
    printf 'new\x0Aline'       
    printf '%b' 'new\0012line'    ### Valid in POSIX printf.

    Daher muss das Tool eine Zeichenfolge mit einer neuen Zeile erstellen:

    $ STR="$(printf 'new\nline')"
    $ printf '%s' "$STR"
    new
    line
  3. In einigen Shells ist die Sequenz $'eine spezielle Shell-Erweiterung. Bekannt für die Arbeit in ksh93, bash und zsh:

    $ STR=$'new\nline'
  4. Natürlich sind auch komplexere Lösungen möglich:

    $ echo '6e65770a6c696e650a' | xxd -p -r
    new
    line

    Oder

    $ echo "new line" | sed 's/ \+/\n/g'
    new
    line

quelle
6

Ein $ direkt vor einfachen Anführungszeichen '... \ n ...' wie folgt, doppelte Anführungszeichen funktionieren jedoch nicht.

$ echo $'Hello\nWorld'
Hello
World
$ echo $"Hello\nWorld"
Hello\nWorld
caot
quelle
4

Ich bin kein Bash-Experte, aber dieser hat für mich funktioniert:

STR1="Hello"
STR2="World"
NEWSTR=$(cat << EOF
$STR1

$STR2
EOF
)
echo "$NEWSTR"

Ich fand es einfacher, die Texte zu formatieren.

Jason
quelle
2
Guter alter Heredoc . Und es ist wahrscheinlich auch POSIX-konform!
Pyb
Die Zitate $NEWSTRin echo "$NEWSTR"sind hier wichtig
Shadi
0

Auf meinem System (Ubuntu 17.10) funktioniert Ihr Beispiel wie gewünscht, sowohl wenn es über die Befehlszeile (in sh) eingegeben als auch als shSkript ausgeführt wird:

[bash sh
$ STR="Hello\nWorld"
$ echo $STR
Hello
World
$ exit
[bash echo "STR=\"Hello\nWorld\"
> echo \$STR" > test-str.sh
[bash cat test-str.sh 
STR="Hello\nWorld"
echo $STR
[bash sh test-str.sh 
Hello
World

Ich denke, das beantwortet Ihre Frage: Es funktioniert einfach. (Ich habe nicht versucht, Details herauszufinden, wie zu welchem ​​Zeitpunkt genau die Ersetzung des Zeilenumbruchs durch \nerfolgt sh).

Ich bemerkte jedoch, dass sich dasselbe Skript bei der Ausführung mitbash anders verhalten und Hello\nWorldstattdessen ausdrucken würde :

[bash bash test-str.sh
Hello\nWorld

Ich habe es geschafft, die gewünschte Ausgabe bashwie folgt zu erhalten:

[bash STR="Hello
> World"
[bash echo "$STR"

Beachten Sie die doppelten Anführungszeichen $STR. Dies verhält sich identisch, wenn es gespeichert und als bashSkript ausgeführt wird.

Das Folgende gibt auch die gewünschte Ausgabe:

[bash echo "Hello
> World"
Alexey
quelle
0

Diejenigen, die nur den Zeilenumbruch benötigen und den mehrzeiligen Code verachten, der die Einrückung unterbricht, könnten Folgendes tun:

IFS="$(printf '\nx')"
IFS="${IFS%x}"

Bash (und wahrscheinlich auch andere Shells) verschlingen alle nachfolgenden Zeilenumbrüche nach dem Ersetzen des Befehls. Sie müssen die printfZeichenfolge daher mit einem Nicht-Zeilenumbruchzeichen beenden und anschließend löschen. Dies kann auch leicht zu einem Oneliner werden.

IFS="$(printf '\nx')" IFS="${IFS%x}"

Ich weiß, dass dies zwei Aktionen anstelle von einer sind, aber meine OCD für Einrückung und Portabilität ist jetzt in Ruhe :) Ich habe diese ursprünglich entwickelt, um nur durch Zeilenumbrüche getrennte Ausgaben aufteilen zu können, und habe am Ende eine Modifikation verwendet, die \rals Abschlusszeichen verwendet wird. Dadurch funktioniert die Aufteilung der Zeilenumbrüche auch für die Dos-Ausgabe, die mit endet \r\n.

IFS="$(printf '\n\r')"
tlwhitec
quelle
0

Ich war mit keiner der Optionen hier wirklich zufrieden. Das hat bei mir funktioniert.

str=$(printf "%s" "first line")
str=$(printf "$str\n%s" "another line")
str=$(printf "$str\n%s" "and another line")
Adam K Dean
quelle
Das ist eine sehr mühsame Art zu schreiben str=$(printf '%s\n' "first line" "another line" "and another line")(die Shell schneidet bequem (oder nicht) alle endgültigen Zeilenumbrüche aus der Befehlsersetzung ab). Einige Antworten hier werden bereits printfin verschiedenen Formen beworben, daher bin ich mir nicht sicher, ob dies als separate Antwort einen Mehrwert bietet.
Tripleee
Es ist für kurze Zeichenfolgen gedacht, aber wenn Sie die Dinge ordentlich halten möchten und jede Zeile tatsächlich 60 Zeichen lang ist, ist dies eine ordentliche Methode. Angesichts der Tatsache, dass es die Lösung ist, mit der ich mich letztendlich entschieden habe, fügt sie etwas hinzu, wenn nicht für Sie, dann für mich selbst in der Zukunft, wenn ich zweifellos zurückkehre.
Adam K Dean
1
Selbst für lange Zeichenfolgen, die ich bevorzuge printf '%s\n' "first line" \ (Zeilenumbruch, Einzug) 'second line'usw. Siehe auch printf -v strfür die Zuweisung zu einer Variablen, ohne eine Unterschale zu erzeugen.
Tripleee