¿Cuál es el propósito de usar shift en los scripts de shell?


118

Me he encontrado con este 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

¿Cuál es el significado de las líneas donde se usan shift? Supongo que el script debe usarse con al menos argumentos, así que ...?

Respuestas:


141

shiftes una función bashincorporada que elimina los argumentos desde el principio de la lista de argumentos. Teniendo en cuenta que los 3 argumentos proporcionadas al script están disponibles en $1, $2, $3, a continuación, una llamada a la shift hará $2el nuevo $1. A shift 2cambiará por dos haciendo nuevo $1lo viejo $3. Para más información, ver aquí:


25
+1 Tenga en cuenta que no los está girando , los está alejando de la matriz (de argumentos). Shift / unshift y pop / push son nombres comunes para este tipo general de manipulación (ver, por ejemplo, bash pushdy popd).
Ricitos


@goldilocks muy cierto. En lugar de "rotar", debería haber encontrado una manera de usar otra palabra, algo así como "mover los argumentos hacia adelante en las variables de argumento". De todos modos, espero que los ejemplos en el texto y el enlace a la referencia lo hayan dejado claro.
humanityANDpeace

Aunque el guión menciona bash, la pregunta es general para todos los shells, por lo tanto, prevalece el estándar Posix: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
calandoa

32

Como comentario Goldilocks' y las referencias de la humanidad describen, shiftvuelve a asignar los parámetros de posición ( $1, $2, etc.) de manera que $1asume el antiguo valor de $2, $2asume el valor de $3, etc *   El antiguo valor de $1se descarta. ( $0no se cambia). Algunas razones para hacerlo incluyen:

  • Le permite acceder al décimo argumento (si hay uno) más fácilmente.  $10no funciona: se interpreta como $1concatenado con un 0 (y, por lo tanto, podría producir algo así Hello0). Después de a shift, se convierte en el décimo argumento $9. (Sin embargo, en la mayoría de los proyectiles modernos, puede usar ${10}).
  • Como lo demuestra la Guía Bash para principiantes , se puede usar para recorrer los argumentos. IMNSHO, esto es torpe; fores mucho mejor para eso
  • Como en su secuencia de comandos de ejemplo, facilita el procesamiento de todos los argumentos de la misma manera, excepto algunos. Por ejemplo, en su secuencia de comandos, $1y $2son cadenas de texto, mientras que $3y todos los demás parámetros son nombres de archivo.

Así es como se desarrolla. Supongamos que su script se llama Patryk_scripty se llama como

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

El guión ve

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

La declaración ostr="$1"establece la variable ostren USSR. La primera shiftdeclaración cambia los parámetros posicionales de la siguiente manera:

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

La declaración nstr="$1"establece la variable nstren Russia. La segunda shiftdeclaración cambia los parámetros posicionales de la siguiente manera:

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

Y a continuación, los forcambios de bucle USSR( $ostr) a Russia( $nstr) en los archivos Treaty1, Atlas2y Pravda3.



