Ciąg wieloliniowy z dodatkową spacją (zachowane wcięcie)


409

Chcę zapisać do pliku niektóre wstępnie zdefiniowane teksty:

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

echo -e $text > filename

Spodziewam się czegoś takiego:

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

Ale mam to:

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

Jestem pewien, że po każdym nie ma miejsca \n, ale jak powstaje dodatkowa przestrzeń?


2
Nie jestem pewien, ale ... co jeśli wpiszesz właśnie text="this is line one\nthis is line two\nthis is line three"ten sam wiersz ..? (bez żadnego wpisu)
Yohanes Khosiawan

4
Usuń \nna każdej linii, już nacisnąłeś nową linię, aby przejść do nowej linii
Mark Setchell

Już dałeś \n. Więc dlaczego wstawiłeś następny wiersz w nowym wierszu? Po prostutext="this is line one\nthis is line two\nthis is line three"
Jayesh Bhoi

1
Usunięcie znaku \nna końcu każdego wiersza powoduje, że wszystkie dane wyjściowe działają razem w jednym wierszu.
Jonathan Hartley

18
Aha: Umieszczenie podwójnych cudzysłowów wokół "$text"linii echa ma kluczowe znaczenie. Bez nich żadna z nowych linii (zarówno dosłowna, jak i „\ n”) nie działa. Z nimi wszyscy tak robią.
Jonathan Hartley

Odpowiedzi:


662

Heredoc wydaje się w tym celu wygodniejszy. Służy do wysyłania wielu poleceń do programu interpretera poleceń, takiego jak ex lub cat

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

Ciąg po <<wskazuje, gdzie się zatrzymać.

Aby wysłać te linie do pliku, użyj:

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

Możesz również zapisać te linie w zmiennej:

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

To przechowuje linie do zmiennej o nazwie VAR.

Podczas drukowania pamiętaj cudzysłowy wokół zmiennej, w przeciwnym razie nie zobaczysz znaków nowej linii.

echo "$VAR"

Co więcej, możesz użyć wcięcia, aby wyróżnić się bardziej w kodzie. Tym razem dodaj -po, <<aby zatrzymać wyświetlanie kart.

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

Ale musisz użyć tabulacji, a nie spacji, do wcięcia w kodzie.


38
dlaczego masz << - zamiast << w pierwszym wierszu 2. i 5. bloku kodu? robi - robi coś specjalnego?
lohfu

35
@ sup3rman '-' Ignoruje wiodące zakładki. Zobacz stackoverflow.com/questions/2500436/…
kervin

7
Jak mogę zakończyć odczyt ze statusem 0? Wygląda na to, że nie może znaleźć końca linii (ponieważ jest to -d) i kończy się na 1, co nie pomaga, gdy jesteś w skrypcie.
Misha Slyusarev

6
@MishaSlyusarev użyj -d '\ 0' i dodaj \ 0 na końcu ostatniej linii
Lars Schneider

11
Korzystanie z -dopcji w read -r -d '' VARIABLE <<- EOMnie działało dla mnie.
dron22

186

Jeśli próbujesz przekształcić ciąg w zmienną, innym łatwym sposobem jest coś takiego:

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

Jeśli wciśniesz swój ciąg za pomocą tabulatorów (tj. „\ T”), wcięcie zostanie usunięte. Jeśli wcinasz spacjami, wcięcie pozostanie w.

UWAGA: Znaczące jest to, że ostatni nawias zamykający znajduje się w innej linii. Sam ENDtekst musi pojawić się w wierszu.


1
Czy to jest znaczące? Działa na moim komputerze Mac z )tą samą linią. Wydaje mi się, że dzieje się tak dlatego, że to, co jest pomiędzy, $(i )będzie działać we własnym wszechświecie, a rzeczywiste polecenie i tak nie zobaczy „)”. Zastanawiam się, czy to działa również na innych.
deej

Możliwe, że różne powłoki będą interpretować różne rzeczy. W dokumentacji bash wydaje się, że nie mówi nic w ten czy inny sposób, ale wszystkie przykłady mają to na swój sposób.
Andrew Miner,

Zabawne jest to, że znalazłem odpowiedź podczas próby ustawienia wartości zmiennej USAGE. Twoja odpowiedź pasuje dokładnie. :)
Bunyk

1
@deej Ze specyfikacji POSIX : Dokument tutaj… trwa, dopóki nie będzie linii zawierającej tylko ogranicznik i <nową linię>
Fornost

1
@Fornost Nie jest to sprzeczne z tym, co powiedziałem
deej

