Comment puis-je avoir une nouvelle ligne dans une chaîne en sh?


340

Ce

STR="Hello\nWorld"
echo $STR

produit en sortie

Hello\nWorld

au lieu de

Hello
World

Que dois-je faire pour avoir une nouvelle ligne dans une chaîne?

Remarque: Cette question ne concerne pas l' écho . Je suis au courant echo -e, mais je suis à la recherche d'une solution qui permette de passer une chaîne (qui inclut un retour à la ligne) comme argument à d' autres commandes qui n'ont pas une option similaire pour interpréter \ncomme des retours à la ligne.

Réponses:


354

La solution consiste à utiliser $'string', par exemple:

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

Voici un extrait de la page de manuel de Bash:

   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.

74
C'est bash valide, mais pas POSIX sh.
jmanning2k

7
+ 1-juste une note: export varDynamic = "$ varAnother1 $ varAnother2" $ '\ n'
Yordan Georgiev

3
Cela fonctionne en effet avec des chaînes littérales comme l'OP l'a demandé, mais échoue dès que les variables doivent être remplacées: STR=$"Hello\nWorld"imprime simplement Hello\nWorld.
ssc

3
@ssc guillemets simples, pas doubles
miken32

7
@ssc il n'y a pas de variables dans votre exemple. De plus, cette méthode nécessite des guillemets simples. Enfin, pour inclure des variables interpolées, vous devez concaténer ensemble des chaînes entre guillemets doubles et entre guillemets spéciaux. par exemple, H="Hello"; W="World"; STR="${H}"$'\n'"${W}"; echo "${STR}"fera écho à "Hello" et "World" sur des lignes distinctes
JDS

166

Echo a tellement quatre-vingt-dix et est si lourd de dangers que son utilisation devrait entraîner des vidages de mémoire d'au moins 4 Go. Sérieusement, les problèmes d'écho étaient la raison pour laquelle le processus de normalisation Unix a finalement inventé l' printfutilitaire, supprimant tous les problèmes.

Donc, pour obtenir une nouvelle ligne dans une chaîne:

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

Là! Pas de folie d'écho SYSV vs BSD, tout est parfaitement imprimé et entièrement portable pour les séquences d'échappement C. Tout le monde, veuillez utiliser printfmaintenant et ne jamais regarder en arrière.


3
Merci pour cette réponse, je pense qu'elle fournit de très bons conseils. Notez, cependant, que ma question initiale n'était pas vraiment de savoir comment obtenir l'écho pour imprimer des sauts de ligne, mais comment obtenir un saut de ligne dans une variable shell. Vous montrez comment faire cela en utilisant également printf, mais je pense que la solution d' amphétamachine en utilisant $'string'est encore plus propre.
Juan A. Navarro

11
Seulement pour une définition déficiente de «propre». La $'foo'syntaxe n'est pas une syntaxe POSIX valide à partir de 2013. De nombreux shells se plaindront.
Jens

5
BAR=$(printf "hello\nworld\n")n'imprime pas le trailing \ n pour moi
Jonny

3
@Jonny Cela ne devrait pas; la spécification du shell pour la substitution de commandes indique qu'un ou plusieurs sauts de ligne à la fin de la sortie sont supprimés. Je me rends compte que c'est inattendu pour mon exemple et l'ai modifié pour inclure une nouvelle ligne dans le printf.
Jens

5
@ismael Non, $()est spécifié par POSIX comme supprimant les séquences d'un ou plusieurs caractères <newline> à la fin de la substitution .
Jens

61

Ce que j'ai fait sur la base des autres réponses était

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

30

Le problème n'est pas avec le shell. Le problème est en fait avec la echocommande elle-même, et le manque de guillemets autour de l'interpolation variable. Vous pouvez essayer d'utiliser echo -emais cela n'est pas pris en charge sur toutes les plates-formes, et l'une des raisons printfest maintenant recommandée pour la portabilité.

Vous pouvez également essayer d'insérer la nouvelle ligne directement dans votre script shell (si un script est ce que vous écrivez) pour qu'il ressemble à ...

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

ou équivalent

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

24

Je trouve le -edrapeau élégant et simple

bash$ STR="Hello\nWorld"

bash$ echo -e $STR
Hello
World

Si la chaîne est la sortie d'une autre commande, j'utilise simplement des guillemets

indexes_diff=$(git diff index.yaml)
echo "$indexes_diff"

2
Cela a déjà été mentionné dans d'autres réponses. De plus, bien qu'il soit déjà là, je viens de modifier la question pour souligner le fait que cette question ne concerne pasecho .
Juan A. Navarro

Ce n'est pas une réponse à la question
Rohit Gupta

Cela répond totalement à la question que je cherchais! Merci!
Kieveli

1
A bien fonctionné pour moi ici. J'ai utilisé pour créer un fichier basé sur cet écho et il a généré correctement de nouvelles lignes sur la sortie.
Mateus Leon

