Jak mogę mieć nową linię w ciągu w sh?


339

To

STR="Hello\nWorld"
echo $STR

produkuje jako wynik

Hello\nWorld

zamiast

Hello
World

Co powinienem zrobić, aby mieć nową linię w ciągu?

Uwaga: To pytanie nie dotyczy echa . Jestem tego świadomy echo -e, ale szukam rozwiązania, które pozwala przekazywać ciąg znaków (który zawiera znak nowej linii) jako argument do innych poleceń, które nie mają podobnej opcji interpretowania \nznaków jako linii nowej linii.

Odpowiedzi:


353

Rozwiązaniem jest użycie $'string'na przykład:

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

Oto fragment strony podręcznika 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
To poprawne bash, ale nie POSIX sh.
jmanning2k

7
+ 1-tylko uwaga: eksport varDynamic = "$ varAnother1 $ varAnother2" $ '\ n'
Yordan Georgiev

3
To rzeczywiście działa z literałowymi ciągami znaków, o które poprosił OP, ale kończy się niepowodzeniem, gdy tylko zmienne zostaną zastąpione: STR=$"Hello\nWorld"po prostu drukuje Hello\nWorld.
ssc

3
@ssc pojedyncze cytaty, nie podwójne
miken32

7
@ssc w twoim przykładzie nie ma żadnych zmiennych. Ponadto ta metoda wymaga pojedynczych cudzysłowów. Wreszcie, aby uwzględnić zmienne interpolowane, należy połączyć ze sobą ciągi cudzysłowów i ciągi specjalnych cudzysłowów. np. H="Hello"; W="World"; STR="${H}"$'\n'"${W}"; echo "${STR}"rozlegnie się echo „Hello” i „World” w osobnych wierszach
JDS

166

Echo ma tak lata dziewięćdziesiąte i jest tak pełne niebezpieczeństw, że jego użycie powinno doprowadzić do zrzutów rdzenia nie mniej niż 4 GB. Poważnie, problemy echa były przyczyną, dla której proces standaryzacji Unixa w końcu wymyślił printfnarzędzie, usuwając wszystkie problemy.

Aby uzyskać nowy wiersz w ciągu:

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

Tam! Bez szaleństwa echa SYSV vs BSD, wszystko jest ładnie wydrukowane iw pełni przenośne wsparcie dla sekwencji ucieczki C. Proszę, skorzystajcie printfteraz i nigdy nie oglądajcie się za siebie.


3
Dzięki za tę odpowiedź, myślę, że to naprawdę dobra rada. Zauważ jednak, że moje oryginalne pytanie tak naprawdę nie dotyczyło tego, jak uzyskać echo przy drukowaniu nowych linii, ale jak zamienić nowy wiersz w zmienną powłoki. Pokazujesz też, jak to zrobić za pomocą printf, ale myślę, że rozwiązanie z użyciem amfetamachiny$'string' jest jeszcze czystsze.
Juan A. Navarro

11
Tylko w przypadku błędnej definicji „czystego”. $'foo'Składnia jest niepoprawna składnia POSIX jako 2013. Wiele muszli będą narzekać.
Jens

5
BAR=$(printf "hello\nworld\n")nie drukuje końcowego \ n dla mnie
Jonny

3
@Jonny To nie powinno; specyfikacja powłoki dla podstawiania poleceń mówi, że jeden lub więcej nowych wierszy na końcu danych wyjściowych jest usuwanych. Zdaję sobie sprawę, że jest to nieoczekiwane w moim przykładzie i dokonałem edycji, aby uwzględnić nowy wiersz w printf.
Jens

5
@ismael Nie, $()jest określony przez POSIX jako usuwanie sekwencji jednego lub więcej <newline> znaków na końcu podstawienia .
Jens

61

To, co zrobiłem na podstawie innych odpowiedzi, było

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

Problem nie dotyczy powłoki. Problem tkwi w echosamej komendzie i braku podwójnych cudzysłowów wokół interpolacji zmiennej. Możesz spróbować użyć, echo -eale nie jest to obsługiwane na wszystkich platformach, a jeden z powodów printfjest teraz zalecany dla przenośności.

Możesz także spróbować wstawić nowy wiersz bezpośrednio do skryptu powłoki (jeśli skrypt jest tym, co piszesz), aby wyglądał jak ...

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

lub równoważnie

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

24

Uważam, że -eflaga jest elegancka i prosta

bash$ STR="Hello\nWorld"

bash$ echo -e $STR
Hello
World

Jeśli ciąg jest wynikiem innego polecenia, po prostu używam cudzysłowów

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

2
Zostało to już wspomniane w innych odpowiedziach. Ponadto, chociaż już tam było, właśnie zredagowałem pytanie, aby podkreślić fakt, że nie chodzi o to pytanie echo.
Juan A. Navarro

To nie jest odpowiedź na pytanie
Rohit Gupta

