Imprimer les arguments du shell dans l'ordre inverse

12

Je suis un peu coincé. Ma tâche consiste à imprimer les arguments de mon script dans l'ordre inverse, à l'exception des troisième et quatrième.

Ce que j'ai c'est ce code:

#!/bin/bash

i=$#
for arg in "$@"
do
    case $i
    in
        3) ;;
        4) ;;
        *) eval echo "$i. Parameter: \$$i";;
    esac
    i=`expr $i - 1`
done

Comme je déteste eval (salutations à PHP), je cherche une solution sans mais je ne trouve pas de solution.

Comment définir dynamiquement la position de l'argument?

PS: Non ce n'est pas un devoir, j'apprends le shell pour un examen donc j'essaie de résoudre les anciens examens.

WarrenFaith
la source

Réponses:

13

evalest le seul moyen portable d'accéder à un paramètre positionnel par sa position choisie dynamiquement. Votre script serait plus clair si vous boucliez explicitement sur l'index plutôt que sur les valeurs (que vous n'utilisez pas). Notez que vous n'en avez pas besoin à exprmoins que vous ne souhaitiez que votre script s'exécute dans des shells Bourne antiques; $((…))l'arithmétique est en POSIX. Limitez l'utilisation de evalau plus petit fragment possible; par exemple, n'utilisez pas eval echo, affectez la valeur à une variable temporaire.

i=$#
while [ "$i" -gt 0 ]; do
  if [ "$i" -ne 3 ] && [ "$i" -ne 2 ]; then
    eval "value=\${$i}"
    echo "Parameter $i is $value"
  fi
  i=$((i-1))
done

En bash, vous pouvez utiliser ${!i}pour signifier la valeur du paramètre dont le nom est $i. Cela fonctionne quand $iest soit un paramètre nommé, soit un nombre (indiquant un paramètre positionnel). Pendant que vous y êtes, vous pouvez utiliser d'autres fonctionnalités pratiques de bash.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done
Gilles 'SO- arrête d'être méchant'
la source
Je ne peux pas les utiliser argcar ils sont commandés correctement et non en sens inverse. À l'usage de expr, je suis limité à utiliser la norme uniquement.
WarrenFaith
1
@WarrenFaith Si votre script commence par #!/bin/bash, vous pouvez utiliser ${!i}et (()). Si vous voulez vous en tenir à sh standard , ceux-ci ne sont pas disponibles, mais ils le $((…))sont.
Gilles 'SO- arrête d'être méchant'
Ok, je pense que je peux travailler avec ça :)
WarrenFaith
Notez que les coquilles Bourne antiques ne pourraient pas accéder aux paramètres de position au-delà de 9 $ de toute façon.
Stéphane Chazelas
1
evaln'est pas le seul moyen portable comme Ryan l'a montré .
Stéphane Chazelas
7

Je garde un script reversesur mon chemin qui fait ceci:

#!/bin/sh

if [ "$#" -gt 0 ]; then
    arg=$1
    shift
    reverse "$@"
    printf '%s\n' "$arg"
fi

Exemple d'utilisation:

$ reverse a b c '*' '-n'
-n
*
c
b
a

Vous pouvez également utiliser une fonction au lieu d'un script dédié.

Ryan Williams
la source
Notez que l'une (contrairement aux autres réponses publiées jusqu'à présent) fonctionnerait également dans le shell Bourne (pas qu'il y ait de raison d'utiliser ce shell de nos jours)
Stéphane Chazelas
@ StéphaneChazelas: Pourquoi citer $#? Peut-il être autre chose qu'un entier non négatif?
G-Man dit `` Réintègre Monica '' le
2
@ G-Man, laisser une variable sans guillemets dans le contexte de la liste appelle l'opérateur split + glob. Il n'y a aucune raison pour que vous souhaitiez l'invoquer ici. Cela n'a rien à voir avec le contenu de la variable. Voir aussi unix.stackexchange.com/a/171347 vers la fin. Notez également que certains shells (tiret, chic par exemple) héritent toujours IFS de l'environnement.
Stéphane Chazelas
J'ai trouvé que, sur Android, je devais déclarerlocal arg=$1
starfry
2

Il s'agit d'une utilisation correcte et non dangereuse d'eval. Vous contrôlez entièrement le contenu que vous evalutilisez.

Si cela vous donne toujours de mauvais sentiments, alors si vous ne vous souciez pas de la portabilité, vous pouvez utiliser la ${!i}syntaxe d'indirection de Bash .

