Qual é o propósito de usar shift em shell scripts?

91

Eu encontrei 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

e não consigo enrolar minha cabeça nas linhas em que eles usam shift .

Qual é o significado disso? Eu presumo que o script deve ser usado com pelo menos argumentos, então ...?

    
por Patryk 16.12.2014 / 22:37

4 respostas

104

shift é um bash embutido, que remove os argumentos do início da lista de argumentos. Considerando que os três argumentos fornecidos para o script estão disponíveis em $1 , $2 , $3 , uma chamada para shift fará $2 o novo $1 . Um shift 2 mudará de dois fazendo novos $1 do antigo $3 . Para mais informações, veja aqui:

por 16.12.2014 / 22:44
26

Como o comentário de Cachinhos Dourados e as referências da humanidade descrevem, shift reatribui os parâmetros posicionais ( $1 , $2 , etc.) para que $1 assuma o valor antigo de $2 , $2 assume o valor de $3 , etc. * O valor antigo de $1 é descartado. ( $0 não é alterado.) Algumas razões para isso incluem:

  • Permite acessar o décimo argumento (se houver) mais facilmente. $10 não funciona - é interpretado como $1 concatenado com 0 (e assim pode produzir algo como Hello0 ). Depois de um shift , o décimo argumento se torna $9 . (No entanto, na maioria dos shells modernos, você pode usar ${10} .)
  • Como o Guia do Bash para iniciantes demonstra, ele pode ser usado para percorrer os argumentos. IMNSHO, isso é desajeitado; for é muito melhor para isso.
  • Como no seu script de exemplo, torna fácil processar todos os argumentos da mesma maneira, exceto alguns. Por exemplo, no seu script, $1 e $2 são cadeias de texto, enquanto $3 e todos os outros parâmetros são nomes de arquivo.

Então, veja como isso acontece. Suponha que seu script seja chamado de Patryk_script e seja chamado de

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

O script vê

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

A instrução ostr="$1" define a variável ostr para USSR . A primeira instrução shift altera os parâmetros posicionais da seguinte forma:

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

A instrução nstr="$1" define a variável nstr para Russia . A segunda instrução shift altera os parâmetros posicionais da seguinte forma:

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

Em seguida, o loop for altera USSR ( $ostr ) para Russia ( $nstr ) nos arquivos Treaty1 , Atlas2 e Pravda3 .

Existem alguns problemas com o script.

  1. for file in $@; do

    Se o script for invocado como

    Patryk_script USSR Russia Treaty1 "World Atlas2" Pravda3

    ele vê

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

    mas, como $@ não é citado, o espaço em World Atlas2 não é citado, e o loop for acha que tem quatro arquivos: Treaty1 , World , Atlas2 , e Pravda3 . Isso deve ser

    for file in "$@"; do

    (para citar qualquer caractere especial nos argumentos) ou simplesmente

    for file do

    (que é equivalente à versão mais longa).

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

    Não é necessário que este seja um eval , e passar a entrada do usuário desmarcada para um eval pode ser perigoso. Por exemplo, se o script for invocado como

    Patryk_script "'; rm *;'" Russia Treaty1 Atlas2 Pravda3

    executará rm * ! Esta é uma grande preocupação se o script puder ser executado com privilégios superiores aos do usuário que a invoca; por exemplo, se puder ser executado por meio de sudo ou invocado em uma interface da web. Provavelmente não é tão importante se você usá-lo como você mesmo, no seu diretório. Mas isso pode ser alterado para

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

    Isso ainda apresenta alguns riscos, mas eles são muito menos graves.

  3. if [ -f $file ] , > $file.tmp e mv $file.tmp $file deve ser if [ -f "$file" ] , > "$file.tmp" e mv "$file.tmp" "$file" , respectivamente, para manipular nomes de arquivos que pode ter espaços (ou outros personagens engraçados) neles. (O comando eval "sed … também manipula nomes de arquivos que possuem espaços neles.)

* shift aceita um argumento opcional: um inteiro positivo que especifica quantos parâmetros para mudar. O padrão é um ( 1 ). Por exemplo, shift 4 causa $5 para se tornar $1 , $6 para se tornar $2 e assim por diante. (Observe que o exemplo no Guia do Bash para iniciantes está errado.) E assim o seu script pode ser modificado para dizer

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

que pode ser considerado mais claro.

Nota final / Aviso:

O idioma do Prompt de Comando do Windows (arquivo de lote) também suporta um comando SHIFT , que faz basicamente a mesma coisa que o comando shift em shells Unix, com uma diferença marcante, que eu vou esconder para evitar que as pessoas fiquem confusas com isso:

  • A command like SHIFT 4 is an error, yielding an “Invalid parameter to SHIFT command” error message.
  • SHIFT /n, where n is an integer between 0 and 8, is valid — but it doesn’t shift n times.  It shifts once, starting with the n th argument.  So SHIFT /4 causes %5 (the fifth argument) to become %4,  %6 to become %5, and so on, leaving arguments 0 through 3 alone.
    
por 18.12.2014 / 02:12
3

A explicação mais simples é essa. Considere o comando:

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

Sem a ajuda de shift , não é possível extrair o valor completo do local porque esse valor pode ser arbitrariamente longo. Quando você muda duas vezes, os argumentos SET e location são removidos de forma que:

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

Dê uma olhada muito longa na coisa $@ . Isso significa que ele pode salvar o local completo na variável x , apesar dos espaços e da vírgula que ele possui. Então, em resumo, chamamos shift e depois recuperamos o valor do que resta da variável $@ .

UPDATE Estou adicionando abaixo um trecho muito curto mostrando o conceito e a utilidade de shift sem o qual, seria muito, muito difícil extrair os campos corretamente.

#!/bin/sh
#

[ $# -eq 0 ] && return 0

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

Explicação : Após o shift , a variável b deverá conter o restante do material sendo passado, não importando os espaços ou etc. O [ -n "$b" ] && echo $b é uma proteção tal que nós somente imprimimos b se ele tiver um conteúdo.

    
por 27.01.2018 / 16:20
2

shift trata os argumentos da linha de comando como uma fila FIFO, o elemento popleft toda vez que é invocado.

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
    
por 11.04.2018 / 17:37