Hay algunos problemas con el script.

  1. para archivo en $ @; hacer

    Si el script se invoca como

    Patryk_script URSS Rusia Tratado1 "Atlas mundial2" Pravda3

    ve

    $ 1 = URSS
    $ 2 = Rusia
    $ 3 = Tratado1
    $ 4 = Atlas mundial2
    $ 5 = Pravda3

    pero, debido a $@que no es citado, en el espacio World Atlas2no es citado, y el forbucle piensa que tiene cuatro archivos: Treaty1, World, Atlas2, y Pravda3. Esto debería ser

    para el archivo en "$ @"; hacer

    (para citar cualquier carácter especial en los argumentos) o simplemente

    para archivo hacer

    (que es equivalente a la versión más larga).

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

    No hay necesidad de que esto sea un eval, y pasar una entrada de usuario no verificada a un evalpuede ser peligroso. Por ejemplo, si el script se invoca como

    Patryk_script "'; rm *;'" Tratado de Rusia1 Atlas2 Pravda3

    se ejecutará rm *! Esto es una gran preocupación si el script se puede ejecutar con privilegios superiores a los del usuario que lo invoca; por ejemplo, si se puede ejecutar sudoo invocar desde una interfaz web. Probablemente no sea tan importante si solo lo usa como usted mismo, en su directorio. Pero se puede cambiar a

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

    Esto todavía tiene algunos riesgos, pero son mucho menos severos.

  3. if [ -f $file ], > $file.tmpy mv $file.tmp $file debe ser if [ -f "$file" ], > "$file.tmp"y mv "$file.tmp" "$file", respectivamente, para manejar nombres de archivos que pueden tener espacios (u otros caracteres divertidos) en ellos. (El eval "sed …comando también manipula los nombres de archivo que tienen espacios en ellos).


* shift toma un argumento opcional: un entero positivo que especifica cuántos parámetros cambiar. El valor predeterminado es uno ( 1). Por ejemplo, shift 4hace $5 que se convierta $1, $6que se convierta $2, etc. (Tenga en cuenta que el ejemplo en la Guía Bash para principiantes es incorrecto). Por lo tanto, su secuencia de comandos podría modificarse para decir

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

lo que podría considerarse más claro.



Nota final / Advertencia:

El lenguaje de símbolo del sistema de Windows (archivo por lotes) también admite un SHIFTcomando, que básicamente hace lo mismo que el shiftcomando en shells de Unix, con una notable diferencia, que ocultaré para tratar de evitar que la gente se confunda:

  • Un comando como SHIFT 4es un error, produciendo un mensaje de error "Parámetro inválido para comando SHIFT".
  • SHIFT /n, donde nes un número entero entre 0 y 8, es válido, pero no cambia los tiempos . Se desplaza una vez, comenzando con el n  º argumento. Por lo tanto , (el quinto argumento) se convierte en , y así sucesivamente, dejando solo los argumentos del 0 al 3.n SHIFT /4%5%4,  %6%5


2
shiftpuede aplicarse a while, untile incluso forbucles para manejar matrices de formas mucho más sutiles que un forbucle simple . A menudo lo encuentro útil en forbucles como ... 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

La explicación más simple es esta. Considere el comando:

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

Sin la ayuda de shiftusted, no puede extraer el valor completo de la ubicación porque este valor podría ser arbitrariamente largo. Cuando cambia dos veces, los args SETy locationse eliminan de manera que:

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

Mire muy largamente la $@cosa. Eso significa que puede guardar la ubicación completa en variable a xpesar de los espacios y la coma que tiene. En resumen, llamamos shifty luego recuperamos el valor de lo que queda de la variable $@.

ACTUALIZACIÓN Estoy agregando a continuación un fragmento muy breve que muestra el concepto y la utilidad de los shiftcuales, sin lo cual, sería muy, muy difícil extraer los campos correctamente.

#!/bin/sh
#

[ $# -eq 0 ] && return 0

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

Explicación : Después de shift, la variable b contendrá el resto de las cosas que se pasan, sin importar espacios o etc. La [ -n "$b" ] && echo $bprotección es tal que solo imprimimos b si tiene un contenido.


(1) Esta no es la explicación más simple. Es un ángulo interesante. (2) Pero siempre que desee concatenar un montón de argumentos (o todos ellos), es posible que desee adquirir el hábito de usar en "$*"lugar de "$@". (3) Iba a votar su respuesta, pero no lo hice, porque usted dice "cambiar dos veces" pero en realidad no lo muestra en el código. (4) Si desea que un script pueda tomar un valor de varias palabras de la línea de comando, probablemente sea mejor requerir que el usuario ponga el valor completo entre comillas. … (Continúa)
Scott

(Cont.) ... Puede que hoy no le importe, pero su enfoque no le permite tener múltiples espacios ( Philippines   6014), espacios iniciales, espacios finales o pestañas. (5) Por cierto, también puedes hacer lo que estás haciendo aquí en golpe x="${*:3}".
Scott

Traté de descifrar de dónde proviene su comentario '*** no permite múltiples espacios ***' porque esto no está relacionado con el shiftcomando. Tal vez se refiera a las diferencias sutiles de $ @ versus $ *. Pero el hecho sigue siendo que mi fragmento de arriba puede extraer con precisión la ubicación sin importar cuántos espacios tenga detrás o en el frente, no importa. Después del turno, la ubicación contendrá la ubicación correcta.
typelogic

2

shift tratar los argumentos de la línea de comandos como una cola FIFO, es un elemento popleft cada vez que se invoca.

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.