Как вывести многострочную строку в Bash?

250

Как я могу вывести многострочную строку в Bash без использования нескольких эхо-вызовов, например, так:

echo "usage: up [--level <n>| -n <levels>][--help][--version]"
echo 
echo "Report bugs to: "
echo "up home page: "

Я ищу портативный способ сделать это, используя только встроенные функции Bash.

helpermethod
источник
4
Если вы выводите сообщение об использовании в ответ на неправильный вызов, вы обычно отправляете это сообщение со стандартной ошибкой, а не со стандартным выводом, сecho >&2 ...
Mark Reed
2
@MarkReed Сообщение об использовании выводится, набирая --help(что должно идти в стандартный выход).
Helpermethod
Для других, кто придет, больше информации об «здесь документах» доступно: tldp.org/LDP/abs/html/here-docs.html
Джеффри Мартинес
Проверьте printfрешение на основе от Гордона Дэвидсона. Несмотря на то, что он находится в тени подходов echoили catоснованных на них подходов, кажется, что это намного меньше помехи. По общему признанию синтаксис `printf 'представляет собой
некоторую
Связанный: stackoverflow.com/questions/23929235/…
Антон Тарасенко

Ответы:

296

Здесь документы часто используются для этой цели.

cat << EOF
usage: up [--level <n>| -n <levels>][--help][--version]

Report bugs to: 
up home page:
EOF

Они поддерживаются во всех оболочках Bourne, включая все версии Bash.

Приостановлено до дальнейшего уведомления.
источник
4
Да, но catне встроенный.
Марк Рид
8
@MarkReed: Это правда, но это всегда доступно (кроме, возможно, при необычных обстоятельствах).
Приостановлено до дальнейшего уведомления.
6
+1 Thx. Я закончил тем, что использовал, read -d '' help <<- EOF ...чтобы прочитать многострочную строку в переменную, а затем повторил результат.
helpermethod
3
я могу сохранить HEREDOC в переменную?
Чови
177

или вы можете сделать это:

echo "usage: up [--level <n>| -n <levels>][--help][--version]

Report bugs to: 
up home page: "
Крис Мор
источник
1
@OliverWeiler: Он будет работать даже в таких оболочках Bourne, как Dash и Heamloom Bourne Shell .
Приостановлено до дальнейшего уведомления.
6
Не очень хорошо, если вам нужно это в функции, потому что вам нужно либо: 1) отступить строку слева от вашего файла, либо 2) оставить ее с отступом, чтобы она соответствовала остальной части вашего кода, но затем она печатается с отступы , а также
к.с.
43

Вдохновленный проницательными ответами на этой странице, я создал смешанный подход, который я считаю самым простым и более гибким. Что вы думаете?

Во-первых, я определяю использование в переменной, что позволяет мне использовать его в разных контекстах. Формат очень прост, почти WYSIWYG, без необходимости добавлять какие-либо управляющие символы. Это кажется мне достаточно портативным (я запускал его на MacOS и Ubuntu)

__usage="
Usage: $(basename $0) [OPTIONS]

Options:
  -l, --level <n>              Something something something level
  -n, --nnnnn <levels>         Something something something n
  -h, --help                   Something something something help
  -v, --version                Something something something version
"

Тогда я могу просто использовать его как

echo "$__usage"

или, что еще лучше, при разборе параметров я могу просто отобразить его там в одну строку:

levelN=${2:?"--level: n is required!""${__usage}"}
Jorge
источник
4
это сработало для меня в сценарии, где вышеуказанный ответ не (без изменений).
Дэвид Уэлч
3
Это намного чище, чем использование набора символов, таких как \ t и \ n, которые трудно найти в тексте и раскрыть, чтобы сделать вывод сильно отличающимся от строки в сценарии
sg
1
По некоторым причинам он печатает все в одной строке для меня: /
Николас де Фонтене
2
@Nicolas: Мне нужно было использовать двойные кавычки echo "$__usage". echo $__usageне сработало.
Марио
24

Используйте -eопцию, тогда вы можете напечатать символ новой строки \nв строке.

Образец (но не уверен, хороший ли он или нет)

Самое интересное, что этот -eпараметр не задокументирован на справочной странице MacOS, но все еще доступен для использования. Это задокументировано на странице руководства Linux .

оборота
источник
6
Эти man-страницы предназначены для системной echoкоманды /bin/echo, которая в Mac OS не имеет -eопции. когда вы используете bash в этих системах, его встроенная echoкоманда вступает во владение. Вы можете увидеть это, явно набрав /bin/echo whateverи наблюдая разницу в поведении. Чтобы увидеть документацию для встроенного, введите help echo.
Марк Рид
1
/bin/echoчасто отличается от одной ОС к другой и отличается от встроенного в Bash echo.
Приостановлено до дальнейшего уведомления.
@MarkReed: попробую позже, но спасибо за информацию. +1. Я просто оставлю здесь свой ответ, так как здесь идет много хороших дискуссий.
nhahtdh
7
echo -eне является переносимым - например, некоторые реализации echo будут выводить «-e» как часть вывода. Если вы хотите переносимости, используйте вместо этого printf. Например, / bin / echo на OS X 10.7.4 делает это. IIRC встроенное эхо bash было также странным в 10.5.0, но я больше не помню деталей.
Гордон Дэвиссон
2
echo -eукусил меня раньше ... Определенно используйте printfили catс наследственником. <<-Вариант здесь документы особенно приятно , потому что вы можете раздеться ведущими отступы на выходе, но отступы для удобства чтения в сценарии
zbeekman
22

Поскольку я рекомендовал printfв комментарии, я, вероятно, должен привести несколько примеров его использования (хотя для печати сообщения об использовании я бы с большей вероятностью использовал ответы Денниса или Криса). printfнемного сложнее в использовании, чем echo. Его первый аргумент - это строка формата, в которой экранирование (например \n) всегда интерпретируется; он также может содержать директивы формата, начиная с %которых, которые определяют, где и как любые дополнительные аргументы включаются в него. Вот два разных подхода к его использованию для сообщения об использовании:

Во-первых, вы можете включить все сообщение в строку формата:

printf "usage: up [--level <n>| -n <levels>][--help][--version]\n\nReport bugs to: \nup home page: \n"

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

printf "usage: up [--level <n>| -n <levels>][--help][--version]\n\nReport bugs to: %s\nup home page: %s\n" "$bugreport" "$homepage"

Во-вторых, вы можете просто использовать строку формата, чтобы она выводила каждый дополнительный аргумент в отдельной строке:

printf "%s\n" "usage: up [--level <n>| -n <levels>][--help][--version]" "" "Report bugs to: " "up home page: "

С этой опцией добавление адресов багрепорта и домашней страницы довольно очевидно:

printf "%s\n" "usage: up [--level <n>| -n <levels>][--help][--version]" "" "Report bugs to: $bugreport" "up home page: $homepage"
Гордон Дэвиссон
источник
9

Также с отступом исходного кода вы можете использовать <<-(с завершающей чертой), чтобы игнорировать ведущие вкладки (но не ведущие пробелы). Например это:

if [ some test ]; then
    cat <<- xx
        line1
        line2
xx
fi

Выводит текст с отступом без начальных пробелов:

line1
line2
Эллиптический вид
источник
Это не сработало для меня. Какую оболочку вы используете?
четыре43
Не работал в Bash 4.4.19 в Ubuntu. Он не удалил интервал перед line1 и line2
четыре43
1
@ four43, ты был прав. Не работает убрать ведущие пробелы. Тем не менее, удаляет лидирующие вкладки. Таким образом, я исправил свой ответ от вкладок и пробелов, до вкладок, а не пробелов. Извините за ошибку. Я проверил руководство, и в нем четко сказано, что вкладки удалены. Спасибо, что обратили на это мое внимание.
эллиптический вид
0

Если вы используете решение из @jorge и обнаружите, что все в одной строке, убедитесь, что вы заключили переменную в кавычки:

echo $__usage

будет печатать все в одной строке, тогда как

echo "$__usage"

сохранит новые строки.

user1165471
источник
На самом деле это единственное решение, которое сработало для меня. Printf делает много вещей, и с моим многострочным xml, вероятно, требуется много экранирования, потому что он полностью искажает содержимое. Я назначаю foo = cat <<EOF .... EOF&& echo "$ foo"
Джиллес ван Гурп