$ @ ยกเว้นอาร์กิวเมนต์ที่ 1


36

ฉันต้องเขียนเชลล์สคริปต์ที่ทำงานด้วยวิธีนี้:

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

มีการวนรอบสำหรับสคริปต์ภายใน

for i in $@

อย่างไรก็ตามอย่างที่ทราบ $ @ ได้รวม $ 1 ถึง $ ($ # - 1) แต่สำหรับโปรแกรมของฉัน $ 1 แตกต่างอย่างชัดเจนจาก $ 2 $ 3 $ 4 ฯลฯ ฉันต้องการวนจาก $ 2 ถึงจุดสิ้นสุด ... ฉันจะทำสิ่งนี้ได้อย่างไร ขอขอบคุณ:)

คำตอบ:


47

ก่อนอื่นโปรดทราบว่า$@หากไม่มีเครื่องหมายอัญประกาศก็ไม่สมเหตุสมผลและไม่ควรใช้ $@ควรใช้เครื่องหมายคำพูด ( "$@") และในบริบทรายการเท่านั้น

for i in "$@" มีคุณสมบัติเป็นบริบทรายการ แต่ที่นี่เพื่อวนซ้ำพารามิเตอร์ตำแหน่งรูปแบบที่ยอมรับได้แบบพกพาและเรียบง่ายที่สุดคือ:

for i
do something with "$i"
done

ตอนนี้การวนซ้ำองค์ประกอบเริ่มต้นจากวิธีที่สองวิธีบัญญัติและแบบพกพามากที่สุดคือการใช้shift:

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

หลังจากนั้นshiftสิ่งที่เคย$1ถูกลบออกจากรายการ (แต่เราได้บันทึกไว้ใน$first_arg) และสิ่งที่เคยเป็นอยู่ใน$2ตอนนี้$1มา พารามิเตอร์ตำแหน่งถูกเลื่อน 1ตำแหน่งไปทางซ้าย (ใช้shift 2เพื่อเลื่อน 2 ... ) โดยพื้นฐานแล้วการวนซ้ำของเรากำลังวนซ้ำจากสิ่งที่เคยเป็นอาร์กิวเมนต์ที่สองถึงครั้งสุดท้าย

ด้วยbash(และzshและksh93แต่ก็เท่านั้น) ทางเลือกอื่นคือ:

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

แต่ทราบว่ามันไม่ได้มาตรฐานไวยากรณ์จึงไม่ควรนำมาใช้ในสคริปต์ที่เริ่มต้นด้วยsh#! /bin/sh -

ในzshหรือyashคุณยังสามารถทำสิ่งต่อไปนี้

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

เพื่อวนซ้ำจากข้อโต้แย้งครั้งที่ 3 ถึงครั้งที่ 3

ในzsh, $@นอกจากนี้ยังเป็นที่รู้จักกันเป็น$argvอาร์เรย์ ดังนั้นเมื่อต้องการอิลิเมนต์ pop จากจุดเริ่มต้นหรือจุดสิ้นสุดของอาร์เรย์คุณยังสามารถทำสิ่งต่อไปนี้

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

( shiftนอกจากนี้ยังสามารถเขียน1=()ในzsh)

ในbashคุณสามารถกำหนด$@องค์ประกอบให้กับsetbuiltin ได้เท่านั้นดังนั้นเมื่อต้องการปิดองค์ประกอบ 3 องค์ประกอบท้ายสุดซึ่งจะเป็นดังนี้:

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

และวนซ้ำจากอันดับ 3 ถึงอันดับ 3

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

POSIXly หากต้องการแสดงองค์ประกอบ 3 รายการสุดท้าย"$@"คุณจะต้องใช้การวนซ้ำ:

n=$(($# - 3))
for arg do
  [ "$n" -gt 0 ] && set -- "$@" "$arg"
  shift
  n=$((n - 1))
done

2
ความเป็นไปได้ทางเลือก (และน่าเกลียด) ทุบตี: ตัวแปรทางอ้อม:for ((i=2; i<=$#; i++)); do something with "${!i}"; done
glenn jackman

ฉันคุ้นเคยกับรุ่นนี้มากขึ้นเนื่องจากฉันคุ้นเคยกับ c ++ :)
40780

10

ฉันคิดว่าคุณต้องการในshiftตัว เปลี่ยนชื่อ$2เป็น$1, $3เป็น$2, ฯลฯ

อย่างนี้:

shift
for i in "$@"; do
    echo $i
done

คุณสามารถอธิบายรายละเอียดเพิ่มเติมได้อย่างไรว่าฉันจะทำสิ่งนั้นให้สำเร็จได้ใน for for loop? ขอขอบคุณ.
user40780

1
คุณทำไม่ได้ - คุณใช้มันก่อนที่จะเข้าสู่forลูปจากนั้นคุณก็วนซ้ำ $ @ ตามปกติ หลังการshiftโทร $ @ ควรเป็นarg2_1 arg2_2 arg2_3...
John

อย่างไรก็ตามฉันจะมีคำถามอีกหนึ่งข้อ: สมมติว่าฉันต้องการวนจาก $ 1 ถึง $ ($ # - 2) (เช่น arg_1 จนถึง arg_2 _ # - 1 ยกเว้น arg_2 _ #) ... ฉันควรทำอย่างไร
user40780

2

มีวิธีมนุษย์ถ้ำเสมอ:

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

สิ่งนี้จะ$@ยังคงอยู่ (ในกรณีที่คุณต้องการใช้ในภายหลัง) และวนซ้ำทุกอาร์กิวเมนต์ แต่จะไม่ประมวลผลข้อแรก


1

ใน bash คุณสามารถเขียนลูปนั้นด้วยการทำดรรชนีที่ชัดเจน:

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

สิ่งนี้วนซ้ำอาร์กิวเมนต์ทั้งหมดจากอันที่สองไปเป็นอันสุดท้าย หากคุณต้องการยกเว้นอาร์กิวเมนต์สุดท้ายแทนเพียงทำสิ่งนี้

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

และถ้าคุณต้องการที่จะโต้แย้งกันเขียนเป็น

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

เรื่องราวที่อยู่เบื้องหลังนี้คือคณิตศาสตร์รุ่นของforbuiltinรวมกับอาร์กิวเมนต์นับ$#และร้ายตัวแปร${…}

แอปพลิเคชั่นที่ดีอย่างหนึ่งคือคุณสามารถใช้สิ่งนี้เพื่อตัดสินใจภายในลูปไม่ว่าตัวเลือกที่กำหนดจะใช้อาร์กิวเมนต์ที่ตามมาเป็นค่าหรือไม่ ถ้าเป็นเช่นนั้นให้เพิ่มi(เช่นการเขียน: $((++i))) เพื่อใช้ค่าถัดไปและข้ามไปในระหว่างการทำซ้ำ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.