$ @ oprócz pierwszego argumentu

36

Muszę napisać skrypt powłoki, który działa w następujący sposób:

./myscript arg1 arg2_1 arg2_2 arg2_3 ....... arg2_#

wewnątrz skryptu znajduje się pętla for

for i in $@

Jednak, jak wiem, $ @ obejmuje 1 $ do $ (# -1 - 1). Ale dla mojego programu 1 $ różni się wyraźnie od 2 $ 3 $ 4 $ itp. Chciałbym zapętlić od 2 $ do końca ... Jak to osiągnąć? Dziękuję Ci:)

użytkownik40780
źródło

Odpowiedzi:

47

Po pierwsze, pamiętaj, że $@bez cudzysłowów nie ma sensu i nie należy ich używać. $@należy używać tylko quoted ( "$@") oraz w kontekście listowym.

for i in "$@" kwalifikuje się jako kontekst listy, ale tutaj, aby zapętlić parametry pozycyjne, kanoniczną, najbardziej przenośną i prostszą formą jest:

for i
do something with "$i"
done

Teraz, aby zapętlić elementy od drugiego, kanonicznym i najbardziej przenośnym sposobem jest użycie shift:

first_arg=$1
shift # short for shift 1
for i
do something with "$i"
done

Później shiftto, co kiedyś było $1, zostało usunięte z listy (ale już je zapisaliśmy $first_arg), a to, co kiedyś było, $2jest teraz $1. Parametry pozycji zostały przesunięte 1 w lewo (użyj, shift 2aby przesunąć o 2 ...). Zasadniczo nasza pętla zapętla się od czegoś, co było drugim argumentem do ostatniego.

Z bash(i zsha ksh93, ale to wszystko), alternatywą jest do zrobienia:

for i in "${@:2}"
do something with "$i"
done

Należy jednak pamiętać, że nie jest to standardowa shskładnia, dlatego nie należy jej używać w skrypcie rozpoczynającym się od #! /bin/sh -.

W zshlub yashmożesz także:

for i in "${@[3,-3]}"
do something with "$i"
done

zapętlić od trzeciego do trzeciego argumentu.

W zsh, $@jest również znany jako $argvtablica. Aby wstawiać elementy od początku lub końca tablic, możesz także:

argv[1,3]=() # remove the first 3 elements
argv[-3,-1]=()

( shiftMoże być również napisany 1=()w zsh)

W bash, można przypisać tylko do $@elementów z setwbudowanego polecenia, tak aby pop 3 elementy z końca, to byłoby coś takiego:

set -- "${@:1:$#-3}"

I aby przejść od 3 do 3 ostatniego:

for i in "${@:3:$#-5}"
do something with "$i"
done

POSIXly, aby wyskoczyć 3 ostatnie elementy "$@", musisz użyć pętli:

n=$(($# - 3))
for arg do
  [ "$n" -gt 0 ] && set -- "$@" "$arg"
  shift
  n=$((n - 1))
done
Stéphane Chazelas
źródło
2
Alternatywna (i brzydka) możliwość for ((i=2; i<=$#; i++)); do something with "${!i}"; done
uderzenia
Jestem bardziej zaznajomiony z tą wersją, ponieważ jestem bardziej zaznajomiony z c ++ :)
user40780
10

Myślę, że chcesz shiftwbudowany. To zmienia nazwę $2na $1, $3na $2, itd.

Lubię to:

shift
for i in "$@"; do
    echo $i
done
Jan
źródło
czy mógłbyś bardziej szczegółowo wyjaśnić, jak to osiągnąć w pętli for? Dziękuję Ci.
user40780,
1
Nie używasz - używasz go przed wejściem w forpętlę, a następnie po prostu przechodzisz przez $ @ normalnie. Po shiftpołączeniu $ @ powinno byćarg2_1 arg2_2 arg2_3...
John
Mam jednak jeszcze jedno pytanie: Załóżmy, że chcę zapętlić od 1 $ do $ ($ # - 2) (tj. Arg_1 do arg_2 _ # - 1, z wyjątkiem arg_2 _ #) ... Co powinienem zrobić?
user40780,
2

Zawsze jest podejście jaskiniowca:

first=1
for i
do
        if [ "$first" ]
        then
                first=
                continue
        fi
        something with "$i"
done

Pozostawia to $@nienaruszone (na wypadek, gdybyś chciał użyć go później) i po prostu zapętla każdy argument, ale nie przetwarza pierwszego.

Scott
źródło
1

W bash możesz również napisać tę pętlę z jawnym indeksowaniem:

for ((i=2; i<=$#; ++i)); do
  process "${!i}"
done

Powtarza to wszystkie argumenty od drugiego do ostatniego. Jeśli zamiast tego chcesz wykluczyć ostatni argument, po prostu to zrób

for ((i=1; i<=$#-1; ++i)); do
  process "${!i}"
done

a jeśli chcesz wziąć co drugi argument, napisz go jako

for ((i=1; i<=$#; i+=2)); do
  process "${!i}"
done

Historia tego polega na arytmetycznej wersji forwbudowanego , w połączeniu z liczbą argumentów$# i zmienną pośrednią${…} .

Jedną fajną aplikacją jest to, że możesz użyć tego, aby zdecydować w pętli, czy dana opcja będzie przyjmować argument następujący po niej jako wartość. Jeśli tak, zwiększ i(np. Zapis : $((++i))), aby użyć następnej wartości i pomiń ją podczas iteracji.

MvG
źródło