用空格缩进heredocs

时间:2015-11-19 22:34:13

标签: bash indentation spaces heredoc

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

答案 0 :(得分: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&#39; 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中执行扩展。如果你想这样做,你和& #39; d必须使分隔符保持未缩进状态,或者对无标记规则进行一次小的异常,并使用<<-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
}

答案 1 :(得分: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读取,剥离前导空格并将结果返回到传入名称的数组中。

第二个getsgeta做同样的事情但返回一个字符串,其中换行符完整(除了最后一行)。

如果您将现有变量的名称传递给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
}