Argumen shell cetak dalam urutan terbalik

12

Saya agak macet. Tugas saya adalah mencetak argumen ke skrip saya dalam urutan terbalik kecuali yang ketiga dan keempat.

Apa yang saya miliki adalah kode ini:

#!/bin/bash

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

Karena saya benci eval (salam ke PHP), saya mencari solusi tanpa itu tetapi saya tidak dapat menemukannya.

Bagaimana saya bisa mendefinisikan posisi argumen secara dinamis?

PS: Bukan itu bukan pekerjaan rumah, saya sedang belajar shell untuk ujian jadi saya mencoba menyelesaikan ujian lama.

Warren Faith
sumber

Jawaban:

13

evaladalah satu-satunya cara portabel untuk mengakses parameter posisi dengan posisinya yang dipilih secara dinamis. Script Anda akan lebih jelas jika Anda secara eksplisit mengulangi indeks daripada nilai-nilai (yang tidak Anda gunakan). Perhatikan bahwa Anda tidak perlu exprkecuali Anda ingin skrip Anda berjalan di kulit Bourne antik; $((…))aritmatika ada dalam POSIX. Batasi penggunaan evalhingga fragmen sekecil mungkin; misalnya, jangan gunakan eval echo, tetapkan nilai ke variabel sementara.

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

Di bash, Anda bisa menggunakan ${!i}nilai parameter yang namanya $i. Ini berfungsi ketika $iparameter atau nomor bernama (menunjukkan parameter posisi). Saat Anda melakukannya, Anda dapat menggunakan fitur kenyamanan bash lainnya.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Saya tidak bisa menggunakan argkarena mereka dipesan dengan benar dan tidak terbalik. Untuk penggunaan expr, saya terbatas hanya menggunakan standar.
WarrenFaith
1
@ WarrenFaith Jika skrip Anda dimulai dengan #!/bin/bash, Anda dapat menggunakan ${!i}dan (()). Jika Anda ingin tetap menggunakan sh standar , ini tidak tersedia, tetapi tersedia $((…)).
Gilles 'SO- stop being evil'
Oke, saya rasa saya bisa
mengatasi
Perhatikan bahwa cangkang Bourne antik tidak akan dapat mengakses parameter posisi melebihi $ 9.
Stéphane Chazelas
1
evaladalah tidak satu-satunya cara portabel seperti Ryan telah menunjukkan .
Stéphane Chazelas
7

Saya menyimpan skrip reversedi jalur saya yang melakukan ini:

#!/bin/sh

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

Contoh penggunaan:

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

Anda juga dapat menggunakan fungsi alih-alih skrip khusus.

Ryan Williams
sumber
Perhatikan bahwa satu (bertentangan dengan jawaban lain yang diposting sejauh ini) juga akan berfungsi di Bourne shell (bukan berarti tidak ada alasan untuk menggunakan shell itu saat ini)
Stéphane Chazelas
@ StéphaneChazelas: Mengapa mengutip $#? Bisakah itu selain bilangan bulat non-negatif?
G-Man Mengatakan 'Reinstate Monica'
2
@ G-Man, membiarkan variabel yang tidak dikutip dalam konteks daftar meminta operator split + glob. Tidak ada alasan mengapa Anda ingin memohonnya di sini. Itu tidak ada hubungannya dengan konten variabel. Lihat juga unix.stackexchange.com/a/171347 menjelang akhir. Perhatikan juga bahwa beberapa shell (tanda hubung, posh misalnya) masih mewarisi IFS dari lingkungan.
Stéphane Chazelas
Saya menemukan bahwa, di Android, saya harus menyatakanlocal arg=$1
starfry
2

Ini adalah penggunaan eval yang benar dan tidak berbahaya. Anda sepenuhnya mengontrol konten yang sedang Anda gunakan eval.

Jika masih memberi Anda perasaan buruk, maka jika Anda tidak peduli tentang portabilitas, Anda dapat menggunakan ${!i}sintaks Bir's indirection.

Shawn J. Goff
sumber
jadi ${!i}bukankah bagian dari sintaks standar?
WarrenFaith
Tidak, ini Bashism. Berikut adalah ekspansi parameter POSIX: pubs.opengroup.org/onlinepubs/009695399/utilities/…
Shawn J. Goff
2

Dengan asumsi parameter posisi tidak mengandung karakter baris baru:

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

Uji:

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

Hasil tes:

6
5
2
1
PSkocik
sumber
1

Dengan zsh:

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

Oaadalah flag ekspansi parameter untuk mengurutkan elemen array setelah ekspansi dalam indeks array terbalik .

Untuk mengecualikan 3 dan 4:

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

Dengan dasarnya semua shell:

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

Dan Anda tidak perlu menggunakan subkulit. Sebagai contoh:

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

... mencetak ...

c

Dan di sini adalah fungsi shell yang dapat mengatur shell aliasuntuk Anda yang akan mencetak argumen maju atau mundur:

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

Itu tidak berusaha untuk menyimpan nilai-nilai literal untuk argumen apa pun, melainkan menempatkan string seperti ini di args alias:

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

... dan hanya menyimpan referensi ke parameter mundur dan maju. Ini akan menyimpan hingga hitungan sebagaimana diberikan sebagai argumen. Maka di atas aliasdihasilkan seperti:

tofro 3

printfPerilaku dipengaruhi berdasarkan nilai kembali dari perintah sebelumnya - yang selalu :merupakan perintah nol, dan biasanya benar. printfakan melewatkan setengah dari argumennya setiap kali ia mencetak - yang, secara default, akan mendapatkan argumen yang dicetak dari yang paling kecil ke yang terbesar. Namun, jika Anda hanya melakukannya:

! args

... itu mencetaknya secara terbalik.

Karena alias tidak menyimpan nilai literal apa pun, nilainya tetap statis sementara arg yang sebenarnya mungkin berubah, tetapi masih akan merujuk sebanyak mungkin. Sebagai contoh:

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

... yang mencetak ...

one
two
three
three
two
one
two
three


three
two

Tetapi mengatur ulang alias bisa dilakukan seperti:

tofro 2
args; ! args

... dan itu mencetak ...

two
three
three
two
mikeserv
sumber
-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done
pengguna119141
sumber
2
tidak ada penjelasan sama sekali, bagus. Downvote dari saya. Jelaskan apa yang kode Anda lakukan dan saya mungkin berubah pikiran.
WarrenFaith
3
Dapat disederhanakan untukfor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas
Apakah saya berhalusinasi? Saya pikir saya melihat kata-kata dalam pertanyaan yang mengatakan "cetak argumen ... kecuali yang ketiga dan keempat".
G-Man Mengatakan 'Reinstate Monica'
1
@ G-Man, judulnya bertuliskan "cetak argumen secara terbalik" yang dijawab tanpa menggunakan eval. Tidak termasuk 3 dan 4 dari itu sepele. Saya tidak berpikir downvotes dibenarkan. Ini juga cukup jelas (meskipun seperti yang saya katakan bisa disederhanakan banyak).
Stéphane Chazelas