用空格缩进heredocs

问题描述 投票:31回答:2

对于我所从事的个人开发和项目,我们使用四个空格而不是制表符。但是,我需要使用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而不是。

bash indentation spaces heredoc
2个回答
36
投票

(如果您使用的是bash 4,请滚动到最后,我认为这是纯shell和可读性的最佳组合。)

对于shell脚本,使用选项卡不是首选项或样式;这就是语言的定义方式。

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[@]}"
}

下面再次使用here文档,但这次使用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
}

0
投票
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[*]}"
}

这是一种稍微不同的方法,由于printf分配给数组元素,因此需要Bash 4.1。 (对于以前的版本,请替换下面的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"
}

如上所述,对于早于4.1的Bash:

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
}
© www.soinside.com 2019 - 2024. All rights reserved.