Forum Programmation.shell Questions sur les fonctions

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
2
7
juin
2020

Bonjour,

J'ai écrit un script bash avec quelques fonctions et j'ai un petit problème.

Comme son nom l'indique, la fonction end_script met fin à l'éxécution du script en expliquant l'origine (passée en variable) de l'erreur.

    end_script()
    {
      local args
      local exit_code
      local reason
      args="$#"
      exit_code="$1"
      reason="$2"
      [ "$args" = 2 ] \
        && printf "%s\n" "Error : $reason"
      printf "%s\n\n" "End of the script."
      exit "$exit_code"
    }

J'ai ensuite une fonction qui teste si une variable est définie :

is_empty()
{
  local var
  var="$1"
  [ -z "$var" ]
}

Le test se fait ensuite simplement avec :

is_empty "$DIR_SRC" \
  && end_script "1" "the user variable \"DIR_SRC\" is not set."

Et cela produit le résultat escompté.

Je voudrais simplifier avec un truc dans ce genre :

is_empty()
{
  local var
  var="$1"
  [ -z "$var" ] \
    && end_script "1" "the user variable $var is not set."
}

is_empty "$DIR_SRC"

Mais cela ne marche pas.
Au lieu d'obtenir "the user variable $DIR_SRC is not set.", j'obtiens "the user variable is not set."
Donc moins précis sur la cause de l'erreur.

