Chaîne multi-lignes avec espace supplémentaire (indentation préservée)

410

Je veux écrire des textes prédéfinis dans un fichier avec les éléments suivants:

text="this is line one\n
this is line two\n
this is line three"

echo -e $text > filename

J'attends quelque chose comme ça:

this is line one
this is line two
this is line three

Mais j'ai ceci:

this is line one
 this is line two
 this is line three

Je suis certain qu'il n'y a pas d'espace après chacun \n, mais comment l'espace supplémentaire sort-il?

cizixs
la source
2
Je ne suis pas sûr mais .. comment si vous venez de taper text="this is line one\nthis is line two\nthis is line three"la même ligne ..? (sans aucune entrée)
Yohanes Khosiawan
4
Supprimez le \nsur chaque ligne, vous avez déjà appuyé sur la nouvelle ligne pour passer à la nouvelle ligne
Mark Setchell
Vous avez déjà donné \n. Alors pourquoi mettre la ligne suivante dans une nouvelle ligne? Simplementtext="this is line one\nthis is line two\nthis is line three"
Jayesh Bhoi
1
La suppression du \nà la fin de chaque ligne entraîne l'exécution de la sortie sur une seule ligne.
Jonathan Hartley
18
Aha: Mettre des guillemets doubles autour "$text"de la ligne d'écho est crucial. Sans eux, aucune des nouvelles lignes (à la fois littérales et «\ n») ne fonctionne. Avec eux, ils le font tous.
Jonathan Hartley

Réponses:

663

Heredoc semble plus pratique à cet effet. Il est utilisé pour envoyer plusieurs commandes à un programme interpréteur de commandes comme ex ou cat

cat << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage

La chaîne après <<indique où s'arrêter.

Pour envoyer ces lignes vers un fichier, utilisez:

cat > $FILE <<- EOM
Line 1.
Line 2.
EOM

Vous pouvez également stocker ces lignes dans une variable:

read -r -d '' VAR << EOM
This is line 1.
This is line 2.
Line 3.
EOM

Cela stocke les lignes dans la variable nommée VAR.

Lors de l'impression, n'oubliez pas les guillemets autour de la variable, sinon vous ne verrez pas les caractères de nouvelle ligne.

echo "$VAR"

Encore mieux, vous pouvez utiliser l'indentation pour le faire ressortir davantage dans votre code. Cette fois, ajoutez simplement un -après <<pour empêcher les onglets d'apparaître.

read -r -d '' VAR <<- EOM
    This is line 1.
    This is line 2.
    Line 3.
EOM

Mais alors vous devez utiliser des tabulations, pas des espaces, pour l'indentation dans votre code.

nouveau-enfant
la source
38
pourquoi avez-vous << - au lieu de << sur la première ligne des 2e et 5e blocs de code? fait-il quelque chose de spécial?
lohfu
35
@ sup3rman '-' Ignore les principaux onglets. Voir stackoverflow.com/questions/2500436/…
kervin
8
comment puis-je faire lire la sortie avec le statut 0? Il semble qu'il ne puisse pas trouver la fin de la ligne (car c'est -d '') et se termine par 1, ce qui n'aide pas lorsque vous êtes dans le script.
Misha Slyusarev
7
@MishaSlyusarev utilisez -d '\ 0' et ajoutez un \ 0 à la fin de votre dernière ligne
Lars Schneider
11
L'utilisation de l' -doption dans read -r -d '' VARIABLE <<- EOMn'a pas fonctionné pour moi.
dron22
186

Si vous essayez de placer la chaîne dans une variable, un autre moyen simple est quelque chose comme ceci:

USAGE=$(cat <<-END
    This is line one.
    This is line two.
    This is line three.
END
)

Si vous indentez votre chaîne avec des tabulations (c'est-à-dire «\ t»), l'indentation sera supprimée. Si vous indentez avec des espaces, l'indentation sera conservée.

REMARQUE: il est significatif que la dernière parenthèse fermante se trouve sur une autre ligne. Le ENDtexte doit apparaître seul sur une ligne.

Andrew Miner
la source
1
Est-ce important? Fonctionne sur mon mac avec )sur la même ligne. Je pense que c'est parce que ce qui se passe entre $(et )va s'exécuter dans son propre univers, et la commande actuelle ne verra pas ')' de toute façon. Je me demande si ça marche aussi pour les autres.
deej
Il est possible que différents obus interprètent les choses différemment. Dans la documentation bash, il ne semble rien dire dans un sens ou dans l'autre, mais tous les exemples l'ont sur sa propre ligne.
Andrew Miner
Chose amusante, j'ai trouvé que vous répondiez en essayant de définir également la valeur de la variable USAGE. Votre réponse correspond donc exactement. :)
Bunyk
1
@deej D'après la spécification POSIX : Le document ici… continue jusqu'à ce qu'il y ait une ligne contenant uniquement le délimiteur et une <nouvelle>
Fornost
1
@Fornost Cela ne contredit pas ce que j'ai dit
deej
84

echoajoute des espaces entre les arguments qui lui sont passés. $textest soumis à une expansion variable et à une division des mots, donc votre echocommande équivaut à:

echo -e "this" "is" "line" "one\n" "this" "is" "line" "two\n"  ...

Vous pouvez voir qu'un espace sera ajouté avant "ceci". Vous pouvez soit supprimer les caractères de nouvelle ligne, soit citer $textpour conserver les nouvelles lignes:

text="this is line one
this is line two
this is line three"

echo "$text" > filename