Shawn J. Goff
la source
${!i}ne fait donc pas partie de la syntaxe standard?
WarrenFaith
Non, c'est un bashisme. Voici les extensions de paramètres POSIX: pubs.opengroup.org/onlinepubs/009695399/utilities/…
Shawn J. Goff
2

En supposant que les paramètres positionnels ne contiennent pas de caractères de nouvelle ligne:

[ "$#" -gt 0 ] && printf '%s\n' "$@" | #print in order
sed '3,4 d' |                          #skip 3 and 4
tac                                    #reverse (try tail -r on
                                       #non-GNU systems).

Tester:

set 1 2 3 4 5 6
printf '%s\n' "$@" | 
sed '3,4 d' |
tac

Sortie de test:

6
5
2
1
PSkocik
la source
1

Avec zsh:

$ set a 'foo bar' c '' '*'
$ printf '<%s>\n' "${(Oa)@}"
<*>
<>
<c>
<foo bar>
<a>

Oaest un indicateur d'extension de paramètre pour trier les éléments du tableau lors de l'expansion dans les indices de tableau inversés .

Pour exclure 3 et 4:

$ printf '<%s>\n' "${(Oa)@[5,-1]}" "${(Oa)@[1,2]}"
<*>
<foo bar>
<a>
Stéphane Chazelas
la source
0

Avec pratiquement n'importe quel shell:

printf '{ PS4=\${$(($#-$x))}; } 2>&3; 2>&1\n%.0s' |
x=LINENO+1 sh -sx "$@" 3>/dev/null

Et vous n'avez pas besoin d'utiliser de sous-coquilles. Par exemple:

set -x a b c
{ last= PS4=\${last:=\${$#}}; set +x; } 2>/dev/null
echo "$last"

... impressions ...

c

Et voici une fonction shell qui peut définir aliaspour vous un shell qui affichera les arguments en avant ou en arrière:

tofro() case $1 in (*[!0-9]*|'') ! :;;(*) set "$1"
        until   [ "$1" -eq "$(($#-1))" ]    &&
                shift && alias args=":; printf \
            \"%.\$((\$??\${#*}:0))s%.\$((!\$??\${#*}:0))s\n\" $* "
        do      [ "$#" -gt 1 ] &&
                set "$@ \"\${$#}\" " '"${'"$((1+$1-$#))"'}"' ||
                set "$1" '"$1" "${'"$1"'}"'
        done;   esac

Il ne tente pas de stocker les valeurs littérales des arguments, mais il place plutôt une chaîne comme celle-ci dans args alias:

:;printf    "%.$(($??${#*}:0))s%.$((!$??${#*}:0))s\n" \
            "$1" "${3}" "${2}"  "${2}" "${3}"  "${1}"

... et ne stocke donc que les références aux paramètres en avant et en arrière. Il stockera jusqu'à un décompte tel qu'il lui est donné comme argument. Et donc ce qui précède a aliasété généré comme:

tofro 3

printfLe comportement de 'est affecté en fonction de la valeur de retour de la commande précédente - qui est toujours :la commande null, et donc généralement true. printfsautera la moitié de ses arguments à chaque impression - ce qui, par défaut, affichera les arguments du plus petit au plus grand. Cependant, si vous le faites simplement:

! args

... il les imprime à l'envers.

Étant donné que l'alias ne stocke aucune valeur littérale, sa valeur reste statique pendant que les arguments réels peuvent changer, mais il en référencera toujours autant qu'il le pourrait. Par exemple:

set one two three
tofro 3
args; ! args
shift; args; ! args

... qui imprime ...

one
two
three
three
two
one
two
three


three
two

Mais la réinitialisation de l'alias peut se faire comme:

tofro 2
args; ! args

... et donc ça imprime ...

two
three
three
two
mikeserv
la source
-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done
user119141
la source
2
aucune explication du tout, sympa. Downvote de moi. Expliquez ce que fait votre code et je pourrais changer d'avis.
WarrenFaith
3
Peut être simplifié àfor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas
Suis-je en train d'halluciner? Je pensais avoir vu des mots dans la question qui disaient "imprimez les arguments ... sauf les troisième et quatrième".
G-Man dit `` Réintègre Monica '' le
1
@ G-Man, le titre dit "imprimer les arguments en sens inverse" auquel cela répond sans utiliser eval. Exclure 3 et 4 de cela est trivial. Je ne pense pas que les downvotes soient justifiés. Il est également explicite (bien que, comme je l'ai dit, il puisse être beaucoup simplifié).
Stéphane Chazelas