Jaki jest cel używania shift w skryptach powłoki?

118

Natknąłem się na ten skrypt:

#! /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

Jakie jest znaczenie linii, w których używają shift? Zakładam, że skrypt powinien być używany przynajmniej z argumentami, więc ...?

Patryk
źródło

Odpowiedzi:

141

shiftjest bashwbudowanym rodzajem usuwania argumentów z początku listy argumentów. Biorąc pod uwagę, że 3 argumenty dostarczone do skryptu są dostępne w $1, $2, $3, a następnie wezwanie do shift uczyni $2nowa $1. shift 2Przesunie przez dwa dokonywania nowy $1stary $3. Aby uzyskać więcej informacji, zobacz tutaj:

ludzkośćANDpeace
źródło
25
+1 Uwaga: nie obraca ich, tylko odsuwa od tablicy (argumentów). Shift / unshift i pop / push to wspólne nazwy dla tego ogólnego rodzaju manipulacji (patrz np. Bash pushdi popd).
goldilocks
1
Ostateczne odniesienie: gnu.org/software/bash/manual/bashref.html#index-shift
glenn jackman
@Goldilocks bardzo prawdziwe. Zamiast „obracać” powinienem był znaleźć sposób na użycie innego słowa, coś w stylu „przesuń argumenty do przodu w zmiennych argumentów”. W każdym razie mam nadzieję, że przykłady w tekście i link do odnośnika jeszcze to wyjaśnią.
humanityANDpeace
Chociaż skrypt wspomina bash, pytanie jest ogólne dla wszystkich powłok, dlatego dominuje standard Posix: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
calandoa
32

Jak opisano komentarz złotowłosa i odniesienia ludzkości, shiftponownie przypisuje parametry pozycyjne ( $1, $2itp.), Aby $1nabiera starej wartości $2, $2przyjmuje wartość $3itp. *   Stara wartość $1jest odrzucana. ( $0nie ulega zmianie). Oto niektóre powody:

  • Umożliwia łatwiejszy dostęp do dziesiątego argumentu (jeśli taki istnieje).  $10nie działa - jest interpretowane jako $1połączone z 0 (i dlatego może wytworzyć coś takiego Hello0). Po a shiftnastępuje dziesiąty argument $9. (Jednak w większości nowoczesnych powłok możesz użyć ${10}.)
  • Jak pokazuje Przewodnik Bash dla początkujących , można go używać do przechodzenia między argumentami. IMNSHO, to jest niezdarne; forjest do tego znacznie lepszy.
  • Podobnie jak w przykładowym skrypcie, ułatwia przetwarzanie wszystkich argumentów w ten sam sposób, z wyjątkiem kilku. Na przykład w skrypcie $1i $2są ciągami tekstowymi, a $3wszystkie inne parametry są nazwami plików.

Oto jak to się gra. Załóżmy, że twój skrypt jest wywoływany Patryk_scripti nazywa się jako

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

Skrypt widzi

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

Instrukcja ostr="$1"ustawia zmienną ostrna USSR. Pierwsza shiftinstrukcja zmienia parametry pozycyjne w następujący sposób:

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

Instrukcja nstr="$1"ustawia zmienną nstrna Russia. Druga shiftinstrukcja zmienia parametry pozycyjne w następujący sposób:

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

A następnie forzmiany loop USSR( $ostr) do Russia( $nstr) w plikach Treaty1, Atlas2oraz Pravda3.