84

echododaje spacje między przekazanymi mu argumentami. $textpodlega zmiennej interpretacji i dzieleniu słów, więc twoje echopolecenie jest równoważne z:

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

Możesz zobaczyć, że spacja zostanie dodana przed „tym”. Możesz usunąć znaki nowego wiersza i zacytować, $textaby zachować nowe znaki :

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

echo "$text" > filename

Lub możesz użyć printf, który jest bardziej solidny i przenośny niż echo:

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

W bash, który obsługuje rozwijanie nawiasów, możesz nawet:

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

1
Dziękujemy za wyjaśnienie, dlaczego mod widzi dodatkowe miejsce podczas używania polecenia „echo”. Ta odpowiedź działa również dobrze, gdy jest używana w skrypcie bash (w przeciwieństwie do interaktywnej sesji powłoki). Wydaje się, że to prawdziwa odpowiedź i powinna być odpowiedzią akceptowaną.
onelaview

1
Należy zaakceptować odpowiedź. Wszystkie pozostałe odpowiedzi dostarczyły mnóstwo interesujących innych sposobów rozwiązania bólu OP, ale technicznie pytanie brzmiało: „jak powstaje dodatkowa przestrzeń” i nikt nie odpowiedział na to :-) !!! Dziękuję Josh za zrobienie tajemnicy -1!
Dmitry Shevkoplyas

45

w skrypcie bash działają następujące elementy:

#!/bin/sh

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

alternatywnie:

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

nazwa pliku kota daje:

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

Wygląda na to, że alternatywna sztuczka działa podczas korzystania ze Skryptów powłoki, ale nie działa na bash.
Macindows

3
@Macindows Wydaje mi się, że zapomniałem -eopcji echo. Proszę spróbuj ponownie.
Chris Maes,

41

Znalazłem więcej rozwiązań, ponieważ chciałem, aby każda linia była odpowiednio wcięta:

  1. Możesz użyć echo:

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

    To nie działa, jeśli umieścisz "\n"tuż przed \końcem linii.

  2. Możesz też użyć printfdla lepszej przenośności (zdarzyło mi się mieć wiele problemów echo):

    printf '%s\n' \
        "this is line one"   \
        "this is line two"   \
        "this is line three" \
        > filename
  3. Jeszcze innym rozwiązaniem może być:

    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

    lub

    text=''
    text+="this is line one\n"
    text+="this is line two\n"
    text+="this is line three\n"
    printf "%b" "$text" > filename
  4. Inne rozwiązanie osiąga się przez mieszanie printfi sed.

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

    Refaktoryzacja kodu sformatowanego w ten sposób nie jest łatwa, ponieważ kodujesz poziom wcięcia w kodzie.

  5. Możliwe jest użycie funkcji pomocnika i niektórych sztuczek polegających na zamianie zmiennych:

    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"

3b jest moim preferowanym rozwiązaniem, gdy chcę zachować wcięcie kodu i wcięcie łańcucha niezależnie, przynajmniej wtedy, gdy nie mogę użyć 2 w przypisaniu zmiennej i jest bardziej czytelne niż 3a. Kiedy mnie to nie obchodzi, po prostu trzymam cytowany ciąg w kilku wierszach.
Pysis

Bardzo ładna odpowiedź, szczególnie 3b
Sumit Trehan 10.09.19

„Nie działa, jeśli umieścisz„ \ n ”tuż przed \ na końcu linii.” - jest świetną wskazówką podczas korzystania z echa.
Noam Manos

6

Poniżej znajduje się mój preferowany sposób przypisywania ciągu wieloliniowego do zmiennej (myślę, że wygląda ładnie).

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

String1
String2
String3
...
StringN
_______________________________________________________________________________

Liczba znaków podkreślenia jest taka sama (tutaj 80) w obu przypadkach.


0

Wystarczy wspomnieć o prostym łączeniu jednowierszowym, ponieważ może być czasem użyteczne.

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

Dostaniesz coś takiego

bar 
   bla 
 guga 
   puga 

Wszystkie wiodące i kończące się białe spacje zostaną zachowane

echo "$v3" > filename

0

Można to zrobić na wiele sposobów. Dla mnie ładowanie wcięcia sznurka w sed działa dobrze.

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"

Ta odpowiedź była oparta na odpowiedzi Mateusza Piotrowskiego , ale nieco dopracowana.


-1

zadziała, jeśli umieścisz go jak poniżej:

AA='first line
\nsecond line 
\nthird line'
echo $AA
output:
first line
second line
third line
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.