BASH traitement sur l'ensemble des arguments

Bonjour,
et merci d'avance si vous pouvez me venir en aide!

J'ai besoin d'effectuer un traitement sur les arguments que je passe à un script bash.
J'ai donc à ma disposition 2 variables :
- $* qui est, si je ne me trompe pas, la concaténation de tous les arguments en un seul
GROS PROBLÈME : Si un de mes arguments contient un espace, impossible par la suite de le séparer correctement des autres ( ar1 "arg 2" arg3 => "arg1 arg 2 arg3" )
- $@ qui est, si je ne me trompe pas nom plus, une variable "spéciale" si laquelle on peut itérer avec une boucle for par exemple, et ainsi traiter un par un chaque argument ( arg, puis "arg 2", puis arg3 )
PROBLÈME : Ça ne me convient pas du tout, je ne souhaite pas itérer sur chaque argument, mais appliquer un traitement sur "l'ensemble de mes arguments", traitement incluant des espaces et des sauts de lignes.

Mes questions : Puis-je faire un traitement sur $@ sans faire boucle ? Comment sont séparés les arguments dans $@ ?

Merci

16 réponses

Résumé de la discussion

L'enjeu central est de traiter un ensemble d'arguments Bash sans les itérer et en conservant les espaces et les sauts de ligne, afin d'éviter la séparation involontaire. Plusieurs propositions s'articulent autour de $@ qui conserve les arguments séparés, contrairement à $* qui les concatène, tandis que certaines solutions jouent sur IFS, ou sur des séparateurs nuls pour éviter les erreurs. D'autres évoquent des méthodes comme printf avec \0, ou l'emploi de sorties non standard et de fichiers pour transmettre les noms, mais ces approches peuvent nécessiter des ajustements et des précautions.

