Qual è lo scopo di usare shift negli script di shell?


118

Mi sono imbattuto in questo 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 è il significato delle linee in cui usano shift? Presumo che lo script dovrebbe essere usato con almeno argomenti, quindi ...?

Risposte:


141

shiftè un tipo bashincorporato che rimuove gli argomenti dall'inizio dell'elenco degli argomenti. Dato che i 3 argomenti addotti allo script sono disponibili in $1, $2, $3, quindi una chiamata a shift farà $2il nuovo $1. A shift 2si sposterà di due rendendo nuovo $1il vecchio $3. Per ulteriori informazioni, vedere qui:


25
+1 Nota che non li sta ruotando , li sta spostando fuori dall'array (di argomenti). Shift / unshift e pop / push sono nomi comuni per questo tipo generale di manipolazione (vedi, ad esempio, bash pushde popd).
Riccioli d'oro


@goldilocks molto vero. Invece di "ruotare" avrei dovuto trovare un modo per usare un'altra parola, qualcosa come "spostare gli argomenti in avanti nelle variabili degli argomenti". Spero comunque che gli esempi nel testo e il link al riferimento abbiano chiarito comunque.
umanità e

Sebbene lo script menzioni bash, la domanda è generale per tutte le shell, quindi prevale lo standard Posix: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
calandoa

32

Come commento Goldilocks' e riferimenti dell'umanità descrivono, shiftriassegna i parametri posizionali ( $1, $2, ecc) in modo che $1assume il vecchio valore di $2, $2assume il valore di $3, ecc *   Il vecchio valore $1viene scartata. ( $0non è cambiato.) Alcuni motivi per farlo includono:

  • Ti consente di accedere più facilmente al decimo argomento (se presente).  $10non funziona - è interpretato come $1concatenato con un 0 (e quindi potrebbe produrre qualcosa di simile Hello0). Dopo un shift, il decimo argomento diventa $9. (Tuttavia, nella maggior parte delle shell moderne, è possibile utilizzare ${10}.)
  • Come dimostra la Bash Guide for Beginners , può essere utilizzata per scorrere gli argomenti. IMNSHO, questo è goffo; forè molto meglio per quello.
  • Come nel tuo script di esempio, semplifica l'elaborazione di tutti gli argomenti nello stesso modo, tranne alcuni. Ad esempio, nel tuo script, $1e $2sono stringhe di testo, mentre $3e tutti gli altri parametri sono nomi di file.

Quindi ecco come si svolge. Supponiamo che il tuo script sia chiamato Patryk_scripte si chiami come

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

La sceneggiatura vede

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

L'istruzione ostr="$1"imposta la variabile ostrsu USSR. La prima shiftistruzione modifica i parametri posizionali come segue:

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

L'istruzione nstr="$1"imposta la variabile nstrsu Russia. La seconda shiftistruzione modifica i parametri posizionali come segue:

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

E poi le formodifiche del ciclo USSR( $ostr) a Russia( $nstr) nei file Treaty1, Atlas2e Pravda3.



Ci sono alcuni problemi con lo script.

  1. per file in $ @; fare

    Se lo script viene invocato come

    Patryk_script USSR Russia Treaty1 "World Atlas2" Pravda3

    vede

    $ 1 = URSS
    $ 2 = Russia
    $ 3 = Trattato1
    $ 4 = Atlante mondiale2
    $ 5 = Pravda3

    ma, perché $@non è citato, lo spazio in World Atlas2non è citato, e il forciclo pensa che ha quattro file: Treaty1, World, Atlas2, e Pravda3. Questo dovrebbe essere uno dei due

    per il file in "$ @"; fare

    (per citare eventuali caratteri speciali negli argomenti) o semplicemente

    per il file do

    (che equivale alla versione più lunga).

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

    Non è necessario che questo sia un evalpassaggio e l'input dell'utente non controllato a un evalpuò essere pericoloso. Ad esempio, se lo script viene invocato come

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

    verrà eseguito rm *! Questa è una grande preoccupazione se lo script può essere eseguito con privilegi superiori a quelli dell'utente che lo invoca; ad esempio, se può essere eseguito tramite sudoo richiamato da un'interfaccia Web. Probabilmente non è così importante se lo usi come te stesso, nella tua directory. Ma può essere cambiato in

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

    Ciò comporta ancora alcuni rischi, ma sono molto meno gravi.

  3. if [ -f $file ], > $file.tmpE mv $file.tmp $file dovrebbe essere if [ -f "$file" ], > "$file.tmp"e mv "$file.tmp" "$file"rispettivamente, per gestire i nomi di file che potrebbero avere spazi (o altri caratteri divertenti) in loro. (Il eval "sed …comando modifica anche i nomi dei file che contengono spazi.)


* shift accetta un argomento facoltativo: un numero intero positivo che specifica quanti parametri spostare. L'impostazione predefinita è one ( 1). Ad esempio, le shift 4cause $5 diventano $1, $6diventano $2, e così via. (Nota che l'esempio nella Guida di Bash per principianti è sbagliato.) E così il tuo script potrebbe essere modificato per dire

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

