web-dev-qa-db-fra.com

Indenter des heredocs avec des espaces

Pour le développement personnel et les projets sur lesquels je travaille, nous utilisons quatre espaces au lieu d'onglets ..__ Cependant, je dois utiliser un heredoc et je ne peux pas le faire sans interrompre le flux d'indentation.

La seule façon efficace de faire cela que je peux penser serait la suivante:

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
}

Y a-t-il une meilleure manière de faire cela?

Faites-moi savoir si cela appartient à la Unix/Linux Stack Exchange à la place.

29
IBPX

(Si vous utilisez bash 4, faites défiler jusqu'à la fin pour obtenir la meilleure combinaison possible de Shell pur et de lisibilité.)

Pour les scripts Shell, l’utilisation des tabulations n’est pas une question de préférence ou de style; c'est comment le langage est défini.

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
}

Une autre option consiste à éviter complètement un document ici, au prix d'avoir à utiliser plus de guillemets et de continuations en ligne:

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."
}

Si vous souhaitez renoncer à la compatibilité POSIX, vous pouvez utiliser un tableau pour éviter les suites de lignes explicites:

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

Ce qui suit utilise à nouveau un document here, mais cette fois avec la commande bash de readarray 4 pour peupler un tableau. L'expansion des paramètres permet de supprimer un nombre fixe d'espaces dès le début de chaque mensonge.

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

Une dernière variante: vous pouvez utiliser un modèle étendu pour simplifier le développement des paramètres. Au lieu de compter le nombre d'espaces utilisés pour l'indentation, terminez simplement l'indentation par un caractère non-espace choisi, puis faites correspondre le préfixe fixe. J'utilise :. (L'espace après Les deux points est destiné à la lisibilité; il peut être supprimé avec une modification mineure du motif de préfixe.)

(De plus, en retranchement, l’un des inconvénients de votre très belle astuce consistant à utiliser un délimiteur here-doc qui commence par un espace est qu’il vous empêche de réaliser des extensions à l’intérieur de cet objet. soit laisser le délimiteur non indenté, soit créer une exception mineure à votre règle sans tabulation et utiliser <<-EOF et un délimiteur de fermeture indenté par une tabulation.)

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

C'est une approche légèrement différente qui nécessite Bash 4.1 en raison de l'affectation des éléments de tableau par printf. (pour les versions précédentes, remplacez la fonction geta ci-dessous). Il s’agit d’espaces blancs arbitraires et non pas d’un montant prédéterminé.

La première fonction, geta, lit à partir de stdin, supprime les espaces en blanc et renvoie le résultat dans le tableau dont le nom a été transmis.

La seconde, gets, fait la même chose que geta mais renvoie une seule chaîne avec des nouvelles lignes intactes (sauf la dernière).

Si vous transmettez le nom d'une variable existante à geta, assurez-vous qu'elle est déjà vide.

Invoquez geta comme ceci:

$ 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"

Cette approche devrait fonctionner pour toute combinaison de caractères d'espacement, à condition qu'ils soient identiques pour toutes les lignes suivantes. La fonction supprime le même nombre de caractères au début de chaque ligne, en fonction du nombre de caractères blancs d'espacement dans la première ligne.

Toutes les variables commençant par un trait de soulignement ont pour but de minimiser les risques de collision de noms avec le nom de tableau transmis. Vous voudrez peut-être réécrire ceci pour les préfixer avec quelque chose d'encore moins susceptible d'entrer en collision.

A utiliser dans la fonction 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"
}

Comme mentionné, pour Bash plus vieux que 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
}
0
Binary Phile