Ou vous pourriez utiliser printf, qui est plus robuste et portable que echo:

printf "%s\n" "this is line one" "this is line two" "this is line three" > filename

Dans bash, qui prend en charge l'expansion de l'accolade, vous pouvez même faire:

printf "%s\n" "this is line "{one,two,three} > filename
Josh Jolly
la source
1
Merci d'avoir expliqué pourquoi le mod voit de l'espace supplémentaire lors de l'utilisation de la commande "echo". Cette réponse fonctionne également bien lors de l'utilisation dans un script bash (contrairement à une session shell interactive). Sentez-vous que c'est la vraie réponse et devrait être une réponse acceptée.
onelaview
1
Cette réponse devrait être acceptée. Toutes les autres réponses ont fourni des tonnes d' autres façons intéressantes de résoudre la douleur d'OP, mais techniquement, la question était "comment l'espace supplémentaire sort-il" et personne ne l'a abordé :-) !!! Merci, Josh d'avoir fait -1 mystère!
Dmitry Shevkoplyas
45

dans un script bash, les travaux suivants:

#!/bin/sh

text="this is line one\nthis is line two\nthis is line three"
echo -e $text > filename

alternativement:

text="this is line one
this is line two
this is line three"
echo "$text" > filename

nom de fichier cat donne:

this is line one
this is line two
this is line three
Chris Maes
la source
Il semble que l'astuce alternative fonctionne avec les scripts Shell, mais elle ne semble pas fonctionner pour bash.
Macindows
3
@Macindows, je semble avoir oublié l' -eoption pour echo. Veuillez réessayer.
Chris Maes
41

J'ai trouvé plus de solutions depuis que je voulais que chaque ligne soit correctement en retrait:

  1. Vous pouvez utiliser echo:

    echo    "this is line one"   \
        "\n""this is line two"   \
        "\n""this is line three" \
        > filename

    Cela ne fonctionne pas si vous mettez "\n"juste avant \la fin d'une ligne.

  2. Alternativement, vous pouvez utiliser printfpour une meilleure portabilité (j'ai eu beaucoup de problèmes avec echo):

    printf '%s\n' \
        "this is line one"   \
        "this is line two"   \
        "this is line three" \
        > filename
  3. Une autre solution pourrait être:

    text=''
    text="${text}this is line one\n"
    text="${text}this is line two\n"
    text="${text}this is line three\n"
    printf "%b" "$text" > filename

    ou

    text=''
    text+="this is line one\n"
    text+="this is line two\n"
    text+="this is line three\n"
    printf "%b" "$text" > filename
  4. Une autre solution est obtenue en mélangeant printfet sed.

    if something
    then
        printf '%s' '
        this is line one
        this is line two
        this is line three
        ' | sed '1d;$d;s/^    //g'
    fi

    Il n'est pas facile de refactoriser le code formaté comme ceci lorsque vous codez en dur le niveau d'indentation dans le code.

  5. Il est possible d'utiliser une fonction d'assistance et quelques astuces de substitution de variables:

    unset text
    _() { text="${text}${text+
    }${*}"; }
    # That's an empty line which demonstrates the reasoning behind 
    # the usage of "+" instead of ":+" in the variable substitution 
    # above.
    _ ""
    _ "this is line one"
    _ "this is line two"
    _ "this is line three"
    unset -f _
    printf '%s' "$text"
Mateusz Piotrowski
la source
3b est ma solution préférée lorsque je souhaite conserver l'indentation de code et l'indentation de chaîne indépendamment, au moins lorsque je ne peux pas utiliser 2 dans l'affectation de variable, et est plus lisible que 3a. Quand je m'en fiche, je garde simplement la chaîne citée ouverte sur plusieurs lignes.
Pysis
Très belle réponse surtout 3b
Sumit Trehan
"Cela ne fonctionne pas si vous mettez" \ n "juste avant \ à la fin d'une ligne." - est un excellent conseil pour utiliser l'écho.
Noam Manos
6

Ce qui suit est ma façon préférée d'assigner une chaîne multi-lignes à une variable (je pense que ça a l'air bien).

read -r -d '' my_variable << \
_______________________________________________________________________________

String1
String2
String3
...
StringN
_______________________________________________________________________________

Le nombre de soulignements est le même (ici 80) dans les deux cas.

Frank-Rene Schäfer
la source
0

Juste pour mentionner une simple concaténation d'une ligne car elle peut parfois être utile.

# for bash

v=" guga "$'\n'"   puga "

# Just for an example.
v2="bar "$'\n'"   foo "$'\n'"$v"

# Let's simplify the previous version of $v2.
n=$'\n'
v3="bar ${n}   foo ${n}$v"

echo "$v3" 

Vous obtiendrez quelque chose comme ça

bar 
   foo 
 guga 
   puga 

Tous les espaces blancs de début et de fin seront conservés pour

echo "$v3" > filename
it3xl
la source
0

Il y a plusieurs façons de procéder. Pour moi, canaliser la chaîne en retrait dans sed fonctionne bien.

printf_strip_indent() {
   printf "%s" "$1" | sed "s/^\s*//g" 
}

printf_strip_indent "this is line one
this is line two
this is line three" > "file.txt"

Cette réponse était basée sur la réponse de Mateusz Piotrowski mais raffinée un peu.

Beni Trainor
la source
-1

cela fonctionnera si vous le mettez comme ci-dessous:

AA='first line
\nsecond line 
\nthird line'
echo $AA
output:
first line
second line
third line
luk
la source