พิมพ์อาร์กิวเมนต์ของเชลล์ในลำดับย้อนกลับ


12

ฉันติดอยู่นิดหน่อย งานของฉันคือการพิมพ์อาร์กิวเมนต์ไปยังสคริปต์ของฉันในลำดับย้อนกลับยกเว้นที่สามและสี่

สิ่งที่ฉันมีคือรหัสนี้:

#!/bin/bash

i=$#
for arg in "$@"
do
    case $i
    in
        3) ;;
        4) ;;
        *) eval echo "$i. Parameter: \$$i";;
    esac
    i=`expr $i - 1`
done

ในขณะที่ฉันเกลียด eval (ทักทาย PHP) ฉันกำลังมองหาวิธีการแก้ปัญหาโดยไม่ได้ทำ แต่ฉันไม่สามารถหามันได้

ฉันจะกำหนดตำแหน่งของการโต้แย้งแบบไดนามิกได้อย่างไร

PS: มันไม่ใช่การบ้านฉันกำลังเรียนรู้การทำข้อสอบดังนั้นฉันจึงพยายามแก้ข้อสอบเก่า

คำตอบ:


13

evalเป็นวิธีพกพาเพียงวิธีเดียวในการเข้าถึงพารามิเตอร์ตำแหน่งโดยตำแหน่งที่เลือกแบบไดนามิก สคริปต์ของคุณจะชัดเจนขึ้นหากคุณวนลูปที่ดัชนีอย่างชัดเจนมากกว่าค่า (ซึ่งคุณไม่ได้ใช้) โปรดทราบว่าคุณไม่ต้องการexprเว้นแต่ว่าคุณต้องการให้สคริปต์ของคุณทำงานในเชลล์บอร์นโบราณ $((…))เลขคณิตอยู่ใน POSIX จำกัด การใช้evalส่วนที่เล็กที่สุดเท่าที่จะเป็นไปได้ ตัวอย่างเช่นไม่ใช้eval echoกำหนดค่าให้กับตัวแปรชั่วคราว

i=$#
while [ "$i" -gt 0 ]; do
  if [ "$i" -ne 3 ] && [ "$i" -ne 2 ]; then
    eval "value=\${$i}"
    echo "Parameter $i is $value"
  fi
  i=$((i-1))
done

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

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done

ฉันไม่สามารถใช้argเนื่องจากมีการสั่งซื้ออย่างถูกต้องและไม่ย้อนกลับ เพื่อการใช้งานของexprฉันถูก จำกัด ให้ใช้มาตรฐานเท่านั้น
WarrenFaith

1
@WarrenFaith หากสคริปต์ของคุณเริ่มต้นด้วย#!/bin/bashคุณสามารถใช้และ${!i} (())หากคุณต้องการที่จะยึดมาตรฐาน sh เหล่านี้ไม่สามารถใช้ได้ แต่$((…))เป็น
Gilles 'หยุดความชั่วร้าย'

ตกลงฉันคิดว่าฉันสามารถทำงานกับสิ่งนี้ได้ :)
WarrenFaith

โปรดทราบว่ากระสุน Bourne แบบโบราณจะไม่สามารถเข้าถึงพารามิเตอร์ตำแหน่งเกินกว่า $ 9 ได้
Stéphane Chazelas

1
evalจะไม่ได้เป็นวิธีเดียวที่พกพาขณะที่ไรอันได้แสดงให้เห็น
Stéphane Chazelas

7

ฉันเก็บสคริปต์reverseบนเส้นทางของฉันที่ทำสิ่งนี้:

#!/bin/sh

if [ "$#" -gt 0 ]; then
    arg=$1
    shift
    reverse "$@"
    printf '%s\n' "$arg"
fi

ตัวอย่างการใช้งาน:

$ reverse a b c '*' '-n'
-n
*
c
b
a

คุณยังสามารถใช้ฟังก์ชันแทนสคริปต์เฉพาะ


โปรดทราบว่าหนึ่ง (ตรงกันข้ามกับคำตอบอื่น ๆ ที่โพสต์จนถึง) จะทำงานในเชลล์เป้าหมาย (ไม่ว่ามีเหตุผลใดที่จะใช้เชลล์นั้นทุกวันนี้)
Stéphane Chazelas

@ StéphaneChazelas: ทำไมต้องพูด$#? มันจะเป็นอย่างอื่นที่ไม่ใช่จำนวนเต็มลบหรือไม่?
G-Man กล่าวว่า 'Reinstate Monica'

2
@ G-Man การปล่อยตัวแปรที่ไม่ได้ระบุไว้ในบริบทรายการจะเรียกใช้ตัวดำเนินการแยก + glob ไม่มีเหตุผลที่คุณต้องการเรียกใช้ที่นี่ ที่ไม่เกี่ยวข้องกับเนื้อหาของตัวแปร ดูunix.stackexchange.com/a/171347 ที่ส่วนท้ายด้วย โปรดทราบว่าเชลล์บางตัว (เส้นประ, ตัวอย่างที่ดี) ยังคงสืบทอด IFS จากสภาพแวดล้อม
Stéphane Chazelas

ฉันพบว่าบน Android ฉันต้องประกาศlocal arg=$1
starfry

2

นี่คือการใช้ eval ที่ถูกต้องและไม่เป็นอันตราย คุณอย่างเต็มที่ควบคุมเนื้อหาที่คุณevalไอเอ็นจี

ถ้ามันยังให้ความรู้สึกไม่ดีถ้าคุณไม่สนใจเรื่องการพกพาคุณสามารถใช้${!i}ไวยากรณ์ทางอ้อมของ Bash


ดังนั้น${!i}ไม่ได้เป็นส่วนหนึ่งของไวยากรณ์มาตรฐานหรือไม่
WarrenFaith