To całkowicie odpowiada pytaniu, którego szukałem! Dzięki!
Kieveli

1
Dobrze mi tu pracowało. Kiedyś tworzyłem plik na podstawie tego echa i poprawnie generował nowe wiersze na wyjściu.
Mateus Leon

echo -ejest znany ze swoich problemów związanych z przenośnością. Obecnie jest to mniej sytuacja na Dzikim Zachodzie niż w wersji sprzed POSIX, ale nadal nie ma powodu, aby preferować echo -e. Zobacz także stackoverflow.com/questions/11530203/…
tripleee

21
  1. Jedyną prostą alternatywą jest wpisanie nowego wiersza w zmiennej:

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

    Tak, oznacza to pisanie w Enterrazie potrzeby w kodzie.

  2. Istnieje kilka odpowiedników new linepostaci.

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

    Ale wszystkie wymagają „interpretacji” przez jakieś narzędzie ( 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.

    Dlatego narzędzie jest wymagane do zbudowania łańcucha z nową linią:

    $ STR="$(printf 'new\nline')"
    $ printf '%s' "$STR"
    new
    line
  3. W niektórych powłokach sekwencja $'jest specjalnym rozszerzeniem powłoki. Znany do pracy w ksh93, bash i zsh:

    $ STR=$'new\nline'
  4. Oczywiście możliwe są również bardziej złożone rozwiązania:

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

    Lub

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

6

$ Tuż przed pojedynczymi cudzysłowami „... \ n ...” w następujący sposób, jednak podwójne cudzysłowy nie działają.

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

4

Nie jestem ekspertem od bash, ale ten działał dla mnie:

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

$STR2
EOF
)
echo "$NEWSTR"

Ułatwiło mi to formatowanie tekstów.


2
Dobry stary heredok . I prawdopodobnie jest również zgodny z POSIX!
py

Cytaty w tym $NEWSTRmiejscu echo "$NEWSTR"są tutaj ważne
shadi

0

W moim systemie (Ubuntu 17.10) twój przykład działa tak, jak chcesz, zarówno po wpisaniu z linii poleceń (do sh), jak i po uruchomieniu jako shskrypt:

[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

Myślę, że to odpowiada na twoje pytanie: to po prostu działa. (Nie próbowałem dowiedzieć się szczegółów, takich jak na jakim dokładnie momencie zastąpienia znak nowej linii dla \nzdarza się sh).

Jednak zauważyłem, że ten sam skrypt będzie zachowywać się inaczej, gdy wykonywane zbash i będzie drukować Hello\nWorldzamiast:

[bash bash test-str.sh
Hello\nWorld

Udało mi się uzyskać pożądany wynik bashw następujący sposób:

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

Zwróć uwagę na podwójne cytaty wokół $STR. To zachowuje się identycznie, jeśli zostanie zapisane i uruchomione jako bashskrypt.

Poniższe daje również pożądany wynik:

[bash echo "Hello
> World"

0

Ci wybredni, którzy potrzebują tylko nowej linii i gardzą kodem wieloliniowym, który łamie wcięcia, mogą zrobić:

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

Bash (i prawdopodobnie inne powłoki) pożerają wszystkie końcowe znaki nowej linii po podstawieniu polecenia, więc musisz zakończyć printfciąg znakiem innym niż znak nowej linii, a następnie go usunąć. Może to również łatwo stać się oneliner.

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

Wiem, że są to dwie akcje zamiast jednej, ale moje wcięcie i przenośność OCD jest teraz spokojna :) Pierwotnie opracowałem to, aby móc rozdzielić dane wyjściowe tylko dla nowej linii i skończyłem na modyfikacji, która używa \rjako znaku kończącego. To sprawia, że ​​podział nowej linii działa nawet dla wyjścia dos kończącego się na \r\n.

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

0

Nie byłam zadowolona z żadnej z dostępnych tu opcji. To działało dla mnie.

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

To bardzo żmudny sposób pisania str=$(printf '%s\n' "first line" "another line" "and another line")(powłoka wygodnie (lub nie) przycina wszelkie końcowe znaki nowej linii z podstawiania poleceń). Kilka odpowiedzi tutaj już promuje się printfw różnych formach, więc nie jestem pewien, czy stanowi to wartość dodaną jako osobna odpowiedź.
tripleee

To jest dla krótkich ciągów, ale jeśli chcesz zachować porządek, a każda linia ma w rzeczywistości 60 znaków, jest to porządny sposób na zrobienie tego. Biorąc pod uwagę, że jest to rozwiązanie, z którym ostatecznie skorzystałem, dodaje coś, jeśli nie dla ciebie, to dla mnie w przyszłości, kiedy niewątpliwie wrócę.
Adam K Dean

1
Nawet w przypadku długich łańcuchów wolę printf '%s\n' "first line" \ (nowa linia, wcięcie) 'second line'itp. Zobacz także, jak printf -v strprzypisać zmienną bez odradzania podpowłoki.
tripleee
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.