Istnieje kilka problemów ze skryptem.

  1. dla pliku w $ @; zrobić

    Jeśli skrypt zostanie wywołany jako

    Patryk_script ZSRR Rosja Traktat1 „Atlas świata2” Prawda3

    to widzi

    1 USD = ZSRR
    2 USD = Rosja
    3 USD = Traktat 1
    4 USD = Atlas Świata 2
    5 USD = Pravda3

    ale, ponieważ $@nie jest cytowany, przestrzeń w World Atlas2nie jest cytowany, a forpętla myśli, że ma cztery pliki: Treaty1, World, Atlas2i Pravda3. Tak powinno być

    dla pliku w „$ @”; zrobić

    (aby zacytować dowolne znaki specjalne w argumentach) lub po prostu

    do pliku do

    (co odpowiada dłuższej wersji).

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

    Nie ma takiej potrzeby eval, a przekazywanie niepotwierdzonych danych wejściowych do użytkownika evalmoże być niebezpieczne. Na przykład, jeśli skrypt jest wywoływany jako

    Patryk_script "'; rm *;'" Rosja Traktat1 Atlas2 Prawda3

    wykona się rm *! Jest to duży problem, jeśli skrypt można uruchomić z uprawnieniami wyższymi niż uprawnienia użytkownika, który go wywołuje; np. jeśli można go uruchomić sudolub wywołać z interfejsu sieciowego. Prawdopodobnie nie jest to tak ważne, jeśli po prostu używasz go jako siebie, w swoim katalogu. Ale można to zmienić na

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

    Wciąż wiąże się to z pewnym ryzykiem, ale są one znacznie mniej dotkliwe.

  3. if [ -f $file ], > $file.tmpI mv $file.tmp $file powinna być if [ -f "$file" ], > "$file.tmp"i mv "$file.tmp" "$file", odpowiednio, do obsługi nazw plików, które mogą mieć spacji (lub innych znaków śmieszne) w nich. ( eval "sed …Polecenie zmienia także nazwy plików ze spacjami).


* shift przyjmuje opcjonalny argument: dodatnia liczba całkowita, która określa liczbę parametrów do przesunięcia. Domyślna wartość to jeden ( 1). Na przykład shift 4powoduje , że $5 się staje $1, $6staje się $2itd. (Zauważ, że przykład w Bash Guide dla początkujących jest błędny.) Tak więc twój skrypt może zostać zmodyfikowany tak, by powiedział

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

co można uznać za bardziej jasne.



Uwaga końcowa / ostrzeżenie:

Język wiersza poleceń systemu Windows (plik wsadowy) obsługuje także SHIFTpolecenie, które działa w zasadzie tak samo jak shiftpolecenie w powłokach uniksowych, z jedną uderzającą różnicą, którą ukryję, aby zapobiec pomyłkom ludzi:

  • Polecenie podobne SHIFT 4jest błędem, co powoduje wyświetlenie komunikatu o błędzie „Nieprawidłowy parametr polecenia SHIFT”.
  • SHIFT /n, gdzie njest liczbą całkowitą od 0 do 8, jest poprawna - ale nie zmienia czasów . Przesuwa się raz, zaczynając od n-  tego argumentu. To powoduje, że (piąty argument) staje się itd., Pozostawiając argumenty od 0 do 3 w spokoju.n SHIFT /4%5%4,  %6%5

Scott
źródło
2
shiftmożna zastosować do while, untila nawet do forpętli do obsługi tablic w znacznie bardziej subtelny sposób niż zwykła forpętla. Często przydaje mi się w forpętlach takich jak ... 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

Najprostsze wytłumaczenie jest następujące. Rozważ polecenie:

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

Bez shiftTwojej pomocy nie możesz wyodrębnić pełnej wartości lokalizacji, ponieważ ta wartość może być dowolnie długa. Po dwukrotnym przesunięciu argumenty SETi locationsą usuwane w taki sposób, że:

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

Spójrz na to bardzo długo $@. Oznacza to, że może zapisać całą lokalizację w zmiennej xpomimo spacji i przecinków, które ma. Podsumowując, wywołujemy, shifta następnie pobieramy wartość tego, co zostało ze zmiennej $@.

AKTUALIZACJA Dodaję poniżej bardzo krótki fragment pokazujący koncepcję i przydatność, shiftbez których bardzo trudno byłoby poprawnie wyodrębnić pola.

#!/bin/sh
#

[ $# -eq 0 ] && return 0

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

Objaśnienie : Po shiftzmiennej zmienna b będzie zawierać resztę przekazywanych elementów, bez względu na spacje itp. [ -n "$b" ] && echo $bJest to taka ochrona, że ​​drukujemy b tylko, jeśli ma treść.

typelogiczny
źródło
(1) To nie jest najprostsze wytłumaczenie. To interesujący kąt. (2) Ale jeśli chcesz połączyć kilka argumentów (lub wszystkie z nich), możesz przyzwyczaić się do używania zamiast . (3) Chciałem głosować za twoją odpowiedzią, ale nie zrobiłem tego, ponieważ mówisz „przesuń dwa razy”, ale tak naprawdę nie pokazujesz tego w kodzie. (4) Jeśli chcesz, aby skrypt mógł pobierać wartość wielu słów z wiersza poleceń, prawdopodobnie lepiej jest wymagać od użytkownika wpisania całej wartości w cudzysłów. … (Ciąg dalszy)"$*""$@"
Scott
(Ciąg dalszy)… Być może dzisiaj Cię to nie obchodzi, ale twoje podejście nie pozwala ci mieć wielu spacji ( Philippines   6014), spacji wiodących, spacji końcowych lub tabulatorów. (5) BTW, możesz także być w stanie robić to, co robisz tutaj w bash x="${*:3}".
Scott
Próbowałem odczytać, skąd pochodzi komentarz „*** nie zezwala na wiele spacji ***”, ponieważ nie jest to związane z shiftpoleceniem. Być może odwołujesz się do subtelnych różnic $ @ versus $ *. Ale nadal pozostaje faktem, że mój fragment powyżej może dokładnie wyodrębnić lokalizację bez względu na to, ile spacji ma przed sobą lub z przodu, nie ma to znaczenia. Po zmianie lokalizacja będzie zawierać prawidłową lokalizację.
typelogiczny
2

shift traktuj argumenty wiersza poleceń jako kolejkę FIFO, jest to element przewijany za każdym razem, gdy jest wywoływany.

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
Algebra
źródło