ไม่มันเป็นความนิยม นี่คือการขยายพารามิเตอร์ POSIX: pubs.opengroup.org/onlinepubs/009695399/utilities/ …
Shawn J. Goff

2

สมมติว่าพารามิเตอร์ตำแหน่งไม่มีอักขระขึ้นบรรทัดใหม่:

[ "$#" -gt 0 ] && printf '%s\n' "$@" | #print in order
sed '3,4 d' |                          #skip 3 and 4
tac                                    #reverse (try tail -r on
                                       #non-GNU systems).

ทดสอบ:

set 1 2 3 4 5 6
printf '%s\n' "$@" | 
sed '3,4 d' |
tac

ผลการทดสอบ:

6
5
2
1

1

ด้วยzsh:

$ set a 'foo bar' c '' '*'
$ printf '<%s>\n' "${(Oa)@}"
<*>
<>
<c>
<foo bar>
<a>

Oaเป็นแฟล็กการขยายพารามิเตอร์เพื่อเรียงลำดับอิลิเมนต์อาร์เรย์ตามการขยายในดัชนีอาเรย์ย้อนกลับ

หากต้องการยกเว้น 3 และ 4:

$ printf '<%s>\n' "${(Oa)@[5,-1]}" "${(Oa)@[1,2]}"
<*>
<foo bar>
<a>

0

ด้วยพื้นเปลือกใด ๆ :

printf '{ PS4=\${$(($#-$x))}; } 2>&3; 2>&1\n%.0s' |
x=LINENO+1 sh -sx "$@" 3>/dev/null

และคุณไม่จำเป็นต้องใช้ subshells ตัวอย่างเช่น:

set -x a b c
{ last= PS4=\${last:=\${$#}}; set +x; } 2>/dev/null
echo "$last"

... พิมพ์ ...

c

และนี่คือฟังก์ชั่นเชลล์ที่สามารถตั้งค่าเชลล์aliasสำหรับคุณที่จะพิมพ์อาร์กิวเมนต์ทั้งไปข้างหน้าหรือข้างหลัง:

tofro() case $1 in (*[!0-9]*|'') ! :;;(*) set "$1"
        until   [ "$1" -eq "$(($#-1))" ]    &&
                shift && alias args=":; printf \
            \"%.\$((\$??\${#*}:0))s%.\$((!\$??\${#*}:0))s\n\" $* "
        do      [ "$#" -gt 1 ] &&
                set "$@ \"\${$#}\" " '"${'"$((1+$1-$#))"'}"' ||
                set "$1" '"$1" "${'"$1"'}"'
        done;   esac

มันไม่ได้พยายามที่จะเก็บค่าที่แท้จริงสำหรับการขัดแย้งใด ๆ แต่มันจะทำให้สตริงเช่นนี้ในargs alias:

:;printf    "%.$(($??${#*}:0))s%.$((!$??${#*}:0))s\n" \
            "$1" "${3}" "${2}"  "${2}" "${3}"  "${1}"

... และเก็บเฉพาะการอ้างอิงพารามิเตอร์ย้อนหลังและไปข้างหน้า มันจะเก็บได้ถึงจำนวนที่ได้รับมันเป็นข้อโต้แย้ง และดังนั้นสิ่งที่aliasถูกสร้างขึ้นเช่น:

tofro 3

printfพฤติกรรมของได้รับผลกระทบโดยขึ้นอยู่กับค่าส่งคืนของคำสั่งก่อนหน้าซึ่งมักจะ:เป็นคำสั่ง null และมักจะเป็นจริง printfจะข้ามข้อโต้แย้งไปครึ่งหนึ่งในแต่ละครั้งที่พิมพ์ซึ่งตามค่าเริ่มต้นจะรับอาร์กิวเมนต์ที่พิมพ์จากหมายเลขที่เล็กที่สุดไปหามากที่สุด อย่างไรก็ตามถ้าคุณทำ:

! args

... มันพิมพ์กลับด้าน

เนื่องจากนามแฝงไม่ได้เก็บค่าตามตัวอักษรใด ๆ ค่าของมันจะยังคงอยู่ในขณะที่ args ที่แท้จริงอาจมีการเปลี่ยนแปลง แต่มันจะยังคงอ้างอิงมากที่สุดเท่าที่จะทำได้ ตัวอย่างเช่น:

set one two three
tofro 3
args; ! args
shift; args; ! args

... ที่พิมพ์ ...

one
two
three
three
two
one
two
three


three
two

แต่การรีเซ็ตนามแฝงสามารถทำได้เช่น:

tofro 2
args; ! args

... และมันจะพิมพ์ ...

two
three
three
two

-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done

2
ไม่มีคำอธิบายเลยดี ลงคะแนนจากฉัน อธิบายสิ่งที่รหัสของคุณทำและฉันอาจเปลี่ยนใจ
WarrenFaith

3
สามารถทำให้ง่ายขึ้นfor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas

ฉันกำลังหลอน ฉันคิดว่าฉันเห็นคำในคำถามที่กล่าวว่า "พิมพ์อาร์กิวเมนต์ ... ยกเว้นข้อสามและข้อสี่"
G-Man กล่าวว่า 'Reinstate Monica'

1
@ G-Man ชื่อกล่าวว่า "อาร์กิวเมนต์การพิมพ์ในสิ่งที่ตรงกันข้าม" ซึ่งเป็นการตอบกลับโดยไม่ใช้ eval ไม่รวม 3 และ 4 จากสิ่งเล็กน้อย ฉันไม่คิดว่า downvotes นั้นเป็นธรรม นอกจากนี้ยังอธิบายด้วยตนเอง (แต่อย่างที่ฉันบอกว่าสามารถทำให้ง่ายขึ้นอย่างมาก)
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.