Bobot (l’IA à votre service)
  1. Contributeur
    hello
    en changeant IFS et avec des ", on peut avoir un autre caractère qu'espace comme séparateur
    $ set "a 1"  "b 2" "c 3" 
    $ echo $* 
    a 1 b 2 c 3 
    $ echo "$*" 
    a 1 b 2 c 3 
    $ IFS=";" 
    $ echo "$*" 
    a 1;b 2;c 3 
    $ 
    0
    1. Merci,
      ça répond vraiment bien à ce que je cherche, je peux alors travailler avec $* sans soucis, mais...
      maintenant il me faut trouver bon séparateur.

      Mes paramètres sont des noms de fichiers, pourrais-tu me dire s'il y a un caractère ne pouvant pas faire parti des noms de fichiers, que je pourrais utiliser ?
      0
      1. Et différent de '/' , car j'ai aussi les chemins dans mes paramètres
        0
        1. Contributeur
          tout les caractères sont autorisés dans les noms, mais je dirais:
          ; puisque c'est un séparateur de commande, # commentaires, etc
          0
          1. Oui mais si j'ai un fichier avec un de ces caractères qui sont autorisés, ça ne marchera pas
            Ce serait une faille dans mon script que de faire un truc pareil.

            Du coup je ne vois pas comment faire... à part en me débrouillant en itérant dans une boucle pour reconstruire ma variable, mais c'est justement pour éviter de faire ça que je suis venu chercher de l'aide.

            $@ arrive bien à les séparer pourtant, il fait comment lui ?
            0
            1. Contributeur
              Salut,

              je ne souhaite pas itérer sur chaque argument, mais appliquer un traitement sur "l'ensemble de mes arguments", traitement incluant des espaces et des sauts de lignes.
              Pour aller plus vite et gagner du temps :
              Quels traitements comptes-tu faire ?
              De quelles façons ?

              0
              1. Contributeur
                $@ affiche l'ensemble des $1, $2, etc

                tester si le caractère est présent dans $*, si oui en prendre un autre
                $  set "a 1"  "b 2" "c;3" 
                $  
                $ echo "$*" 
                a 1 b 2 c;3 
                $  
                $ grep -q ";"  <<<"$*"
                $ echo $? 
                0 
                $  
                $ IFS="#"  # on utilise # et pas ; 
                $  
                $ echo "$*" 
                a 1#b 2#c;3 
                $  
                $  
                
                0
                1. -->Réponse à zipe31 :
                  Bah en fait peu importe le traitement, je sais que je pourrai éventuellement le faire avec une boucle mais ce n'est pas cette solution que je cherche, même si elle fonctionnerait parfaitement.

                  Pour simplifier au maximum, disons que j'ai en arguments des noms de fichiers
                  Et je souhaite en faire une liste dans une seule variable qui contiendra alors tous mes noms de fichiers séparés par un caractère.
                  Ensuite, je passe cette liste à un programme exécutable qui lui effectuera le traitement.

                  Mais je ne trouve pas comment séparer mes noms de fichiers puisque tous les caractères peuvent être utilisés.
                  Pourtant je constate que dans $@ ils semblent correctement séparés, donc ça doit être possible.

                  J'espère être suffisamment clair. N'hésitez pas à me poser des questions si ça ne l'est pas.
                  Et encore merci

                  -->À ducek :

                  Intéressant mais si dans mes arguments j'ai tous les caractères possibles de noms de fichiers, ce qui est possible, le script ne peut plus rien faire,non?

                  Cette façon de faire à l'air en plus très compliquée, tester tous les caractères possibles pour en trouver un de libre...ça fait beaucoup
                  En mettant directement Le séparateur qu'il faut (celui de $@ peut être?) ça sera quand même beaucoup plus simple et efficace
                  0
                  1. Contributeur
                    Normalement le traitement de "$@" (guillemets compris) devrait faire l'affaire.

                    $ cat plop 
                    Salut
                    
                    $ cat mon\ fich 
                    bonjour
                    
                    $ set plop "mon fich"
                    
                    $ cat $@
                    Salut
                    cat: mon: Aucun fichier ou dossier de ce type
                    cat: fich: Aucun fichier ou dossier de ce type
                    
                    $ cat "$@"
                    Salut
                    bonjour
                    
                    $ 

                    ;-))
                    0
                  2. Effectivement j'ai fait plusieurs tests avec cat et ça marche !
                    Merci
                    Mais alors pour en finir, les noms sont séparés avec quoi dans "$@" ?

                    Et puis-je effectuer des traitements de type sed sur $@ tout en conservant sa structure ?
                    0
                  3. Contributeur
                    Mais alors pour en finir, les noms sont séparés avec quoi dans "$@" ?
                    Par un caractère null.
                    0
                  4. c'est le fameux \0 ?
                    0
                  5. Il y a un moyen de le reproduire dans un script ?
                    ex : aa="fichier1\0fichier2"
                    0
                2. Rebonsoir,
                  Alors j'ai un peu creusé là dessus et mon traitement fonctionne parfaitement si je sépare mes fichiers par le caractère NUL ( '\0' )
                  J'ai pu faire les tests grâce à l'option -print0 de find qui me sépare bien les fichiers avec un '\0'

                  Maintenant il faut juste que je fasse la même chose sans find
                  Comment mettre sur la sortie de mon script la liste de ses arguments séparés par ce caractère ?

                  En effet, je dois passer cette liste à l'entrée standard de mon programme de traitement
                  J'ai essayé des choses comme : ./traitement <<<"$@" mais pas concluant

                  (Je suis ouvert à toute suggestion cette fois :] )
                  0
                  1. salut,

                    bash ne sait pas manipuler le caractère NULL.

                    pourquoi pas une boucle ?
                    pourquoi pas find ?
                    0
                    1. Je veux bien faire une boucle si au final j'obtiens ma liste
                      Quelque chose comme

                      for f in "$@"
                      do
                         liste="$liste\0$f"
                      done


                      Conviendrait parfaitement.

                      Et find ne convient pas car je ne souhaite traiter qu'une liste de fichiers précis.

                      ./script fic1 file5 "aaa bb" | traitement
                      find recherche des fichiers. dans mon cas je connais déjà les fichiers que je veux traiter. Find peut prendre ma liste et me la renvoyer au bon format avec print0 ?
                      0
                      1. Bon alors pour l'instant j'ai fait ça :

                        for f in "$@"
                        do
                        echo -n "$f"
                        echo -ne "\0"
                        done | traitement

                        Ça marche, mais c'est pas terrible je trouve, surtout quand on pense que "$@" est déjà au bon format.

                        Voilà. Qu'en pensez-vous? Comment auriez-vous fait ?
                        0
                        1. ah, j'ai oublié : pourquoi «./traitement <<<"$@" [...] pas concluant » ?
                          $ premiere() {  deuxieme "$@";}
                          $ deuxieme() { printf '%s\n' "$@";}
                          $ premiere foo bar "bar baz"
                          foo
                          bar
                          bar baz
                          là, c'est des fonctions, mais ce serait pareil avec des scripts.
                          0
                          1. oui ça ça marche mais là tu passes "$@" en argument à deuxieme()

                            Alors que j'ai besoin d'envoyer "$@" sur l'entrée standard de mon script, et là ça ne marche plus.
                            Le echo "$@" | script
                            ou script <<<"$@"
                            enlève les '\0' de "$@"
                            0
                          2. un conseil évident : réécrit le script pour qu'il accepte des arguments.
                            0
                          3. Non c'est justement ce qu'il faisait au départ.
                            Mais parfois le traitement est amené a travailler sur un grand nombre de fichiers, Et quand il y a trop de noms j'ai une erreur "argument line too long"

                            Voilà pourquoi je cherche maintenant à faire passer cette liste sur l'entrée standard. Comme ça , pas de limite, plus de problème
                            0
                          4. montre-nous le script, parce que comme ça...
                            0
                          5. Bah je vois pas ou est le problème je pense avoir donné tous les détails
                            Je n'ai plus le script ni le prog de traitement sous les yeux

                            Si vous voulez tester le même comportement:

                            #Version avec lecture de l'entrée standard
                            for f in "$@"
                            do
                            echo -n "$f"
                            echo -ne "\0"
                            done | ssh 127.0.0.1 cat

                            #Version utilisant les paramètres
                            ssh 127.0.0.1 "echo \"$@\""

                            Les 2 fonctionnent mais... faites le test avec 4000 noms de fichiers dans "$@" , vous verrez une belle erreur too many arguments
                            Si vous avez une solution pour éviter le pb tout en conservant le fonctionnement par arguments, je suis ouvert
                            Mais à mon avis avec une liste de fichiers pouvant atteindre une grosse taille, un traitement par flux dans l'entrée standard c'est mieux que la ligne de commande limitée
                            0
                        2. J'ai une autre interrogation sur le même sujet :

                          Est-il possible de récupérer avec la commande ls une liste de fichiers pour la traiter en arguments ?
                          C'est à dire : "./script $(ls)" pour obtenir la liste des fichiers dans la liste des arguments "$@" du script.
                          Ça ne marche pas avec un fichier contenant un espace, il pense qu'il y a 2 fichiers. "aa bb" donne "aa" et "bb" dans "$@"
                          En faisant ./script "$(ls)" c'est pire, il considère toute la liste de fichiers comme un seul
                          0
                          1. Contributeur
                            essayer
                            $ IFS=$'\n' ; ./script $(ls -1) 
                            # ou
                            $ ./script  *
                            
                            0
                        3. normalement
                          ./script *
                          devrait traiter tous les fichiers du répertoire courant
                          0
                          1. Effectivement le IFS=$'\n' ; peut être une bonne solution mais il le problème c'est qu'il faudra le taper à chaque fois, on ne peut pas l'automatiser ou le mettre dans le script car la "transformation" du retour du 'ls' et même le ls lui même qui renvoie un mauvais résultat(\n séparateur) se fait en amont. (ls sépare par des \n alors que c'est un caractère possible pour les noms de fichiers...dommage). Donc soit je l'utilise et je fais avec son résultat sachant à quoi m'en tenir, soit je ne l'utilise pas.

                            En tout cas mon souci premier est résolu et je vous remercie pour votre aide !
                            Merci
                            0
                            1. Contributeur
                              l'option -print0 de la commande find remplace le \n par \0 après chaque nom de fichier
                              $ find . -maxdepth 1 -print0
                              0