Auriez-vous une idée sur comment atteindre le résultat souhaité ?

  • # Évaluation en deux temps

    Posté par  (site Web personnel) . Évalué à 3.

    Tu obtiens même the user variable is not set. avec deux espaces. ;)

    Il y a une légère confusion : tu essaies de passer à ta fonction le nom de ta variable ou son éventuel contenu ?

    Si tu passes son nom à la fonction, il va falloir demander à ton shell d'aller chercher son contenu. Cela peut se faire en utilisant eval (qui n'est pas trivial car il faut penser à la double évaluation que cela implique).

    Si tu passes son contenu à la fonction, celle-ci n'a pas accès à son nom. Elle ne peut donc pas l'utiliser dans un éventuel message d'erreur.

    Debian Consultant @ DEBAMAX

    • [^] # Re: Évaluation en deux temps

      Posté par  . Évalué à 2. Dernière modification le 12/06/20 à 14:36.

      en bash, on peut passer une variable par référence :

      maFonc()
      {
         local -n var=$1 #ou declare
         test -z "$var" && { >&2 echo "\"${!var}\" n'a pas de valeur."; exit 1;}
      }

      on exécute ainsi :

      $ variable=''
      $ maFonc variable # on indique **le nom** de la variable, pas son développement.
      "variable" n'a pas de valeur.
      $
    • [^] # Re: Évaluation en deux temps

      Posté par  . Évalué à 1.

      Merci pour eval.
      Après avoir googlé, je suis arrivé à ça :

      is_empty()
      {
        local var1
        local var2
        local var3
        var1="$1"
        var2='$'$var1
        eval var3="$var2"
        [ -z "$var3" ] \
          && end_script "1" "the user variable $var1 is not set."
      }
      
      is_empty "DIR_SRC"  # <- sans le $ de la variable sinon ça part en vrille

      Avec le résultat que je recharchais :

      Error : the user variable DIR_SRC is not set.
      End of the script.

      Cela fait le boulot comme demandé mais cela génère un nouveau problème : la variable passé à la fonction "is_empty" ne doit avoir le symbole $, sinon eval ne marche pas.

      Du coup ça permet d'enlever des lignes de code dans le script mais l'utilisation de DIR_SRC est moins lisible :-(

  • # une bien belle fonction...

    Posté par  . Évalué à -3.

    mais tellement inutile.

    • [^] # Re: une bien belle fonction...

      Posté par  . Évalué à 3.

      Bonjour,

      Pourquoi donc ?

      Je ne suis pas administrateur système ni développeur, donc si vous pouviez m'expliquer en quoi je me suis trompé… :-)

      • [^] # Re: une bien belle fonction...

        Posté par  . Évalué à -2.

        il est inutile de faire une fonction pour un simple test.

        • [^] # Re: une bien belle fonction...

          Posté par  . Évalué à 3.

          Salut,

          Au début, je voulais répondre, puis j'ai laissé de côté. Puis j'ai vu que l'OP t'avais répondu et toi aussi.

          Donc là, ça me gratte trop ce que j'avais en tête : parfois, faire du code complètement inutile, c'est bon pour la compréhension.

          J'ai l'impression (je peux me tromper) que l'OP est juste en train d'apprendre. Et que les bases sont pas bancales, juste besoin d'un peu d'aide (que je ne peux malheureusement pas apporter pour du shell).

          Je me souviens des cours d'algo que je donnais il y a longtemps, basés sur les tris. Est-ce que je commençais par le tri rapide ? Non. Ça commençait plus par le tri bulle, complètement inefficace la plus part du temps. Donc les élèves étaient prévenu, hein, genre : Ça c'est super débile, mais à la fin, on arrivera peut-être au tri rapide si vous suivez.

          Faire du code, même mort né, si on apprend, c'est un bon moyen de comprendre.

          Bref, tout dépend de ce que tu veux au final.

          Matricule 23415

        • [^] # Re: une bien belle fonction...

          Posté par  . Évalué à 6.

          C'est ton commentaire que j'ai noté comme inutile car :

          • la fonction aide à la lisibilité du code (nom explicite contrairement à un test qu'il faut interpréter)
          • la fonction permet une maintenance plus aisée du code (si le test doit être modifié ou corrigé)
          • la fonction aide à avoir une homogénéité (le test est toujours effectué de la même façon)
          • la fonction aide à la portabilité (ce genre de test est souvent utilisé sur des projets multiples, conserver une liste de ces fonctions peut faire gagner du temps)
          • [^] # Re: une bien belle fonction...

            Posté par  . Évalué à 0.

            tu as du mal à interpréter test ?

            bof, à la rigueur, si le test est répété dans le script dans les mêmes conditions.

            mais, je ne vais pas réécrire une fonction pour chaque option de chaque commande du shell pour ça.

            • [^] # Re: une bien belle fonction...

              Posté par  . Évalué à 3.

              test -z "$my_string"
              

              ou

              is_empty "$my_string"
              

              Oui, je prétends que dans le premier cas le lecteur doit interpréter que le test est fait pour savoir s'il s'agit d'un string "vide" alors que dans le deuxième cas, c'est écrit. Dans le deuxième cas, on se concentre sur ce qui est fait pas comment cela est fait.

              mais, je ne vais pas réécrire une fonction pour chaque option de chaque commande du shell pour ça.

              Un des principes de base de la fonction, c'est d'éviter la duplication de code en écrivant à un seul endroit ce qui est fait dans de nombreux endroits. Si les tests que tu veux faire n'ont rien en commun, c'est effectivement "inutile". En ignorant l'utilisation qui est faite de la fonction proposée, il m'apparait bien délicat de dire que faire une fonction pour un test est "inutile" (sauf à des fins de compréhension ou d'apprentissage) comme tu le prétendais dans ton commentaire initial.

              • [^] # Re: une bien belle fonction...

                Posté par  . Évalué à 2.

                Je fais subir à quelques variables les mêmes tests.

                Il m'a donc paru préférable de faire une fonction pour ça (les autres fonctions de test sont similaires). En plus, cela me permet d'apprendre les subtilités du scripting :-)

      • [^] # Re: une bien belle fonction...

        Posté par  (site Web personnel) . Évalué à 4.

        Je suis administrateur système et développeur. Je t’affirme donc en connaissance de cause que ta fonction is_empty est une idée très intelligente, et même la base d’un code maintenable sur le long terme.

        À chaque fois qu’un "développeur" prétendra qu’il peut faire la même chose en moins de lignes de codes que toi, ignore-le. Tout le monde préfère collaborer avec ceux qui privilégient un code verbeux et facile à comprendre.

        • [^] # Re: une bien belle fonction...

          Posté par  . Évalué à 0.

          oh oui, soyons moyen.

        • [^] # Re: une bien belle fonction...

          Posté par  . Évalué à 1.

          ta fonction is_empty

          Merci mais ce n'est pas la mienne.
          Je l'ai pompé depuis ce site qui a été donné en réponse à un utilisateur cherchant justement quelles sont les bonnes pratiques pour écrire du code :-)

          https://blog.seboss666.info/2020/04/programmation-defensive-en-bash/

        • [^] # Re: une bien belle fonction...

          Posté par  . Évalué à 3.

          Même si je suis tout à fait d'accord avec une utilisation intense des fonctions.

          J’emmétrai juste quelques observations :

          Il me semble que plus il y a de lignes de code, plus il y a de risques de bugs.

          D'une autre part, le principe de base de la programmation est d'éviter de recoder des instructions natives du langage non ?

          Mais je peux me tromper

          • [^] # Une dernière remarque

            Posté par  . Évalué à 1.

            Je m'étonne juste que les codes retour des fonctions bash ne soient pas utililisés et testé.

          • [^] # Re: une bien belle fonction...

            Posté par  . Évalué à 2.

            Salut,

            Il me semble que plus il y a de lignes de code, plus il y a de risques de bugs.

            Alors ça, c'est un beau fantasme. ;)

            Moi, je fais principalement du java, là, c'est super verbeux, mais si quelqu'un d'autre passe par derrière, si tu n'a pas "foiré" ta logique, c'est assez rapide à comprendre au final. Je fais un peu de python de temps en temps aussi. Ça peut être parfois un peu plus compact (enfin, normalement pas beaucoup plus), et parfois du R.

            Et là, ça compacte beaucoup plus si tu as changé de manière de programmer. Et c'est une autre paire de manche pour comprendre si ce n'est pas toi qui a écrit le code.

            Je n'ai jamais vu un programme trop long en termes de lignes de code. S'il y a une structure pensée, ce n'est pas le nombre de lignes qui importe. C'est de pouvoir lire, comprendre, et taper là où ça fait mal, si y'a bug.

            Matricule 23415

            • [^] # Re: une bien belle fonction...

              Posté par  . Évalué à 3.

              Je me rappelle trois conseils des mes profs d'informatique dans la rédaction d'un programme :

              -Soyez concis dans votre rédaction (programmation)
              -Faites des fonctions , procédures ou méthodes
              -Si une méthode , procédure ou une fonction fait plus de 15 lignes c'est que vous pouvez la décomposer.

              et un dernier conseil important.
              Ne réinventez pas la roue :D

              Et ça marche assez bien.

              • [^] # Re: une bien belle fonction...

                Posté par  (site Web personnel) . Évalué à 3. Dernière modification le 09/06/20 à 16:35.

                Zut, je me suis trompée de bouton en voulant répondre (ne jamais faire trois choses à la fois) et j'ai moinsé alors que ça valait largement d'être pertinenté. Désolée et pas moyen de corriger le tir.

                Bref c'est ce genre de conseil que j'ai aussi tendance à donner pour la conception de formules dans un tableur, la décomposition des étapes permet un meilleur contrôle et une meilleure évolution de la formule finale. Quand la formule est très longue, on n'y voit plus rien. Ça fait moins formule magique, mais c'est plus fiable.

                « Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.

              • [^] # Re: une bien belle fonction...

                Posté par  . Évalué à 3.

                Salut,

                Oui, c'est pas trop mal comme approche, même s'il faut savoir sortir des clous par moment. Enfin, quand c'est le moment.

                J'ai déjà eu le cas de conscience. Un petit problème de validation de données à résoudre en mode "la rache".

                J'avais déjà lu "le" truc là dessus. Donc je ne partais pas en terrain inconnu ;)

                Je pouvais partir dans plein de classes, pour expliquer tout ça, mais il fallait aller vite. Donc ça c'est fini en une regexp one-liner. Et un super gros commentaire au dessus : Touchez pas à cette ligne en dessous !

                Parfois, chercher la concision (ou le nombre minimal de ligne), ce n'est pas la bonne approche. Parce que personne d'autre ne comprendra.

                Matricule 23415

                • [^] # Re: une bien belle fonction...

                  Posté par  . Évalué à 2. Dernière modification le 09/06/20 à 23:33.

                  Je dis simplement qu'utiliser cette fonction

                  end_script()
                  {
                  local args
                  local exit_code
                  local reason
                  args="$#"
                  exit_code="$1"
                  reason="$2"
                  [ "$args" = 2 ] \
                  && printf "%s\n" "Error : $reason"
                  printf "%s\n\n" "End of the script."
                  exit "$exit_code"
                  }

                  is_empty()
                  {
                  local var
                  var="$1"
                  [ -z "$var" ]
                  }

                  is_empty "$DIR_SRC" \
                  && end_script "1" "the user variable \"DIR_SRC\" is not set."

                  plutôt que

                  [ -z "$DIR_SRC" ] \
                  && printf "%s\n%s\n" "the user variable \"DIR_SRC\" is not set." "End of the script." \
                  && exit 1

                  c'est pousser le bouchon un peu loin.

                  après le site fait de la programmation défensive :

                  structurer son code pour limiter au strict minimum les surfaces d’attaques.

                  Il faut aussi dire plus il y'a de code plus la surface d'attaque est grande.

                  Je pense qu'il faut maîtriser la sémantique d'un langage pour l'utiliser.
                  vouloir faire se ressembler tous les langages de programmation est une utopie.

                  Comme dirait Guenièvre un moment pour faire du cheval il faut faire du cheval.

                  Pour le shell et le bash en particulier c'est pareil. On peut utiliser des bonnes pratiques pour sécuriser la programmation mais il ne faut pas nier les spécificités du langage.

                  Il ne faut pas oublié non plus d'être rigoureux : il n'y a pas de mauvais langage…

                  • [^] # Re: une bien belle fonction...

                    Posté par  . Évalué à 3.

                    c'est pousser le bouchon un peu loin.

                    Même si la fonction est appelée 50 fois ?

                    • [^] # Re: une bien belle fonction...

                      Posté par  . Évalué à 2.

                      Bonne remarque :

                      bien en l’occurrence elle ne peux être appeler qu'une seule fois au vue du exit à la fin.
                      bref passons sur cet argument discutable( mais pas tant que ça).

                      Elle sera plutôt appeler à 50 emplacements différents dans le programme mais exécutée une seule fois.

                      comme je disais dans le post précédent je suis pour les fonction et dans ce cas cette méthode est très bien dans le cadre d'une programmation sur des scripts qui ont besoin d'une bonne rigueur pour éviter de construire des script non maintenable.

                      Mais il faut juste "raison garder" , si on suis jusqu'au bout le principe on fini par tout recoder en fonction.

                      et pourquoi pas

                      une fonction :

                      pour s’assurer d'avoir toujours un entier en retour

                      affecteint() {
                      local var ;
                      var="${1}"
                      var=(printf "%d" "{var}" )
                      return $var
                      }
                      affecteint 10
                      monentier=$?

                      est-ce bien judicieux?

                      • [^] # Re: une bien belle fonction...

                        Posté par  . Évalué à 2.

                        Elle sera plutôt appeler à 50 emplacements différents dans le programme mais exécutée une seule fois.

                        Tout à fait, c'est un problème de formulation de ma part mais c'est à cela que je pensais. Merci pour la correction.

                        affecteint 10
                        monentier=$?
                        

                        Effectivement, cet exemple semble peu judicieux mais je garderais cette fonction bien volontiers si elle venait valider l'input d'un utilisateur pour avoir le même comportement dans le cas d'une entrée erronée/inattendue.

                        • [^] # Re: une bien belle fonction...

                          Posté par  . Évalué à 2. Dernière modification le 10/06/20 à 13:50.

                          en shell pour avoir une réponse normalisée et sans input inattendue.

                          Le plus simple est de créer un menu ou une liste d'options définies et valide toute autre réponse doit renvoyer une erreur.

                          tant qu'a permettre une saisie encadrez la

                      • [^] # Re: une bien belle fonction...

                        Posté par  . Évalué à 3.

                        Salut,

                        Désolé de ne toujours pas être d'accord (même si je comprends tes arguments).

                        Au risque de me planter, il me semble que l'OP a indiqué dans un autre commentaire qu'il était dans une phase "découverte", ou "apprentissage", ou met le mot que tu souhaite.

                        C'est super contre-productif à ce stade (même si j'ai l'impression qu'il ne part pas de rien du tout) de bourriner sur l'optimisation dès le début à mon avis.

                        Ce n'est pas grave/gênant quand tu fais ton petit projet de commencer avec juste des approximations. Ça peut être une version qui ne verra pas la sortie parce qu'une fois les problèmes plus complexes compris, la version 2 sera peut-être déjà dans les bacs.

                        Mode 3615 MyLife : je ne compte plus le nombre de personnes que je vois arriver avec un truc du style « Mon code marche pas sur 10 millions de lignes ! A l'aide ! ». Et où là, je suis presque obligé de leur demander s'il marche sur un échantillon (parfois c'est pas moi :) ). En général, non. Le problème est structurel, pas la donnée. Donc, la réponse, c'est "fais un code qui marche sur un échantillon, déjà, on voit pour l'optimisation après".

                        Matricule 23415

                        • [^] # Re: une bien belle fonction...

                          Posté par  . Évalué à 0.

                          Pour faire une découverte, je conseille de ne pas commencer par de la programmation défensive en bash.

                          Il faut apprendre un langage dans toutes ses spécificités en commençant du général au particulier.

                          c'est pas de l'optimisation la fonction ci-dessus c'est un exemple un peu absurde et ç'est tout sauf du K.I.S.S.

                          Donc apprenez un langage en respectant sa nature.

                          On ne transforme pas du bash en python.

                          Mode 3615 MYlife : ça me rappel un garçon fan de programmation objet et qui pour un projet ou le langage imposé était le c normal pour de bonnes raison à l'époque avait pondu un système de macros pour générer des pseudo-objets et pouvoir faire de la programmation objet faisant augmenter le volume de code et la complexité du programme et posé des problèmes sur le projet.

                          
                          
                  • [^] # Re: une bien belle fonction...

                    Posté par  (site Web personnel) . Évalué à 3.

                    Forcément, ici la fonction end_script est un bricolage de la part de quelqu’un qui découvre ses outils ;)

                    Voici ce qui est finalement la même fonction après s’être frottée à quelques cas supplémentaires :

                    # display an error when a variable required by the calling function is not set
                    # USAGE: error_variable_not_set $function $variable
                    error_variable_not_set() {
                        local message function variable
                        function="$1"
                        variable="$2"
                        case "${LANG%_*}" in
                            ('fr')
                                message='La fonction "%s" ne peut pas être appelée lorsque "%s" nʼa pas de valeur définie.\n'
                            ;;
                            ('en'|*)
                                message='"%s" function can not be called when "%s" is not set.\n'
                            ;;
                        esac
                        print_error
                        printf "$message" "$function" "$variable"
                        return 1
                    }

                    source

                    # print a localized error message
                    # USAGE: print_error
                    print_error() {
                        local string
                        case "${LANG%_*}" in
                            ('fr')
                                string='Erreur :'
                            ;;
                            ('en'|*)
                                string='Error:'
                            ;;
                        esac
                        exec 1>&2
                        printf '\n\033[1;31m%s\033[0m\n' "$string"
                    }

                    source

                    • [^] # Re: une bien belle fonction...

                      Posté par  . Évalué à 2. Dernière modification le 10/06/20 à 14:09.

                      Dans le cas de tes fonctions elle apportent des fonctionnalités supplémentaires au programme.

                      Je pense que notre OP n'a pas pris la voie la plus facile :D

                      Dans le cas de la programmation Défensive

                      On structure pour éviter les débordements dus à une mauvaise maîtrise du langage.
                      Je ne dis pas que c'est mal je dis simplement qu'il ne faut pas allez trop loin jusqu'a dénaturer le langage.

                      • [^] # Re: une bien belle fonction...

                        Posté par  (site Web personnel) . Évalué à 3.

                        Je pense que notre OP n'a pas pris la voie la plus facile :D

                        Dans le cas de la programmation Défensive

                        Il va falloir que j’aille lire ça, que je ne connais pas encore mais qui commence à revenir pas mal dans la discussion. J’admets de suite que le titre de l’article ne me donne pas un a priori positif…

                        • [^] # Re: une bien belle fonction...

                          Posté par  . Évalué à 2.

                          L'article est correcte c'est juste que l'objectif est de montrer comment durcir un script pour éviter des attaques utilisants les tolérances d'un langage permissif.

                          Et pas apprendre à utiliser le bash.

                          • [^] # Re: une bien belle fonction...

                            Posté par  . Évalué à 2.

                            l'objectif est de montrer comment durcir un script pour éviter des attaques utilisants les tolérances d'un langage permissif.

                            Et pas apprendre à utiliser le bash.

                            il faut le dire bien fort, parce que
                            ls | commande
                            ou
                            commande | grep | awk
                            ce n'est pas possible.

                            c'est bizarre d'indiquer de bonnes pratiques en en utilisant de mauvaises…

                          • [^] # Re: une bien belle fonction...

                            Posté par  (site Web personnel) . Évalué à 3.

                            Avertissement : Je n’ai aucune formation académique, mes connaissances en développement en général et en shell en particulier viennent essentiellement de la lecture de pages de man. Et d’années de pratique.

                            L'article est correcte

                            Bah finalement, après lecture, je ne suis pas d’accord ;)

                            J’y vois un mélange de "bonnes pratiques" répétées sans forcément les comprendre, de conseils complètement absurdes (« Les symboles doivent être en début de ligne. », gni ??), et de mauvaise connaissance des outils (cf. la réponse de NBaH).

                            D’ailleurs je suis à peu près certain que le code donné dans les exemples ferait hurler ShellCheck, qui est étonnamment absent de ce guide.

                            • [^] # Re: une bien belle fonction...

                              Posté par  . Évalué à 2.

                              Après une seconde lecture je pense que tu as raison l'article n'est pas terrible.

                              Sinon ShellCheck est un outil que j'adore pour améliorer la sécurité des script shell.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.