Quel est le but d'utiliser shift dans les scripts shell?

118

Je suis tombé sur ce script:

#! /bin/bash                                                                                                                                                                                           

if (( $# < 3 )); then
  echo "$0 old_string new_string file [file...]"
  exit 0
else
  ostr="$1"; shift
  nstr="$1"; shift  
fi

echo "Replacing \"$ostr\" with \"$nstr\""
for file in $@; do
  if [ -f $file ]; then
    echo "Working with: $file"
    eval "sed 's/"$ostr"/"$nstr"/g' $file" > $file.tmp 
    mv $file.tmp $file
  fi  
done

Quelle est la signification des lignes où ils utilisent shift? Je présume que le script devrait être utilisé avec au moins des arguments, alors ...?

Patryk
la source

Réponses:

141

shiftest un type bashintégré qui supprime les arguments du début de la liste des arguments. Étant donné que les 3 arguments fournis au script sont disponibles dans $1, $2, $3puis un appel à shift fera $2le nouveau $1. Un shift 2va changer par deux faisant neuf $1l'ancien $3. Pour plus d'informations, voir ici:

humanitéet paix
la source
25
+1 Notez que ce n'est pas les faire pivoter , mais les décaler du tableau (des arguments). Shift / unshift et pop / push sont des noms communs pour ce type général de manipulation (voir, par exemple, bash pushdet popd).
goldilocks
1
La référence définitive: gnu.org/software/bash/manual/bashref.html#index-shift
glenn jackman
@ Goldlocks très vrai. Au lieu de "faire pivoter", j'aurais mieux fait de trouver un moyen d'utiliser un autre mot, par exemple "déplacer les arguments dans les variables d'argument". Quoi qu’il en soit, j’espère que les exemples cités dans le texte et le lien vers la référence l’auront néanmoins clarifié.
humanite et
Bien que le script mentionne bash, la question concerne tous les obus. C'est pourquoi le standard Posix prévaut: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
calandoa Le
32

Comme le commentaire et les références de l' humanité de Goldilocks décrivent, shiftréaffecte les paramètres de position ( $1, $2, etc.) de sorte que $1prend l'ancienne valeur $2, $2prend la valeur $3, etc. *   L'ancienne valeur $1est mis au rebut. ( $0n'est pas modifié.) Voici quelques raisons pour cela:

  • Il vous permet d’accéder plus facilement au dixième argument (s’il en existe un).  $10ne fonctionne pas - il est interprété comme $1concaténé avec un 0 (et peut donc produire quelque chose comme Hello0). Après un shift, le dixième argument devient $9. (Cependant, dans la plupart des coquillages modernes, vous pouvez utiliser ${10}.)
  • Comme le montre le Bash Guide for Beginners , il peut être utilisé pour parcourir les arguments. IMNSHO, c'est maladroit; forc'est bien mieux pour ça.
  • Comme dans votre exemple de script, il est facile de traiter tous les arguments de la même manière, à l'exception de quelques-uns. Par exemple, dans votre script, $1et $2sont des chaînes de texte, alors $3que tous les autres paramètres sont des noms de fichier.

Alors, voici comment ça se passe. Supposons que votre script s'appelle Patryk_scriptet s'appelle comme

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

Le script voit

$1 = USSR
$2 = Russia
$3 = Treaty1
$4 = Atlas2
$5 = Pravda3

L'instruction ostr="$1"définit variable ostrà USSR. La première shiftinstruction modifie les paramètres de position comme suit:

$1 = Russia
$2 = Treaty1
$3 = Atlas2
$4 = Pravda3

L'instruction nstr="$1"définit variable nstrà Russia. La deuxième shiftinstruction modifie les paramètres de position comme suit:

$1 = Treaty1
$2 = Atlas2
$3 = Pravda3

Et puis les forchangements en boucle USSR( $ostr) à Russia( $nstr) dans les fichiers Treaty1, Atlas2et Pravda3.



Il y a quelques problèmes avec le script.

  1. pour le fichier dans $ @; faire

    Si le script est appelé en tant que

    Patryk_script USSR Russia Treaty1 "World Atlas2" Pravda3

    il voit

    1 $ = URSS
    2 $ = la Russie
    3 $ = Traité1
    4 $ = World Atlas2
    5 $ = Pravda3

    mais, parce que $@n'est pas cité, l'espace World Atlas2n'est pas cité, et la forboucle pense qu'il a quatre fichiers: Treaty1, World, Atlas2et Pravda3. Cela devrait être soit

    pour le fichier dans "$ @"; faire

    (pour citer des caractères spéciaux dans les arguments) ou simplement

    pour le fichier faire

    (ce qui équivaut à la version longue).

  2. eval "sed 's /" $ ostr "/" $ nstr "/ g' $ fichier"

    Il n'est pas nécessaire que cela soit un evalet il evalpeut être dangereux de passer une entrée utilisateur non contrôlée à un . Par exemple, si le script est appelé en tant que

    Patryk_script "'; rm *;'" Traité de Russie1 Atlas2 Pravda3

    ça va exécuter rm *! Cela peut poser un problème si le script peut être exécuté avec des privilèges supérieurs à ceux de l'utilisateur qui l'invoque. Par exemple, s'il peut être exécuté via sudoou appelé à partir d'une interface Web. Ce n'est probablement pas si important si vous l'utilisez comme vous-même, dans votre répertoire. Mais cela peut être changé en

    sed "s / $ ostr / $ nstr / g" "$ file"

    Cela comporte encore certains risques, mais ils sont beaucoup moins graves.

  3. if [ -f $file ], > $file.tmpet mv $file.tmp $file devraient être if [ -f "$file" ], > "$file.tmp"et mv "$file.tmp" "$file", respectivement, pour gérer les noms de fichiers pouvant contenir des espaces (ou d’autres personnages amusants). (La eval "sed …commande modifie également les noms de fichiers contenant des espaces.)


* shift prend un argument optionnel: un entier positif spécifiant le nombre de paramètres à décaler. La valeur par défaut est un ( 1). Par exemple, shift 4fait $5 devenir $1, $6devenir $2, etc. (Notez que l'exemple du Guide Bash pour les débutants est erroné.) Ainsi, votre script pourrait être modifié pour indiquer

ostr="$1"
nstr="$2"
shift 2

qui pourrait être considéré comme plus clair.



Note de fin / Avertissement:

Le langage d'invite de commande Windows (fichier de commandes) prend également en charge une SHIFTcommande, qui fait essentiellement la même chose que la shiftcommande dans les shells Unix, à une différence frappante, que je vais masquer pour éviter que les utilisateurs ne s'y perdent:

  • Une commande similaire à SHIFT 4est une erreur, générant un message d'erreur «Paramètre non valide pour la commande SHIFT».
  • SHIFT /n, où nest un entier compris entre 0 et 8, est valide - mais cela ne change pas les temps . Il se déplace une fois, en commençant par le n  ième argument. Ainsi , les causes (le cinquième argument) pour devenir à devenir , et ainsi de suite, laissant les arguments 0 à 3 seul.n SHIFT /4%5%4,  %6%5

Scott
la source
2
shiftpeut être appliqué à while, untilet même des forboucles pour manipuler des tableaux de manière beaucoup plus subtile qu'une simple forboucle. Je le trouve souvent utile dans les forboucles du genre ... set -f -- $args; IFS=$split; for arg do set -- $arg; shift "${number_of_fields_to_discard}" && fn_that_wants_split_arg "$arg"; done
mikeserv
3

L'explication la plus simple est la suivante. Considérez la commande:

/bin/command.sh SET location Cebu
/bin/command.sh SET location Cebu, Philippines 6014 

Sans l'aide de shiftvous, vous ne pouvez pas extraire la valeur complète de l'emplacement, car cette valeur peut être arbitrairement longue. Lorsque vous vous déplacez deux fois, les arguments SETet locationsont supprimés tels que:

x="$@"
echo "location = $x"

Jetez un long regard à la $@chose. Cela signifie qu’il peut enregistrer l’emplacement complet en variable xmalgré les espaces et la virgule dont il dispose. Donc, en résumé, nous appelons shiftet, plus tard, nous récupérons la valeur de ce qui reste de la variable $@.

UPDATE J'ajoute ci-dessous un très court extrait montrant le concept et l'utilité shiftsans lequel il serait très, très difficile d'extraire correctement les champs.

#!/bin/sh
#

[ $# -eq 0 ] && return 0

a=$1
shift
b=$@
echo $a
[ -n "$b" ] && echo $b

Explication : Après le shift, la variable b doit contenir le reste du contenu transféré, peu importe les espaces, etc. Il [ -n "$b" ] && echo $bs'agit d'une protection telle que nous n'imprimons b que si elle a un contenu.

typelogic
la source
(1) Ce n'est pas l'explication la plus simple. C'est un angle intéressant. (2) Mais aussi longtemps que vous voulez concaténer un tas d'arguments (ou tous), vous voudrez peut-être prendre l'habitude d'utiliser à la "$*"place de "$@". (3) J'allais upvoter votre réponse, mais je ne l'ai pas fait, parce que vous dites «changer deux fois» mais vous ne l'indiquez pas réellement dans le code. (4) Si vous souhaitez qu'un script puisse extraire une valeur de plusieurs mots de la ligne de commande, il est probablement préférable d'exiger de l'utilisateur qu'il mette la valeur entière entre guillemets. … (Suite)
Scott
(Suite)… Vous ne vous en souciez peut-être pas aujourd'hui, mais votre approche ne vous permet pas d'avoir plusieurs espaces ( Philippines   6014), des espaces de début, des espaces de fin ou des onglets. (5) BTW, vous pourrez peut-être aussi faire ce que vous faites ici à Bash x="${*:3}".
Scott
J'ai essayé de déchiffrer d'où vient votre commentaire '*** n'autorise pas les espaces multiples ***' car ce n'est pas lié à la shiftcommande. Vous faites peut-être allusion aux différences subtiles entre $ @ et $ *. Mais le fait demeure que mon extrait ci-dessus peut extraire avec précision l’emplacement, quel que soit le nombre d’espaces qu’il comporte, soit à l’avant, peu importe. Après le quart de travail, l'emplacement contiendra l'emplacement correct.
Typelogic
2

shift Traiter les arguments de ligne de commande comme une file d'attente FIFO, c'est un élément popleft chaque fois qu'il est appelé.

array = [a, b, c]
shift equivalent to
array.popleft
[b, c]
$1, $2,$3 can be interpreted as index of the array.
$# is the length of array
Algèbre
la source