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

118

Eu me deparei com 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

Qual é o significado das linhas em que elas usam shift? Presumo que o script deve ser usado com pelo menos argumentos, então ...?

Patryk
fonte

Respostas:

141

shifté um bashrecurso interno que remove os argumentos do início da lista de argumentos. Dado que os 3 argumentos fornecidos para o script estão disponíveis em $1, $2, $3, em seguida, uma chamada para shift fará $2o novo $1. A shift 2mudará duas vezes, renovando $1a velha $3. Para mais informações, veja aqui:

humanidade e paz
fonte
25
+1 Observe que não está girando- os, mas deslocando -os da matriz (de argumentos). Shift / unshift e pop / push são nomes comuns para esse tipo geral de manipulação (consulte, por exemplo, bash pushde popd).
Goldilocks
1
A referência definitiva: gnu.org/software/bash/manual/bashref.html#index-shift
glenn jackman
@goldilocks very true. Em vez de "rodar", eu deveria ter encontrado uma maneira de usar outra palavra, algo como "avançar os argumentos nas variáveis ​​de argumento". De qualquer forma, espero que os exemplos no texto e o link para a referência tenham deixado claro, no entanto.
Humanandpeace
Embora o script mencione bash, a pergunta é geral para todas as conchas, portanto o padrão Posix prevalece: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
calandoa
32

Como comentário goldilocks e referências da humanidade descrever, shiftatribui novamente os parâmetros de posição ( $1, $2, etc.) de modo a que $1assume o valor antigo $2, $2assume o valor $3, etc. *   O valor antigo $1é descartado. ( $0não é alterado.) Alguns motivos para fazer isso incluem:

  • Permite acessar o décimo argumento (se houver) com mais facilidade.  $10não funciona - é interpretado como $1concatenado com um 0 (e, portanto, pode produzir algo como Hello0). Depois de a shift, o décimo argumento se torna $9. (No entanto, na maioria das conchas modernas, você pode usar ${10}.)
  • Como o Bash Guide for Beginners demonstra, ele pode ser usado para percorrer os argumentos. IMNSHO, isso é desajeitado; foré muito melhor para isso.
  • Como no exemplo de script, facilita o processamento de todos os argumentos da mesma maneira, exceto por alguns. Por exemplo, no seu script, $1e $2são cadeias de texto, enquanto $3e todos os outros parâmetros são nomes de arquivos.

Então, aqui está como isso acontece. Suponha que seu script seja chamado Patryk_scripte seja chamado como

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 ostrcomo USSR. A primeira shiftinstrução altera os parâmetros posicionais da seguinte maneira:

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

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

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

E então as formudanças de loop USSR( $ostr) para Russia( $nstr) nos arquivos Treaty1, Atlas2e Pravda3.



Existem alguns problemas com o script.

  1. para arquivo em $ @; Faz

    Se o script for chamado como

    Patryk_script Tratado da URSS Rússia1 "Atlas Mundial2" Pravda3

    $ 1 = URSS
    $ 2 = Rússia
    $ 3 = Tratado1
    US $ 4 = Atlas Mundial2
    $ 5 = Pravda3

    mas, porque $@não é citado, o espaço World Atlas2não é citado, eo forciclo pensa que tem quatro arquivos: Treaty1, World, Atlas2, e Pravda3. Isso deve ser

    para arquivo em "$ @"; Faz

    (para citar qualquer caractere especial nos argumentos) ou simplesmente

    para o arquivo

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

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

    Não é necessário que isso seja uma evalpassagem de entrada desmarcada do usuário para uma evalpode ser perigosa. Por exemplo, se o script for chamado como

    Patryk_script "'; rm *;'" Tratado da Rússia1 Atlas2 Pravda3

    vai executar rm *! Essa é uma grande preocupação se o script puder ser executado com privilégios mais altos que os do usuário que o invoca; por exemplo, se pode ser executado via sudoou invocado a partir de uma interface da web. Provavelmente não é tão importante se você apenas usá-lo como você mesmo, em seu diretório. Mas pode ser alterado para

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

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

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


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

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

o que pode ser considerado mais claro.



Nota final / Aviso:

A linguagem do prompt de comando do Windows (arquivo em lote) também suporta um SHIFTcomando, que basicamente faz a mesma coisa que o shiftcomando nos shells do Unix, com uma diferença marcante, que vou esconder para tentar impedir que as pessoas se confundam com ele:

  • Um comando como SHIFT 4é um erro, gerando uma mensagem de erro "Parâmetro inválido no comando SHIFT".
  • SHIFT /n, onde né um número inteiro entre 0 e 8, é válido - mas não muda os horários . Ela muda uma vez, começando com o n  º argumento. Assim, faz com que o quinto argumento se torne e assim por diante, deixando os argumentos de 0 a 3 sozinhos.n SHIFT /4%5%4,  %6%5

Scott
fonte
2
shiftpode ser aplicado a while, untile até mesmo forloops para manipular matrizes de maneiras muito mais sutis do que um forloop simples . Muitas vezes eu achar que é útil em forloops 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

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 shiftvocê, não é possível extrair o valor completo do local, pois esse valor pode ser arbitrariamente longo. Quando você alterna duas vezes, os argumentos SETe locationsão removidos de forma que:

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

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

ATUALIZAÇÃO Estou adicionando abaixo um trecho muito curto, mostrando o conceito e a utilidade shiftsem os quais, 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 deve conter o restante do material que está sendo passado, independentemente de espaços ou etc. A [ -n "$b" ] && echo $bproteção é tal que só imprimimos b se tiver um conteúdo.

typelogic
fonte
(1) Esta não é a explicação mais simples. É um ângulo interessante. (2) Mas contanto que você queira concatenar vários argumentos (ou todos eles), você pode adquirir o hábito de usar em "$*"vez de "$@". (3) Eu ia aprovar sua resposta, mas não o fiz, porque você diz "mudar duas vezes", mas na verdade não a mostra em código. (4) Se você deseja que um script consiga obter um valor de várias palavras na linha de comando, provavelmente é melhor exigir que o usuário coloque o valor inteiro entre aspas. ... (continua)
Scott
(Continua) ... Você pode não se importar hoje, mas sua abordagem não permite que você tenha vários espaços ( Philippines   6014), espaços à esquerda, espaços à direita ou guias. (5) Aliás, você também pode fazer o que está fazendo aqui no bash x="${*:3}".
Scott
Tentei decifrar de onde o seu comentário '*** não permite vários espaços ***' está vindo, porque isso não está relacionado ao shiftcomando. Talvez você esteja se referindo a diferenças sutis $ @ versus $ *. Mas o fato ainda permanece: meu snippet acima pode extrair com precisão o local, independentemente de quantos espaços ele esteja à direita ou na frente, não importa. Após o turno, o local conterá o local correto.
typelogic 11/03/19
2

shift tratar os argumentos da linha de comando como uma fila FIFO, ele remove o elemento toda vez que é chamado.

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
Álgebra
fonte