Forum Programmation.shell Bash, ksh, POSIX et compatibilité

Posté par  . Licence CC By‑SA.
Étiquettes :
4
11
juil.
2016

Bonjour,

J’étais en train d’écrire un script pour AIX en Korn shell et étant donné que c’est un script très simple je me suis dit : « tiens mais pourquoi pas rendre mon script compatible également Linux ? » et là je tombe sur un os… :/

Même avec un shell compatible POSIX de chaque côté (#!/usr/bin/env sh), je me rends compte que la commande echo ne fonctionne pas de la même manière sur AIX et Linux :(

Sous AIX les caractères tels que \n, \t, etc… sont bien interprétés. Sous Linux il faut pour ça utiliser l’option -e de echo, option qui n’existe pas sous AIX (ce serait trop simple).

Je subodore qu’il faut utiliser printf plutôt que echo mais avec printf je ne peux pas, à ma connaissance, faire ce genre de truc :

export HELP="\n\
### check_files.sh #####################################################################\n\
#                                                                                      #\n\
# Checks if presence of files in a directory on AIX.                                   #\n\
#                                                                                      #\n\
# Arguments :                                                                          #\n\
#                                                                                      #\n\
#   -d <path>       Directory to search files in.                                      #\n\
#   -a <int>        Search files older than this parameter, in minutes.                #\n\
#   -s <int>        Size of each file must be superior to this value, in bytes         #\n\
#   -n <int>        Maximum number of files                                            #\n\
#   -h              Show this help.                                                    #\n\
#                                                                                      #\n\
########################################################################################\n\
\n";

Si j’ai le code ci-dessus, pour que l’affichage soit correct sous AIX il faut faire echo "$HELP" alors que sous Linux je devrais faire echo -e "$HELP". Si je mets l’option -e sous AIX ça va m’afficher ce '-e' dans la sortie.

Est-ce que vous voyez une solution à laquelle je n’aurais pas pensé ?

  • # Contournement

    Posté par  . Évalué à 3.

    Hello,

    En utilisant un variable ECHO :

    [ uname -a| grep -ci linux -gt 0 ] && ECHO="echo -e " || ECHO="echo "

    Puis

    $ECHO $HELP

    Cdlt

    • [^] # Re: Contournement

      Posté par  . Évalué à 4.

      Encore mieux que de déduire à partir du nom du système, c'est de tester la feature directement. Genre

      [ "x`echo -e`" = "x-e" ]
      # ou encore
      NL="
      "
      [ "x$NL" = "x`echo \n`"]

      PS: au lieu de terminer les lignes avec \n\, il suffit de ne rien mettre, faire du POSIX c'est aussi apprendre à ne pas se compliquer la vie ;-)
      PS2: Un cat <<HEREDOC marcherait très bien aussi…

      • [^] # Re: Contournement

        Posté par  . Évalué à 2.

        PS3: Si on veut vraiment utiliser uname (mais pourquoi?) alors au moins ne matcher que sur le "-s". "-a" inclue le hostname et plein d'infos superflues, ton code risque ne marchera plus si ton hostname contient "linux" (les gens qui mandatent d'utiliser AIX suivent souvent d'autres conventions étranges).

      • [^] # Re: Contournement

        Posté par  . Évalué à 1.

        PS4: pourquoi faire un "export" de la variable ? pourquoi printf ne marcherait pas ? Attention aussi à longueur maximale d'une ligne de commande qui est plus restreinte sur AIX que sur linux, un HEREDOC serait vraiment plus approprié en fait. Le ';' n'a rien à faire là non plus.

        • [^] # Re: Contournement

          Posté par  . Évalué à 1.

          longueur maximale d'une ligne de commande qui est plus restreinte sur AIX

          ça fait bien longtemps que cette limitation n'existe plus….

          c'est le paramètre ncargs.

          Pour visualiser la valeur : lsattr -El sys0 -a ncargs
          On peut le changer à chaud mais ça fait bien longtemps que la valeur par défaut "should be enough for everyone"

          • [^] # Re: Contournement

            Posté par  . Évalué à 0.

            Il y a une différence entre ne plus exister et devrait être suffisante. Le jour où il lancera son script avec set -x, il va bien rigoler aussi d'avoir mis toute son aide dans un argument de commande… 'fin bon je dis ça…

            • [^] # Re: Contournement

              Posté par  . Évalué à 2.

              il va bien rigoler aussi d'avoir mis toute son aide dans un argument de commande… 'fin bon je dis ça…

              Quand je fais echo "toto tatat titi" ça compte trois arguments ?

              De plus je ne comprends pas « dans un argument de commande ». J’ai mis l’aide dans une variable, cette variable est "lue" si l’argument de la commande est "-h"… Tu peux m’expliquer ?

              • [^] # Re: Contournement

                Posté par  . Évalué à 1.

                Ton shell fais l'expansion de variables sur la commande afin de construire un ARGV. Je ne crois pas que même pour une builtin (echo/printf), il fonctionne de manière différente, ça ne serait plus posix pour le coût je suppose. Bref, le contenu de ta variable se retrouve expansé dans un buffer qui sert de ARGV, buffer qui est limité. Autant utiliser la sortie de cat directement, éventuellement à travers un autre programme en utilisant un pipe ("|") si tu dois réellement faire un traitement dessus avant de l'afficher.
                Essaye avec "set -x" dans ton script, tu vas vite voir la différence.

              • [^] # Re: Contournement

                Posté par  . Évalué à 1. Dernière modification le 12 juillet 2016 à 14:51.

                De plus je ne comprends pas « dans un argument de commande ». J’ai mis l’aide dans une variable, cette variable est "lue" si l’argument de la commande est "-h"… Tu peux m’expliquer ?

                Argument de la commande "echo" bien sûr. Qui se retrouve dans la sortie de debug quand tu actives set -x, sortie qui deviendrait largement moins lisible pour le coups.

              • [^] # Re: Contournement

                Posté par  (site web personnel) . Évalué à 2.

                Quand je fais echo "toto tatat titi" ça compte trois arguments ?

                Non un seul. Si tu programmes en shell, il faut absolument clarifier le remplacement des variables, et les règles de lecture des lignes, sans quoi tu vas constamment te créer des difficultés inutiles (du type triple échappement croisé des caractères de fin de ligne comme dans l'exemple du début ;) ).

                Le petit programme ci-dessous te permet d'expérimenter pour savoir ce que reçoit une fonction ou un programme comme vecteur d'arguments:

                argv_dump()
                {
                   local index
                   index=0
                
                   while [ $# -gt 0 ]; do
                       index=$(( ${index} + 1 ))
                       printf 'argv_dump: %d: %s\n' "${index}" "$1"
                     shift
                   done
                }
                
                argv_dump "$@"
                
          • [^] # Re: Contournement

            Posté par  . Évalué à 2. Dernière modification le 12 juillet 2016 à 16:26.

            ça fait bien longtemps que cette limitation n'existe plus….

            Primo: c'est faux, tu l'écris toi-même .

            Deuxio: si IBM a augmenté le défaut, je parie que c'est pour éviter de "casser" des scripts qui "fonctionnent" sous linux. J'imagine du coups qu'ils ont aussi augmenté la taille de la stack par défaut, ai-je bon ? Pour la même raison qu'il y a des buffers alloués plus grands, histoire d'éviter de "casser" tous ces programmes qui font des "petis" buffer-overflow. De là à considérer que ces programmes soient techniquement corrects, voire se comportent correctement, il y a un pas que je ne franchis pas. Autant ne pas emprunter cette route du "si c'est pas assez, on augmente puis on croise les doigts".

            Tersio: Pourquoi cette limitation existe en premier lieu, i.e. pourquoi ça n'est pas complètement dynamique ? Par ce que cela a un cout, non ? À toi de me montrer qu'augmenter le défaut n'engendre pas un coût supplémentaire… (avec un peu de chance, c'est documenté par IBM). En d'autres mots, en augmentant le défaut, tout le monde paye pour les quelques utilisateurs qui programment n'importe comment… C'est pour ça que je préfère avoir un défaut plus petit, et en bonus on se rend plus vite compte que l'on fait n'importe quoi.

            BTW: AIX c'est pas aussi l'OS qui mappe qq chose à l'adresse NULL ?

            • [^] # Re: Contournement

              Posté par  . Évalué à 1.

              Deuxio: si IBM a augmenté le défaut, je parie que c'est pour éviter de "casser" des scripts qui "fonctionnent" sous linux.

              La limitation historique (avant AIX 6.1) méritait quand même d'être retouchée.

              J'imagine du coups qu'ils ont aussi augmenté la taille de la stack par défaut, ai-je bon ?

              Non je n'ai pas remarqué que la taille de la stack kernel par défaut a augmentéen AIX 6.1, même si je n'ai pas fait de recherche très approfondies. En tous cas ce n'est pas écrit dans le redbook "aix 6.1 diff guide".

              Pour la même raison qu'il y a des buffers alloués plus grands, histoire d'éviter de "casser" tous ces programmes qui font des "petis" buffer-overflow. De là à considérer que ces programmes soient techniquement corrects, voire se comportent correctement, il y a un pas que je ne franchis pas. Autant ne pas emprunter cette route du "si c'est pas assez, on augmente puis on croise les doigts".

              Plus simplement, IBM a implémenté ça la SED (Stack Execution Disable protection)

              https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.security/stack_exec_disable.htm

              Je pense que ça devrait répondre à ta question…

              Tersio: Pourquoi cette limitation existe en premier lieu, i.e. pourquoi ça n'est pas complètement dynamique ? Par ce que cela a un cout, non ?

              Sous Linux aussi cette limitation avait cours jusqu'en 2.6.22 (ce qui n'est pas si loin que ça) :

              http://www.in-ulm.de/~mascheck/various/argmax/

              Depuis AIX 6.1 (2007), le défaut est désormais 4096 * 256 (1 048 576) mais on peut aller jusqu'à 4096 * 1024 (4 194 304).
              Côté Linux, à peu près à la même époque, ils ont préféré faire autrement, aller jusqu'à 1/4 de la Stack.

              Et sinon évidemment oui que cela a un coût de toucher au noyau.
              Et comme AIX n'est pas open source, ce genre d'initiative ne peut provenir que de l'éditeur.

              C'est pour ça que je préfère avoir un défaut plus petit, et en bonus on se rend plus vite compte que l'on fait n'importe quoi.

              Donc à l'ère du 64 bits, tu préfères revenir à l'âge de pierre et avoir une limitation forte sur la taille des arguments ?
              Je trouve ça bien que le défaut ait augmenté. Cela ne veut pas dire qu'on fait forcément n'importe quoi. Il y a toujours une limite, mais cette fois beaucoup plus grande.

              • [^] # Re: Contournement

                Posté par  . Évalué à 2.

                Ok merci pour cette réponse et ton lien factuel.

                Donc à l'ère du 64 bits, tu préfères revenir à l'âge de pierre et avoir une limitation forte sur la taille des arguments ?

                Oui car je ne suis pas d'accord avec ta proposition suivante. Au passage, 4MO=22bits d'adressage, ça reste rien du tout hein ;-) Si tu passes 4MO, ben pourquoi pas à 8 et pourquoi pas à 16, etc. ?

                Cela ne veut pas dire qu'on fait forcément n'importe quoi. Il y a toujours une limite, mais cette fois beaucoup plus grande.

                Si car en ce faisant 1) tu deviens de-facto non portable (cf ton lien!) 2) l'argv n'a jamais été prévu pour transmettre plus que le stricte minimum entre les processus. Un exemple de déficience: il n'y a pas moyen libérer cette mémoire. Corolaire du 2) unix propose d'autres moyens—portables, optimaux et efficaces—pour transmettre de l'information entre processus. Pourquoi le faire alors si ce n'est par fainéantise ?

                (Je me permets de dire fainéantise car j'ai déjà "pêché" moi aussi… rétrospectivement j'aurais du mettre en place un meilleur design tout de suite, 'fin bon j'étais jeune et sous pression. tant pis pour mon employeur qui finalement a perdu plus que moi dans l'histoire: moi j'ai gardé la leçon, lui le code pourri :p).

        • [^] # Re: Contournement

          Posté par  . Évalué à 2.

          Oui l’export sert à rien ici. Le ';' non plus mais j’aime bien le mettre quand même par endroit :) Pour printf j’ai répondu plus bas.

          Je vais tester la méthode donnée par Michaël plus bas (plutôt qu’une commande personnalisée dans une variable), je suppose que c’est ça qu’on appelle un HEREDOC.

  • # printf

    Posté par  . Évalué à 2.

    Pourquoi l'utilisation de printf n'est elle pas possible ?

    Je n'ai pas d'AIX sous la main pour tester mais sous linux un script avec les lignes suivantes affiche correctement le texte sous bash :

    #!/bin/sh
    export HELP="\n\
    ### check_files.sh #####################################################################\n\
    #                                                                                      #\n\
    # Checks if presence of files in a directory on AIX.                                   #\n\
    #                                                                                      #\n\
    # Arguments :                                                                          #\n\
    #                                                                                      #\n\
    #   -d <path>       Directory to search files in.                                      #\n\
    #   -a <int>        Search files older than this parameter, in minutes.                #\n\
    #   -s <int>        Size of each file must be superior to this value, in bytes         #\n\
    #   -n <int>        Maximum number of files                                            #\n\
    #   -h              Show this help.                                                    #\n\
    #                                                                                      #\n\
    ########################################################################################\n\
    \n";
    
    
    printf "$HELP"
    • [^] # Re: printf

      Posté par  . Évalué à 2.

      Oui grave… en fait il suffit de ne pas spécifier de format, ou plutôt, utiliser directement la variable comme template pour printf :) J’aurais dû y penser…

      printf "$HELP"

      • [^] # Re: printf

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

        C'est affreux.

        Pour le question : qu'utiliser puisque echo n'est pas portable, la réponse est bien printf.

        En revanche, pour cet usage là, ce n'est pas la meilleure solution, mieux veut utiliser un cat, comme mentionné plus bas.

  • # Tu cherches les ennuis :)

    Posté par  (site web personnel) . Évalué à 7. Dernière modification le 12 juillet 2016 à 11:46.

    C'est beaucoup plus facile de faire comme ça:

    help_message()
    {
      cat <<EOF
    ### check_files.sh #####################################################################
    #                                                                                      #
    # Checks if presence of files in a directory on AIX.                                   #
    #                                                                                      #
    # Arguments :                                                                          #
    #                                                                                      #
    #   -d <path>       Directory to search files in.                                      #
    #   -a <int>        Search files older than this parameter, in minutes.                #
    #   -s <int>        Size of each file must be superior to this value, in bytes         #
    #   -n <int>        Maximum number of files                                            #
    #   -h              Show this help.                                                    #
    #                                                                                      #
    ########################################################################################
    EOF
    }
    
    export HELP="$(help_message)"

    À noter que les variables sont remplacées comme avec les "" – ce qui permet d'afficher les valeurs par défaut. À noter que tu peux en profiter pour gérer ton aide en utf-8 en utilisant iconv à la place de cat (avec iconv -c -f utf-8) ou faire plein d'autres choses avec sed ou awk par exemple, ou ton filtre préféré.

    L'affichage portable d'une chaîne quelconque se fait avec printf '%s' "${HELP}".

    PS: Sinon tu peux aussi planquer des fichiers après le exit de ton programme et les extraire en utilisant les sélecteurs awk '/DELIM1/,/DELIM2/' mais je trouve ça moins propre au niveau de l'organisation logicielle. Par contre pour afficher l'aide d'un Makefile, cette astuce est très appréciable.

    • [^] # Re: Tu cherches les ennuis :)

      Posté par  . Évalué à 2. Dernière modification le 12 juillet 2016 à 14:00.

      À noter que les variables sont remplacées comme avec les ""

      Note pour la complétude: cela dépend en fait, il y a moyen de désactiver l'interpolation de variables tout comme il y a aussi moyen de supprimer une indentation.

      PS: Je ne suis toujours pas convaincu que mettre tout dans une variable soit une si bonne idée. Cf mes remarques ci-dessus.

      • [^] # Re: Tu cherches les ennuis :)

        Posté par  . Évalué à 2. Dernière modification le 12 juillet 2016 à 14:08.

        PS2:Surtout si on l'"export"e après… que je sache le coût de stocker ces variables sera payé par tous les programmes jamais lancés par le script et leurs descendants… Bon après on va encore me dire "mais si ça marche, etc…" mais bon avec une mentalité pareille, on fini vite par construire des usines à gaz.

        • [^] # Re: Tu cherches les ennuis :)

          Posté par  . Évalué à 2.

          Je vais probablement virer ces export car ces variables ne sont pas destinées à être utilisée dans un sous processus. Tu as raison.

      • [^] # Re: Tu cherches les ennuis :)

        Posté par  (site web personnel) . Évalué à 2. Dernière modification le 12 juillet 2016 à 14:50.

        Note pour la complétude: cela dépend en fait, il y a moyen de désactiver l'interpolation de variables

        Comme ça:

        cat <<'EOF'
        ${do_not_expand_me}
        EOF
        
      • [^] # Re: Tu cherches les ennuis :)

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

        PS: Je ne suis toujours pas convaincu que mettre tout dans une variable soit une si bonne idée. Cf mes remarques ci-dessus.

        À mon avis aussi c'est plutôt une mauvaise idée, j'ai juste reproduit la structure du programme existant. Normalement dans les variables du shell on ne stocke que des “objets du monde Unix” comme des noms de fichiers, des PID etc. mais tout ce qui fait l'objet de calculs se place dans des fichiers ou se balade entre les pipes d'une programme. <Mode ironie> Lire ce fabuleux article sur mon fabuleux blog :) http://michipili.github.io/shell/2015/04/12/shell-filter-programming.html

    • [^] # Re: Tu cherches les ennuis :)

      Posté par  . Évalué à 2.

      Merci ! Je vais tester ça. Mon problème avec printf c’est qu’en faisant printf '%s' "toto\ntiti" ça n’interprète pas le "\n", mais avec ta méthode ça devrait marcher. Je testerai ça seulement vendredi par contre.

      En plus c’est cool, ça évite de coller explicitement des "\n\" à toutes les fins de ligne du texte d’aide, c’est plus joli :)

      • [^] # Re: Tu cherches les ennuis :)

        Posté par  . Évalué à 3. Dernière modification le 12 juillet 2016 à 14:23.

        avec ta méthode ça devrait marcher

        <Mode jaloux> méthode mentionnée dans mon premier commentaire :p </>

        ça évite de coller explicitement des "\n\" à toutes les fins de ligne

        Cela n'était pas nécessaire avec ta méthode non plus (aussi dans mon premier commentaire… :p).

Suivre le flux des commentaires

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