Как получить перевод строки в sh?

Это

STR="Hello\nWorld"
echo $STR

производит как выход

Hello\nWorld

вместо

Hello
World

Что я должен сделать, чтобы иметь новую строку в строке?

Примечание: этот вопрос не об эхо. Я в курсе echo -e, но я ищу решение, которое позволяет передавать строку (которая включает новую строку) в качестве аргумента для других команд, которые не имеют аналогичной опции для интерпретации \nкак новые строки.

10 ответов

Решение

Решение заключается в использовании $'string', например:

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

Вот выдержка из страницы руководства 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.

Echo настолько девяностых и настолько чреват опасностями, что его использование должно привести к дампам ядра не менее 4 ГБ. Серьезно, проблемы эха были причиной того, что процесс стандартизации Unix, наконец, изобрел printf утилита, избавляющая от всех проблем.

Итак, чтобы получить новую строку в строке:

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

Там! Нет SYSV против BSD эха безумия, все становится аккуратно напечатано и полностью переносимая поддержка escape-последовательностей Си. Все, пожалуйста, используйте printf сейчас и никогда не оглядывайся назад.

На основании других ответов я сделал

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

Я нахожу -e флаг элегантный и прямой

STR="Hello\nWorld" echo -e $STR #outputs Hello World

Если строка является выводом другой команды, я просто использую кавычки

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

Проблема не с оболочкой. Проблема на самом деле с echo сама команда и отсутствие двойных кавычек вокруг переменной интерполяции. Вы можете попробовать использовать echo -e но это не поддерживается на всех платформах, и одна из причин printf сейчас рекомендуется для мобильности.

Вы также можете попробовать вставить новую строку прямо в ваш скрипт оболочки (если вы пишете скрипт), чтобы он выглядел следующим образом...

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

или эквивалентно

#!/bin/sh
string="Hello
World"
echo "$string"  # note double quotes!
  1. Единственная простая альтернатива - ввести новую строку в переменной:

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

    Да, это означает, что нужно вводить Enter в коде.

  2. Есть несколько эквивалентов new line персонаж.

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

    Но все они требуют "интерпретации" каким-либо инструментом ( 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.
    

    И поэтому инструмент необходим для построения строки с новой строкой:

    $ STR="$(printf 'new\nline')"
    $ printf '%s' "$STR"
    new
    line
    
  3. В некоторых оболочках последовательность $' является специальным расширением оболочки. Известно, что работает в ksh93, bash и zsh:

    $ STR=$'new\nline'
    
  4. Конечно, возможны и более сложные решения:

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

    Или же

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

Знак $ перед одинарными кавычками '...\n...' выглядит следующим образом, однако двойные кавычки не работают.

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

Я не эксперт по Bash, но этот работал для меня:

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

$STR2
EOF
)
echo "$NEWSTR"

Я нашел это проще для форматирования текстов.

Отказ от ответственности: я сначала написал это, а затем наткнулся на этот вопрос. Я думал, что это решение еще не опубликовано, и увидел, что tlwhitec опубликовал аналогичный ответ. Тем не менее, я публикую это, потому что надеюсь, что это полезное и подробное объяснение.

Краткий ответ:

Это кажется довольно переносимым решением, так как оно работает с некоторыми оболочками (см. Комментарий).
Таким образом, вы можете получить настоящую новую строку в переменной.

Преимущество этого решения заключается в том, что вам не нужно использовать символы новой строки в исходном коде, поэтому вы можете делать отступы в коде любым удобным для вас способом, и решение по-прежнему работает. Это делает его надежным. Он также портативный.

# Robust way to put a real newline in a variable (bash, dash, ksh, zsh; indentation-resistant).
nl="$(printf '\nq')"
nl=${nl%q}

Более длинный ответ:

Объяснение вышеуказанного решения:

Новая строка обычно теряется из-за подстановки команд, но чтобы предотвратить это, мы добавляем 'q' и удаляем его впоследствии. (Причина двойных кавычек объясняется ниже.)

Мы можем доказать, что переменная содержит фактический символ новой строки (0x0A):

printf '%s' "$nl" | hexdump -C
00000000  0a  |.|
00000001

(Обратите внимание, что '%s' был нужен, иначе printf переведет буквальный '\n' строка в фактический символ 0x0A, что означает, что мы ничего не будем доказывать.)

