Forum Programmation.shell Problème avec getopts ?

Posté par . Licence CC by-sa
Tags :
1
24
juil.
2016

Bonjour,

Je rencontre un comportement que je n’arrive pas à m’expliquer.

Le script est interprété par /bin/sh et utilise getopts. Lorsque je fais ceci :

$ ./check_files.sh -d /tmp -td -x*a*

Mon argument est bien repris (à la fin) :

+ find /tmp/geany_socket.9912fb56 /tmp/gpg-OBxSqk /tmp/log /tmp/ssh-bfbl8kSNDM8o /tmp/systemd-private-0a769fff0c124fed8a42ec56733ade46-systemd-timesyncd.service-09Wbx1 /tmp/. /tmp/.. /tmp/.ICE-unix /tmp/.Test-unix /tmp/.X0-lock /tmp/.X11-unix /tmp/.XIM-unix /tmp/.font-unix /tmp/.xfsm-ICE-BILXKY -prune -type d ! -name *a*

Seulement si je remplace le 'a' pour un 'i' :

$ ./check_files.sh -d /tmp -td -x*i*

j’obtiens ceci :

+ find /tmp/geany_socket.9912fb56 /tmp/gpg-OBxSqk /tmp/log /tmp/ssh-bfbl8kSNDM8o /tmp/systemd-private-0a769fff0c124fed8a42ec56733ade46-systemd-timesyncd.service-09Wbx1 /tmp/. /tmp/.. /tmp/.ICE-unix /tmp/.Test-unix /tmp/.X0-lock /tmp/.X11-unix /tmp/.XIM-unix /tmp/.font-unix /tmp/.xfsm-ICE-BILXKY -prune -type d ! -name check_files.sh

pour la seule est unique raison qu’il y a un fichier contenant un 'i' dans mon répertoire courant, c’est ce nom qui est repris. J’ai bien-sûr testé avec d’autres fichiers…

Je n’ai pas ce problème si je n’ai qu’un '*', mais je voudrais pouvoir aussi chercher une chaîne en milieu de nom, ce qui est tout à fait faisable avec find.

J’ai essayé en utilisant des guillemets simples ou double mais j’ai le même comportement.

Auriez-vous une explication ?

  • # Utiliser des '\'

    Posté par (page perso) . Évalué à 2.

    Ton shell check_files.sh fait une interprétation des paramètres que tu lui passes : Quand tu écris -x*a* tu n'as aucun fichier qui matche donc il récupère *a* et quand il le ré-interprète il n'y a aucun fichier qui matche *a* et donc il utilise *a*.

    Quand tu écris -x*i* tu n'as aucun fichier qui matche donc il récupère *i*, malheureusement quand il le ré-interprète il y a check_files.sh qui matche, donc il remplace par ce nom.

    La seule solution c'est l'utilisation des '\' (et de l'aspirine…)

    Un petit exemple :

    $ i='a*'
    $ echo $i
    a2ps-4.13.tar.gz around_the_world_1.jpeg autoindex autosave
    

    Ce n'est pas du tout ce que je veux : il m'affiche tous les fichiers qui commencent par 'a' dans mon répertoire principal.

    $ i=a\\*
    $ echo $i
    a*
    

    C'est ce que je voulais…

    Sinon l'autre solution c'est d'utiliser l'option noglob

  • # Solution

    Posté par . Évalué à 2.

    find_name_clause() {
                if [ $(expr length $1) -gt 2 ]
                then
                    FIND_NAME_CLAUSE=" -name "$1
                fi
                if [ $(expr length $1) -gt 2 -a $(expr length $2) -gt 2  ]
                then
                    FIND_NAME_CLAUSE=${FIND_NAME_CLAUSE}" -a "
                fi    
                if [ $(expr length $2) -gt 2  ]
                then
                    FIND_NAME_CLAUSE=${FIND_NAME_CLAUSE}"! -name  "$2
                fi    
    }
    find_name_clause "'"${SEARCH_NAME_INCLUDE}"'" "'"${SEARCH_NAME_EXCLUDE}"'"

    Vive le shell !

  • # Chez moi ça marche

    Posté par . Évalué à 3. Dernière modification le 24/07/16 à 11:05.

    Script test_op.sh:

    #!/bin/sh -x
    while getopts "x:" option
    do
      case "$option" in
      x)
        op="$OPTARG"
        ;;
      esac
    done
    find . -name "$op"
    

    Appel:

     ./test_op.sh -x"*sh"
    

    Résultat:

    + find . -name *sh
    ./test_op.sh
    
    • [^] # Re: Chez moi ça marche

      Posté par . Évalué à 2.

      Oui. C’est à un autre endroit dans mon script que je me prenais les pieds dans le globbing, getopts n’y est définitivement pour rien.

  • # Une petite réponse à moi-même

    Posté par . Évalué à 3. Dernière modification le 24/07/16 à 16:37.

    Alors du coup je pense que c’est ni élégant, ni académique, ni efficient mais je m’en sort comme ça :

    # Do find
    do_find() {
    cat <<EOF
    find $* 2>/dev/null
    EOF
    }

    La redirection de 2 vers /dev/null est bien entendu optionnelle mais pour l’instant je ne traite pas les fichiers inaccessibles.

    J’utilise la fonction ainsi :

    NEWER_FILES_NB=$(eval $(do_find $1 ${FIND_TYPE_CLAUSE} ${FIND_NAME_CLAUSE} -mmin -${MIN_AGE}) |wc -l)

    ou encore :

        format="-printf '%s;%Cc;%p;%k kB;%Y\n'"
        firstlast=$(eval $(do_find $search ${FIND_TYPE_CLAUSE} ${FIND_NAME_CLAUSE} ${format}) |sort -n |awk 'BEGIN{FS=";"} {if (NR==1) print "(" $5 ")" $3 " (" $4 ") " $2} END{print "(" $5 ")" $3 " (" $4 ") " $2}')

    (firstlast est un nom de variable très mal choisi, il s’agit en fait de la liste des fichiers sous forme de CSV)

    Le dernier exemple fonctionne avec le find de GNU mais pas avec toutes les implémentations de find.

    Avec cette méthode je dois échapper les caractères '(' et '!' pour find par contre. Enfin quand je dis je dois… pour les parenthèses oui mais le '!' passe, qu’il soit échappé ou pas… (vu que j’ai le choix je l’échappe)

    La prochaine fois que je me dis, tiens et si je démarrais un projet en shell ? J’y réfléchirai à deux fois :)

    Merci à tous pour vos réponses qui m’ont mis sur la voie.

Suivre le flux des commentaires

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