$ @ kecuali argumen 1

36

Saya perlu menulis skrip shell yang berjalan dengan cara ini:

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

ada untuk loop di dalam skrip

for i in $@

Namun, seperti yang saya tahu, $ @ mencakup $ 1 hingga $ ($ # - 1). Tetapi untuk program saya $ 1 jelas berbeda dari $ 2 $ 3 $ 4 dll. Saya ingin mengulang dari $ 2 sampai akhir ... Bagaimana cara mencapainya? Terima kasih:)

pengguna40780
sumber

Jawaban:

47

Pertama, perhatikan bahwa $@tanpa tanda kutip tidak masuk akal dan tidak boleh digunakan. $@seharusnya hanya digunakan dikutip ( "$@") dan dalam konteks daftar.

for i in "$@" memenuhi syarat sebagai konteks daftar, tetapi di sini, untuk mengulang parameter posisi, bentuk kanonik, paling portabel dan lebih sederhana adalah:

for i
do something with "$i"
done

Sekarang, untuk mengulang elemen mulai dari yang kedua, cara kanonik dan paling portabel adalah dengan menggunakan shift:

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

Setelah itu shift, apa yang dulu $1telah dihapus dari daftar (tapi kami telah menyimpannya $first_arg) dan apa yang dulu ada di $2sekarang $1. Parameter posisi telah digeser 1 posisi ke kiri (gunakan shift 2untuk menggeser oleh 2 ...). Jadi pada dasarnya, perulangan kita adalah perulangan dari apa yang dulunya adalah argumen kedua ke argumen terakhir.

Dengan bash(dan zshdan ksh93, tapi hanya itu), alternatifnya adalah dengan melakukan:

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

Tetapi perhatikan bahwa itu bukan shsintaks standar sehingga tidak boleh digunakan dalam skrip yang dimulai #! /bin/sh -.

Di zshatau yash, Anda juga dapat melakukan:

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

untuk mengulang dari argumen terakhir ke 3 ke 3.

In zsh, $@juga dikenal sebagai $argvarray. Jadi untuk memunculkan elemen dari awal atau akhir array, Anda juga dapat melakukan:

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

( shiftdapat juga ditulis 1=()dalam zsh)

Di bash, Anda hanya dapat menetapkan $@elemen dengan setbuiltin, jadi untuk menghilangkan 3 elemen pada akhirnya, itu akan menjadi seperti:

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

Dan untuk mengulang dari 3 ke 3 terakhir:

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

POSIXly, untuk memunculkan 3 elemen terakhir "$@", Anda harus menggunakan loop:

n=$(($# - 3))
for arg do
  [ "$n" -gt 0 ] && set -- "$@" "$arg"
  shift
  n=$((n - 1))
done
Stéphane Chazelas
sumber
2
Alternatif kemungkinan bash (dan jelek): variabel tidak langsung:for ((i=2; i<=$#; i++)); do something with "${!i}"; done
glenn jackman
Saya lebih akrab dengan versi ini, karena saya lebih terbiasa dengan c ++ :)
user40780
10

Saya pikir Anda ingin shiftbuiltin. Itu mengubah nama $2menjadi $1, $3untuk $2, dll.

Seperti ini:

shift
for i in "$@"; do
    echo $i
done
John
sumber
dapatkah Anda menjelaskan secara lebih rinci bagaimana cara mencapainya dalam for for loop? Terima kasih.
user40780
1
Anda tidak - Anda menggunakannya sebelum memasukkan forloop, maka Anda hanya mengulang $ $ secara normal. Setelah shiftpanggilan, $ @ seharusnyaarg2_1 arg2_2 arg2_3...
John
Namun, saya akan memiliki satu pertanyaan lagi: Misalkan saya ingin mengulang dari $ 1 hingga $ ($ # - 2) (yaitu arg_1 hingga arg_2 _ # - 1, kecuali arg_2 _ #) ... Apa yang harus saya lakukan?
user40780
2

Selalu ada pendekatan manusia gua:

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

Ini $@tetap utuh (jika Anda ingin menggunakannya nanti), dan cukup simpulkan setiap argumen, tetapi tidak memproses yang pertama.

Scott
sumber
1

Di bash Anda juga bisa menulis loop itu dengan pengindeksan eksplisit:

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

Ini mengulangi semua argumen dari yang kedua ke yang terakhir. Jika Anda ingin mengecualikan argumen terakhir, cukup buat ini

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

dan jika Anda hanya ingin mengambil setiap argumen lain, tulis sebagai

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

Kisah di balik ini adalah versi aritmatika dari forbuiltin , dikombinasikan dengan argumen-count$# dan tipuan variabel${…} .

Satu aplikasi bagus adalah Anda bisa menggunakan ini untuk memutuskan, di dalam loop, apakah opsi yang diberikan akan mengambil argumen yang mengikutinya sebagai nilai. Jika ya, tambahkan i(misalnya menulis : $((++i))) untuk menggunakan nilai berikutnya dan lewati selama iterasi.

MvG
sumber