Отступ наследников с пробелами
Для личного развития и проектов, над которыми я работаю, мы используем четыре пробела вместо вкладок. Однако мне нужно использовать heredoc, и я не могу сделать это, не нарушив поток отступов.
Единственный рабочий способ сделать это, я могу придумать, это:
usage() {
cat << ' EOF' | sed -e 's/^ //';
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That's all.
EOF
}
Есть лучший способ сделать это?
Дайте мне знать, если это относится к Unix/Linux Stack Exchange.
2 ответа
(Если вы используете bash
4, прокрутите до конца для того, что я считаю лучшим сочетанием чистой оболочки и читабельности.)
Для сценариев оболочки использование вкладок не является вопросом предпочтений или стиля; это как язык определяется.
usage () {
# Lines between EOF are each indented with the same number of tabs
# Spaces can follow the tabs for in-document indentation
cat <<-EOF
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That's all.
EOF
}
Другой вариант - полностью избежать здесь документа, за счет необходимости использовать больше кавычек и продолжений строк:
usage () {
printf '%s\n' \
"Hello, this is a cool program." \
"This should get unindented." \
"This code should stay indented:" \
" something() {" \
" echo It works, yo!" \
" }" \
"That's all."
}
Если вы хотите отказаться от POSIX-совместимости, вы можете использовать массив, чтобы избежать явных продолжений строки:
usage () {
message=(
"Hello, this is a cool program."
"This should get unindented."
"This code should stay indented:"
" something() {"
" echo It works, yo!"
" }"
"That's all."
)
printf '%s\n' "${message[@]}"
}
Далее снова используется документ здесь, но на этот раз с bash
4-х readarray
Команда для заполнения массива. Расширение параметра заботится об удалении фиксированного количества пробелов в начале каждой лжи.
usage () {
# No tabs necessary!
readarray message <<' EOF'
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That's all.
EOF
# Each line is indented an extra 8 spaces, so strip them
printf '%s' "${message[@]# }"
}
Последний вариант: вы можете использовать расширенный шаблон для упрощения расширения параметров. Вместо того, чтобы считать, сколько пробелов используется для отступа, просто завершите отступ выбранным непробельным символом, а затем сопоставьте фиксированный префикс. я использую :
, (Пробел, следующий за двоеточием, предназначен для удобства чтения; его можно удалить с небольшим изменением шаблона префикса.)
(Кроме того, кроме того, один недостаток вашего очень приятного трюка с использованием разделителя here-doc, начинающегося с пробела, заключается в том, что он не позволяет выполнять расширения внутри here-doc. Если бы вы захотели это сделать, вы бы либо оставить разделитель без отступов, либо сделать одно незначительное исключение из правила без табуляции и использовать <<-EOF
и закрывающий разделитель с вкладкой.)
usage () {
# No tabs necessary!
closing="That's all"
readarray message <<EOF
: Hello, this is a cool program.
: This should get unindented.
: This code should stay indented:
: something() {
: echo It works, yo!;
: }
: $closing
EOF
shopt -s extglob
printf '%s' "${message[@]#+( ): }"
shopt -u extglob
}
geta() {
local _ref=$1
local -a _lines
local _i
local _leading_whitespace
local _len
IFS=$'\n' read -rd '' -a _lines ||:
_leading_whitespace=${_lines[0]%%[^[:space:]]*}
_len=${#_leading_whitespace}
for _i in "${!_lines[@]}"; do
printf -v "$_ref"[$_i] '%s' "${_lines[$_i]:$_len}"
done
}
gets() {
local _ref=$1
local -a _result
local IFS
geta _result
IFS=$'\n'
printf -v "$_ref" '%s' "${_result[*]}"
}
Это немного другой подход, который требует Bash 4.1 из-за присваивания printf элементам массива. (для предыдущих версий, заменить geta
функция ниже). Он имеет дело с произвольным лидирующим пробелом, а не только с заранее определенной суммой.
Первая функция, geta
, читает из stdin, удаляет начальные пробелы и возвращает результат в массив, имя которого было передано.
Второй, gets
делает то же самое, что и geta
но возвращает одну строку с новыми строками без изменений (кроме последней).
Если вы передаете имя существующей переменной geta
, убедитесь, что он уже пуст.
взывать geta
вот так:
$ geta hello <<'EOS'
> hello
> there
>EOS
$ declare -p hello
declare -a hello='([0]="hello" [1]="there")'
gets
:
$ unset -v hello
$ gets hello <<'EOS'
> hello
> there
> EOS
$ declare -p hello
declare -- hello="hello
there"
Этот подход должен работать для любой комбинации начальных пробельных символов, если они являются одинаковыми символами для всех последующих строк. Функция удаляет одинаковое количество символов в начале каждой строки, основываясь на количестве начальных пробельных символов в первой строке.
Причина, по которой все переменные начинаются с подчеркивания, состоит в том, чтобы минимизировать вероятность конфликта имен с переданным именем массива. Возможно, вы захотите переписать это, чтобы префикс с чем-то еще менее вероятно столкнулся.
Для использования в функции OP:
gets usage_message <<'EOS'
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That's all.
EOS
usage() {
printf '%s\n' "$usage_message"
}
Как уже упоминалось, для Bash старше 4.1:
geta() {
local _ref=$1
local -a _lines
local _i
local _leading_whitespace
local _len
IFS=$'\n' read -rd '' -a _lines ||:
_leading_whitespace=${_lines[0]%%[^[:space:]]*}
_len=${#_leading_whitespace}
for _i in "${!_lines[@]}"; do
eval "$(printf '%s+=( "%s" )' "$_ref" "${_lines[$_i]:$_len}")"
done
}