In các đối số shell theo thứ tự ngược lại


12

Tôi hơi bế tắc. Nhiệm vụ của tôi là in các đối số vào tập lệnh của mình theo thứ tự ngược lại ngoại trừ thứ ba và thứ tư.

Những gì tôi có là mã này:

#!/bin/bash

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

Vì tôi ghét eval (lời chào đến PHP), tôi đang tìm kiếm một giải pháp mà không có nó nhưng tôi không thể tìm thấy nó.

Làm thế nào tôi có thể xác định vị trí của đối số một cách linh hoạt?

PS: Không phải bài tập về nhà, tôi đang học bài kiểm tra nên tôi cố gắng giải các bài kiểm tra cũ.

Câu trả lời:


13

evallà cách di động duy nhất để truy cập một tham số vị trí theo vị trí được chọn động của nó. Kịch bản của bạn sẽ rõ ràng hơn nếu bạn lặp lại rõ ràng trên chỉ mục chứ không phải các giá trị (mà bạn không sử dụng). Lưu ý rằng bạn không cần exprtrừ khi bạn muốn tập lệnh của mình chạy trong trình bao Bourne cổ; $((…))số học là trong POSIX. Hạn chế sử dụng evalđến các mảnh nhỏ nhất có thể; ví dụ: không sử dụng eval echo, gán giá trị cho biến tạm thời.

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

Trong bash, bạn có thể sử dụng ${!i}để chỉ giá trị của tham số có tên $i. Điều này hoạt động khi $ilà một tham số được đặt tên hoặc một số (biểu thị một tham số vị trí). Trong khi bạn đang ở đó, bạn có thể sử dụng các tính năng tiện lợi khác của bash.

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

Tôi không thể sử dụng argvì chúng được sắp xếp chính xác và không ngược lại. Đối với việc sử dụng expr, tôi chỉ giới hạn sử dụng tiêu chuẩn.
WarrenFaith

1
@WarrenFaith Nếu tập lệnh của bạn bắt đầu #!/bin/bash, bạn có thể sử dụng ${!i}(()). Nếu bạn muốn dính vào sh tiêu chuẩn , những thứ này không có sẵn, nhưng $((…))là.
Gilles 'SO- ngừng trở nên xấu xa'

Ok, tôi nghĩ rằng tôi có thể làm việc với điều này :)
WarrenFaith

Lưu ý rằng các vỏ Bourne cổ sẽ không thể truy cập các tham số vị trí vượt quá $ 9.
Stéphane Chazelas

1
evalkhông chỉ cách cầm tay như Ryan đã thể hiện .
Stéphane Chazelas

7

Tôi giữ một kịch bản reversetrên con đường của tôi mà làm điều này:

#!/bin/sh

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

Ví dụ sử dụng:

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

Bạn cũng có thể sử dụng một chức năng thay vì một tập lệnh chuyên dụng.


Lưu ý rằng một câu trả lời (trái với các câu trả lời khác được đăng cho đến nay) cũng sẽ hoạt động trong trình bao Bourne (không có lý do nào để sử dụng lớp vỏ đó ngày nay)
Stéphane Chazelas

@ StéphaneChazelas: Tại sao báo giá $#? Nó có thể là bất cứ điều gì khác ngoài một số nguyên không âm?
G-Man nói 'Phục hồi Monica'

2
@ G-Man, để lại một biến không được trích dẫn trong ngữ cảnh danh sách đang gọi toán tử split + global. Không có lý do tại sao bạn muốn gọi nó ở đây. Điều đó không liên quan gì đến nội dung của biến. Xem thêm unix.stackexchange.com/a/171347 cho đến cuối. Cũng lưu ý rằng một số shell (ví dụ: dash, posh) vẫn kế thừa IFS từ môi trường.
Stéphane Chazelas

Tôi thấy rằng, trên Android, tôi đã phải khai báolocal arg=$1
bắt đầu từ

2

Đây là một cách sử dụng chính xác và không nguy hiểm của eval. Bạn hoàn toàn kiểm soát nội dung mà bạn đang evaling.

Nếu nó vẫn mang lại cho bạn cảm giác tồi tệ, thì nếu bạn không quan tâm đến tính di động, bạn có thể sử dụng ${!i}cú pháp gián tiếp của Bash .


vì vậy ${!i}không phải là một phần của cú pháp tiêu chuẩn?
WarrenFaith

Không, đó là một chủ nghĩa Bashism. Dưới đây là các mở rộng tham số POSIX: pubs.opengroup.org/onlinepub/009695399/utilities/ gợi
Shawn J. Goff

2

Giả sử các tham số vị trí không chứa các ký tự dòng mới:

[ "$#" -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).

Kiểm tra:

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

Đầu ra thử nghiệm:

6
5
2
1

1

Với zsh:

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

Oalà cờ mở rộng tham số để sắp xếp các phần tử mảng khi mở rộng trong các chỉ mục mảng đảo ngược .

Để loại trừ 3 và 4:

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

0

Với cơ bản bất kỳ vỏ:

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

Và bạn không cần phải sử dụng subshells. Ví dụ:

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

... bản in ...

c

Và đây là một hàm shell có thể đặt shell aliascho bạn, nó sẽ in các đối số tiến hoặc lùi:

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

Nó không cố lưu trữ các giá trị theo nghĩa đen cho bất kỳ đối số nào, mà thay vào đó, nó đặt một chuỗi như thế này trong args alias:

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

... và do đó, các cửa hàng chỉ tham chiếu đến các tham số ngược và xuôi. Nó sẽ lưu trữ đến một số lượng như được đưa ra như là một đối số. Và do đó, những điều trên aliasđã được tạo ra như sau:

tofro 3

printfHành vi của bị ảnh hưởng dựa trên giá trị trả về của lệnh trước - luôn luôn :là lệnh null và thường là đúng. printfsẽ bỏ qua một nửa số đối số của nó mỗi lần nó in - theo mặc định, sẽ lấy các đối số được in ra từ số nhỏ nhất đến lớn nhất. Tuy nhiên, nếu bạn chỉ làm:

! args

... Nó in chúng ngược lại.

Bởi vì bí danh không lưu trữ bất kỳ giá trị bằng chữ nào, giá trị của nó vẫn ở trạng thái tĩnh trong khi các đối số thực tế có thể thay đổi, nhưng nó vẫn sẽ tham chiếu nhiều nhất có thể. Ví dụ:

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

... mà in ...

one
two
three
three
two
one
two
three


three
two

Nhưng đặt lại bí danh có thể được thực hiện như sau:

tofro 2
args; ! args

... và vì vậy nó in ...

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
không có lời giải thích nào cả, tốt đẹp Downvote từ tôi. Giải thích mã của bạn làm gì và tôi có thể thay đổi suy nghĩ của mình.
WarrenFaith

3
Có thể được đơn giản hóa thànhfor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas

Tôi có bị ảo giác không? Tôi nghĩ rằng tôi đã thấy những từ trong câu hỏi có nội dung "in các đối số ... ngoại trừ thứ ba và thứ tư".
G-Man nói 'Phục hồi Monica'

1
@ G-Man, tiêu đề cho biết "in đối số ngược" mà điều này đang trả lời mà không sử dụng eval. Không bao gồm 3 và 4 từ đó là tầm thường. Tôi không nghĩ rằng các downvote là hợp lý. Nó cũng là tự giải thích (mặc dù như tôi đã nói có thể được đơn giản hóa rất nhiều).
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.