Конечно, вместо решения, предложенного в этом ответе, можно было бы использовать это (но...):

nl='
'

... но это менее надежно и может быть легко повреждено из-за случайного отступа в коде или из-за того, что впоследствии он будет забыт, что делает его неудобным для использования в функциях (с отступом), тогда как более раннее решение является надежным.

Теперь что касается двойных кавычек:
причина двойных кавычек " окружающие подстановку команд, как в nl="$(printf '\nq')" состоит в том, что вы можете даже префикс присвоения переменной с помощью local ключевое слово или встроенная функция (например, в функциях), и она по-прежнему будет работать во всех оболочках, тогда как в противном случае dashУ оболочки возникнут проблемы в том смысле, что в противном случае тире потеряет "q", и вы получите пустую переменную "nl" (опять же, из-за подстановки команд).
Эту проблему лучше проиллюстрировать на другом примере:

dash_trouble_example() {
    e=$(echo hello world) # Not using 'local'.
    echo "$e" # Fine. Outputs 'hello world' in all shells.

    local e=$(echo hello world) # But now, when using 'local' without double quotes ...:
    echo "$e" # ... oops, outputs just 'hello' in dash,
              # ... but 'hello world' in bash and zsh.

    local f="$(echo hello world)" # Finally, using 'local' and surrounding with double quotes.
    echo "$f" # Solved. Outputs 'hello world' in dash, zsh, and bash.

    # So back to our newline example, if we want to use 'local', we need
    # double quotes to surround the command substitution:
    # (If we didn't use double quotes here, then in dash the 'nl' variable
    # would be empty.)
    local nl="$(printf '\nq')"
    nl=${nl%q}
}

Практический пример вышеуказанного решения:

# Parsing lines in a for loop by setting IFS to a real newline character:

nl="$(printf '\nq')"
nl=${nl%q}

IFS=$nl

for i in $(printf '%b' 'this is line 1\nthis is line 2'); do
    echo "i=$i"
done

# Desired output:
# i=this is line 1
# i=this is line 2

# Exercise:
# Try running this example without the IFS=$nl assignment, and predict the outcome.

Это не идеально, но я написал много кода и определил строки аналогично методу, использованному в вопросе. Принятое решение потребовало от меня рефакторинга большого количества кода, поэтому вместо этого я заменил все\n с участием "$'\n'" и это сработало для меня.

Те требовательные, которые нуждаются только в новой строке и презирают многострочный код, который ломает отступы, могут сделать:

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

Bash (и, вероятно, другие оболочки) сожмет все завершающие символы новой строки после подстановки команд, поэтому вам нужно завершить printf строка с не-символом новой строки и удалите его впоследствии. Это также может легко стать вкладчиком.

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

Я знаю, что это два действия вместо одного, но мой OCD отступа и переносимости теперь в порядке:) Я изначально разработал это, чтобы иметь возможность разделять вывод, разделенный только на новую строку, и в итоге я использовал модификацию, которая использует \r как завершающий персонаж. Это делает расщепление новой строки работоспособным даже для вывода DOS, заканчивающегося на \r\n,

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

В моей системе (Ubuntu 17.10) ваш пример просто работает как нужно, как при вводе из командной строки (в sh) и когда исполняется как sh сценарий:

[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

Я думаю, что это отвечает на ваш вопрос: это просто работает. (Я не пытался выяснить детали, такие как, в какой момент именно замена символа новой строки для \n происходит в sh).

Тем не менее, я заметил, что этот же скрипт будет вести себя по-разному при выполнении сbash и распечатал бы Hello\nWorld вместо:

[bash]§ bash test-str.sh
Hello\nWorld

Мне удалось получить желаемый результат с bash следующее:

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

Обратите внимание на двойные кавычки $STR, Это ведет себя одинаково, если сохранить и запустить как bash скрипт.

Следующее также дает желаемый результат:

[bash]§ echo "Hello
> World"

Я не был действительно счастлив ни с одним из вариантов здесь. Это то, что сработало для меня.

str=$(printf "%s" "first line")
str=$(printf "$str\n%s" "another line")
str=$(printf "$str\n%s" "and another line")
Другие вопросы по тегам