echo -eest connu pour ses problèmes de portabilité. Il s'agit moins d'une situation dans le Far West que pré-POSIX, mais il n'y a toujours aucune raison de préférer echo -e. Voir aussi stackoverflow.com/questions/11530203/…
tripleee

21
  1. La seule alternative simple est de taper réellement une nouvelle ligne dans la variable:

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

    Oui, cela signifie écrire Enterlà où c'est nécessaire dans le code.

  2. Il existe plusieurs équivalents à un new linepersonnage.

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

    Mais tous ceux-ci nécessitent "une interprétation" par un outil ( 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.
    

    Et par conséquent, l'outil est nécessaire pour créer une chaîne avec une nouvelle ligne:

    $ STR="$(printf 'new\nline')"
    $ printf '%s' "$STR"
    new
    line
    
  3. Dans certains shells, la séquence $'est une extension de shell spéciale. Connu pour fonctionner dans ksh93, bash et zsh:

    $ STR=$'new\nline'
  4. Bien entendu, des solutions plus complexes sont également possibles:

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

    Ou

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

6

Un $ juste avant les guillemets simples '... \ n ...' comme suit, mais les guillemets doubles ne fonctionnent pas.

$ echo $'Hello\nWorld'
Hello
World
$ echo $"Hello\nWorld"
Hello\nWorld

4

Je ne suis pas un expert bash, mais celui-ci a fonctionné pour moi:

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

$STR2
EOF
)
echo "$NEWSTR"

J'ai trouvé cela plus facile à mettre en forme les textes.


2
Bon vieux hérédoc . Et il est probablement aussi compatible POSIX!
pyb

Les guillemets autour $NEWSTRde echo "$NEWSTR"sont importants ici
Shadi

0

Sur mon système (Ubuntu 17.10), votre exemple fonctionne comme vous le souhaitez, à la fois lorsqu'il est saisi à partir de la ligne de commande (dans sh) et lorsqu'il est exécuté en tant que shscript:

[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

Je suppose que cela répond à votre question: cela fonctionne. (Je n'ai pas essayé de comprendre des détails comme à quel moment exactement la substitution du caractère de nouvelle ligne \nse produit sh).

Cependant, j'ai remarqué que ce même script se comporterait différemment lorsqu'il serait exécuté avecbash et s'imprimerait à la Hello\nWorldplace:

[bash bash test-str.sh
Hello\nWorld

J'ai réussi à obtenir la sortie souhaitée avec bashcomme suit:

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

Notez les guillemets autour $STR. Cela se comporte de manière identique s'il est enregistré et exécuté en tant que bashscript.

Ce qui suit donne également la sortie souhaitée:

[bash echo "Hello
> World"

0

Ceux difficiles qui ont juste besoin de la nouvelle ligne et méprisent le code multiligne qui rompt l'indentation, pourraient faire:

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

Bash (et probablement d'autres shells) engloutit tous les sauts de ligne après la substitution de commande, vous devez donc terminer la printfchaîne avec un caractère non nouveau et le supprimer ensuite. Cela peut également facilement devenir un oneliner.

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

Je sais que c'est deux actions au lieu d'un, mais mon retrait et la portabilité TOC est en paix maintenant :) J'ai développé à l' origine que ce soit en mesure de diviser la sortie séparée newline seule et je fini par utiliser une modification que les utilisations \rcomme le caractère de fin. Cela fait que le fractionnement de la nouvelle ligne fonctionne même pour la sortie DOS se terminant par \r\n.

IFS="$(printf '\n\r')"

0

Je n'étais pas vraiment satisfait de l'une des options ici. C'est ce qui a fonctionné pour moi.

str=$(printf "%s" "first line")
str=$(printf "$str\n%s" "another line")
str=$(printf "$str\n%s" "and another line")

C'est une façon très fastidieuse d'écrire str=$(printf '%s\n' "first line" "another line" "and another line")(le shell coupera commodément (ou non) toutes les nouvelles lignes finales de la substitution de commande). Plusieurs réponses ici promeuvent déjà printfsous diverses formes, donc je ne suis pas sûr que cela ajoute une valeur en tant que réponse distincte.
tripleee

C'est pour les chaînes courtes, mais si vous voulez garder les choses en ordre et que chaque ligne est en fait de 60 caractères, alors c'est une bonne façon de le faire. Étant donné que c'est la solution que j'ai finalement choisie, cela ajoute quelque chose, si ce n'est pour vous, puis pour moi-même à l'avenir lorsque je reviendrai sans aucun doute.
Adam K Dean

1
Même pour les chaînes longues, je préfère printf '%s\n' "first line" \ (saut de ligne, retrait), 'second line'etc. Voir aussi printf -v strpour l'affectation à une variable sans engendrer un sous-shell.
tripleee
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.