variable - shift bash




Comment parcourir des arguments dans un script Bash (4)

J'ai une commande complexe que je voudrais faire un script shell / bash. Je peux l'écrire en termes de $1 facilement:

foo $1 args -o $1.ext

Je veux être capable de transmettre plusieurs noms d'entrée au script. Quelle est la bonne façon de le faire?

Et, bien sûr, je veux gérer les noms de fichiers avec des espaces en eux.


Notez que la réponse de Robert est correcte, et cela fonctionne également en sh . Vous pouvez (facilement) le simplifier encore plus:

for i in "[email protected]"

est équivalent à:

for i

Autrement dit, vous n'avez besoin de rien!

Testing ( $ est l'invite de commande):

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "[email protected]"; do echo "$i"; done
a
b
spaces here
d

J'ai d'abord lu à ce sujet dans Unix Programming Environment de Kernighan et Pike.

En bash , help for documents ceci:

for NAME [in WORDS ... ;] do COMMANDS; done

Si 'in WORDS ...;' n'est pas présent, alors 'in "[email protected]"' est supposé.


Pour les cas simples, vous pouvez également utiliser shift . Il traite la liste d'arguments comme une file d'attente, chaque shift renvoie le premier argument, le nombre de chaque argument restant est décrémenté.

#this prints all arguments
while test $# -gt 0
do
    echo $1
    shift
done

Vous pouvez également y accéder en tant qu'éléments de tableau, par exemple si vous ne souhaitez pas parcourir tous les éléments.

argc=$#
argv=([email protected])

for (( j=0; j<argc; j++ )); do
    echo ${argv[j]}
done

Réécriture d'une answer supprimée par VonC .

La réponse succincte de Robert Gamble traite directement de la question. Celui-ci amplifie sur certains problèmes les noms de fichiers contenant des espaces.

Voir aussi: $ {1: + "$ @"} dans / bin / sh

Thèse de base: "[email protected]" est correct, et $* (non cité) est presque toujours faux. Cela est dû au fait que "[email protected]" fonctionne correctement lorsque les arguments contiennent des espaces et fonctionne de la même manière que $* lorsqu'ils ne le sont pas. Dans certaines circonstances, "$*" est également OK, mais "[email protected]" généralement (mais pas toujours) aux mêmes endroits. Non cité, [email protected] et $* sont équivalents (et presque toujours faux).

Alors, quelle est la différence entre $* , [email protected] , "$*" et "[email protected]" ? Ils sont tous liés à «tous les arguments de la coquille», mais ils font des choses différentes. Quand non cité, $* et [email protected] font la même chose. Ils traitent chaque «mot» (séquence de non-espace) comme un argument séparé. Les formes quotées sont cependant très différentes: "$*" traite la liste d'arguments comme une seule chaîne séparée par un espace, alors que "[email protected]" traite les arguments presque exactement comme ils le sont quand ils sont spécifiés sur la ligne de commande. "[email protected]" transforme en rien quand il n'y a pas d'arguments positionnels; "$*" transforme en une chaîne vide - et oui, il y a une différence, bien que cela puisse être difficile à percevoir. Voir plus d'informations ci-dessous, après l'introduction de la commande (non standard) al .

Thèse secondaire: si vous devez traiter des arguments avec des espaces et les transmettre ensuite à d'autres commandes, vous avez parfois besoin d'outils non standard pour vous aider. (Ou vous devriez utiliser des tableaux, avec précaution: "${array[@]}" se comporte de manière analogue à "[email protected]" .)

Exemple:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

Pourquoi ça ne marche pas? Cela ne fonctionne pas car l'interpréteur de commandes traite les citations avant d'étendre les variables. Donc, pour que le shell prenne en compte les citations incluses dans $var , vous devez utiliser eval :

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

Cela devient vraiment difficile quand vous avez des noms de fichiers tels que " He said, "Don't do this!" " (Avec des guillemets et des guillemets et des espaces).

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

Les coquilles (toutes) ne facilitent pas la manipulation de telles choses, donc (bizarrement) de nombreux programmes Unix ne les traitent pas très bien. Sous Unix, un nom de fichier (un seul composant) peut contenir tous les caractères sauf slash et NUL '\0' . Cependant, les shells n'encouragent aucun espace ou nouvelle ligne ou aucun onglet dans les noms de chemin. C'est aussi pourquoi les noms de fichiers Unix standard ne contiennent pas d'espaces, etc.

Lorsque vous traitez des noms de fichiers qui peuvent contenir des espaces et d'autres caractères gênants, vous devez être extrêmement prudent, et j'ai trouvé il y a longtemps que j'avais besoin d'un programme qui n'est pas standard sur Unix. Je l'appelle escape (la version 1.1 était datée du 1989-08-23T16: 01: 45Z).

Voici un exemple d' escape en cours d'utilisation - avec le système de contrôle SCCS. C'est un script de couverture qui fait à la fois un delta (think check-in ) et un get (think check-out ). Divers arguments, en particulier -y (la raison pour laquelle vous avez effectué le changement) contiendraient des espaces vides et des retours à la ligne. Notez que le script date de 1992, donc il utilise back-ticks au lieu de notation $(cmd ...) et n'utilise pas #!/bin/sh sur la première ligne.

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "[email protected]"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(Je n'utiliserais probablement pas l'échappement de manière aussi complète ces jours-ci - ce n'est pas nécessaire avec l'argument -e , par exemple - mais dans l'ensemble, c'est l'un de mes scripts les plus simples utilisant escape .)

Le programme d' escape sort simplement ses arguments, comme le fait echo , mais il s'assure que les arguments sont protégés pour eval (un niveau d' eval , j'ai un programme qui exécute l'exécution de shell à distance, et qui doit échapper à la sortie de escape ).

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

J'ai un autre programme appelé al qui liste ses arguments un par ligne (et c'est encore plus ancien: version 1.1 datée du 1987-01-27T14: 35: 49). C'est très utile lors du débogage de scripts, car il peut être branché sur une ligne de commande pour voir quels arguments sont réellement passés à la commande.

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[ Ajouté: Et maintenant, pour montrer la différence entre les différentes notations "[email protected]" , voici un autre exemple:

$ cat xx.sh
set -x
al [email protected]
al $*
al "$*"
al "[email protected]"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

Notez que rien ne préserve les espaces d'origine entre * et */* sur la ligne de commande. Notez également que vous pouvez modifier les 'arguments de ligne de commande' dans le shell en utilisant:

set -- -new -opt and "arg with space"

Cela définit 4 options, ' -new ', ' -opt ', ' and ', et ' arg with space '.
]

Hmm, c'est une réponse assez longue - peut-être que l' exégèse est le meilleur terme. Le code source pour l' escape disponible sur demande (email au prénom par le nom de famille sur gmail dot com). Le code source de al est incroyablement simple:

#include <stdio.h>
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

C'est tout. Il est équivalent au script test.sh que Robert Gamble a montré, et pourrait être écrit comme une fonction shell (mais les fonctions shell n'existaient pas dans la version locale de Bourne shell quand j'ai écrit al ).

Notez également que vous pouvez écrire al tant que script shell simple:

[ $# != 0 ] && printf "%s\n" "[email protected]"

Le conditionnel est nécessaire pour qu'il ne produise aucune sortie lorsqu'il n'est pas passé d'arguments. La commande printf produira une ligne vide avec seulement l'argument de chaîne de format, mais le programme C ne produit rien.





command-line