che potrebbe essere considerato più chiaro.



Nota finale / Avvertenza:

Il linguaggio Prompt dei comandi di Windows (file batch) supporta anche un SHIFTcomando, che fa sostanzialmente la stessa cosa del shiftcomando nelle shell Unix, con una notevole differenza, che nasconderò per cercare di evitare che le persone ne vengano confuse:

  • Un comando simile SHIFT 4è un errore, che genera un messaggio di errore "Parametro non valido per il comando SHIFT".
  • SHIFT /n, dove nè un numero intero compreso tra 0 e 8, è valido, ma non cambia i tempi . Si sposta una volta, partendo con il n  ° argomento. Quindi fa sì che il quinto argomento diventi , e così via, lasciando solo gli argomenti da 0 a 3.n SHIFT /4%5%4,  %6%5


2
shiftpuò essere applicato a while, untile persino forloop per gestire array in modi molto più sottili di quanto non faccia un semplice forloop. Lo trovo spesso utile in forcicli come ... 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 spiegazione più semplice è questa. Considera il comando:

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

Senza l'aiuto di shiftte non puoi estrarre il valore completo della posizione perché questo valore potrebbe diventare arbitrariamente lungo. Quando si sposta due volte, gli arg SETe locationvengono rimossi in modo tale che:

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

Guarda molto a lungo la $@cosa. Ciò significa che può salvare la posizione completa in variabile xnonostante gli spazi e la virgola che ha. Quindi, in sintesi, chiamiamo shifte poi recuperiamo il valore di ciò che resta della variabile $@.

AGGIORNAMENTO Sto aggiungendo di seguito uno snippet molto breve che mostra il concetto e l'utilità dei shiftquali senza, sarebbe molto, molto difficile estrarre correttamente i campi.

#!/bin/sh
#

[ $# -eq 0 ] && return 0

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

Spiegazione : Dopo il shift, la variabile b deve contenere il resto del materiale che viene passato, indipendentemente dagli spazi o ecc. La [ -n "$b" ] && echo $bprotezione è tale che stampiamo b solo se ha un contenuto.


(1) Questa non è la spiegazione più semplice. È un angolo interessante. (2) Ma finché vuoi concatenare un mucchio di argomenti (o tutti), potresti prendere l'abitudine di usare "$*"invece di "$@". (3) Avrei votato a favore della tua risposta, ma non l'ho fatto, perché dici "sposta due volte" ma in realtà non la mostri nel codice. (4) Se si desidera che uno script sia in grado di prendere un valore di più parole dalla riga di comando, è probabilmente meglio richiedere all'utente di inserire l'intero valore tra virgolette. ... (proseguendo)
Scott,

(Proseguendo) ... Potrebbe non interessarti oggi, ma il tuo approccio non ti consente di avere più spazi ( Philippines   6014), spazi iniziali, spazi finali o tabulazioni. (5) A proposito, potresti anche essere in grado di fare quello che stai facendo qui con bash x="${*:3}".
Scott,

Ho provato a decifrare da dove proviene il tuo commento '*** non consente più spazi ***' perché non è correlato al shiftcomando. Forse ti riferisci alle differenze sottili di $ @ contro $ *. Ma resta il fatto che il mio frammento sopra può estrarre con precisione la posizione, indipendentemente da quanti spazi ha in coda o nella parte anteriore, non importa. Dopo il turno, la posizione conterrà la posizione corretta.
dattiloscritto l'

2

shift tratta gli argomenti della riga di comando come una coda FIFO, popleft element ogni volta che viene invocato.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.