web-dev-qa-db-ja.com

ヒアドキュメントをスペースでインデントする

私が取り組んでいる個人的な開発とプロジェクトでは、タブの代わりに4つのスペースを使用します。ただし、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
}

これを行うためのより良い方法はありますか?

代わりに nix/Linux Stack Exchange に属しているかどうかを教えてください。

31
IBPX

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区切り文字を使用する非常に素晴らしいトリックの1つの欠点は、here-doc内で展開を実行できないことです。そうする場合は、区切り文字をインデントせずに残すか、タブなしルールに1つの小さな例外を加えて、<<-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
}
37
chepner
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から読み取り、先頭の空白を取り除き、名前が渡された配列に結果を返します。

2番目の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
}
0
Binary Phile