Отступ наследников с пробелами

Для личного развития и проектов, над которыми я работаю, мы используем четыре пробела вместо вкладок. Однако мне нужно использовать 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
}
Другие вопросы по тегам