Mendapatkan argumen terakhir diteruskan ke skrip shell

272

$1 adalah argumen pertama.
$@adalah mereka semua.

Bagaimana saya bisa menemukan argumen terakhir yang diteruskan ke skrip shell?

Thomas
sumber
Saya menggunakan bash, tetapi semakin banyak solusi portabel semakin baik.
Thomas
10
gunakan juga dapat menggunakan $ {! #}
Prateek Joshi
11
Untuk bash saja , jawaban Kevin Little mengusulkan yang sederhana ${!#}. Uji dengan menggunakan bash -c 'echo ${!#}' arg1 arg2 arg3. Untuk bash , ksh dan zsh , jawaban Dennis Williamson mengusulkan ${@: -1}. Apalagi ${*: -1}bisa juga digunakan. Uji dengan menggunakan zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Tapi itu tidak berhasil untuk dash , csh dan tcsh .
olibre
2
${!#}, tidak seperti ${@: -1}, juga berfungsi dengan ekspansi parameter. Anda dapat mengujinya dengan bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton

Jawaban:

177

Ini sedikit hack:

for last; do true; done
echo $last

Yang ini juga cukup portabel (sekali lagi, harus bekerja dengan bash, ksh dan sh) dan tidak menggeser argumen, yang bisa bagus.

Ia menggunakan fakta yang forsecara implisit memotong argumen jika Anda tidak memberi tahu apa yang harus diulang, dan fakta bahwa untuk variabel loop tidak dibatasi: mereka menyimpan nilai terakhir yang ditetapkan.

Laurence Gonsalves
sumber
10
@ MichałŠrajer, saya pikir Anda bermaksud usus besar dan bukan koma;)
Paweł Nadolski
2
@ MichałŠrajer trueadalah bagian dari POSIX .
Rufflewind
4
Dengan Solaris lama, dengan shell bourne lama (bukan POSIX), saya harus menulis "untuk yang terakhir di" $ @ "; lakukan benar; selesai"
mcoolive
8
@mcoolive @LaurenceGolsalves selain lebih portabel, for last in "$@"; do :; donejuga membuat maksudnya lebih jelas.
Adrian Günter
1
@ mcoolive: ia bahkan bekerja di shell bourne unix v7 dari tahun 1979. Anda tidak bisa mendapatkan lebih portabel jika berjalan di kedua v7 sh dan POSIX sh :)
Dominik R
291

Ini hanya untuk Bash:

echo "${@: -1}"
Dijeda sampai pemberitahuan lebih lanjut.
sumber
43
Bagi mereka (seperti saya) yang bertanya-tanya mengapa ruang dibutuhkan, man bash mengatakan ini tentang itu:> Perhatikan bahwa offset negatif harus dipisahkan dari usus besar dengan setidaknya satu ruang untuk menghindari kebingungan dengan: - ekspansi.
foo
1
Saya telah menggunakan ini dan rusak di MSYS2 bash di windows saja. Aneh.
Steven Lu
2
Catatan: Jawaban ini berfungsi untuk semua array Bash, tidak seperti ${@:$#}yang hanya berfungsi $@. Jika Anda menyalin $@ke array baru dengan arr=("$@"), ${arr[@]:$#}tidak akan ditentukan. Ini karena $@memiliki elemen ke-0 yang tidak termasuk dalam "$@"ekspansi.
Tn. Llama
@ Mr.Llama: Tempat lain untuk menghindari $#adalah ketika mengulangi array karena, di Bash, array jarang dan sementara $#akan menunjukkan jumlah elemen dalam array itu tidak selalu menunjuk ke elemen terakhir (atau elemen +1). Dengan kata lain, seseorang seharusnya tidak melakukan for ((i = 0; i++; i < $#)); do something "${array[$i]}"; donedan sebaliknya melakukan for element in "${array[@]}"; do something "$element"; doneatau mengulangi indeks: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; donejika Anda perlu melakukan sesuatu dengan nilai-nilai indeks.
Dijeda sampai pemberitahuan lebih lanjut.
80
$ set quick brown fox jumps

$ echo ${*: -1:1} # last argument
jumps

$ echo ${*: -1} # or simply
jumps

$ echo ${*: -2:1} # next to last
fox

Ruang tersebut diperlukan agar tidak bisa ditafsirkan sebagai nilai default .

Perhatikan bahwa ini hanya untuk bash.

Steven Penny
sumber
9
Jawaban terbaik, karena itu juga mencakup argumen terakhir. Terima kasih!
e40
1
Steven, aku tidak tahu apa yang kamu lakukan untuk mendarat di Penalty Box, tapi aku suka pekerjaan kamu di sini.
Bruno Bronosky
4
Iya. hanya yang terbaik. semua kecuali perintah dan argumen terakhir ${@: 1:$#-1}
Dyno Fu
1
@DynoFu terima kasih untuk itu, Anda menjawab pertanyaan saya berikutnya. Jadi pergeseran mungkin terlihat seperti echo ${@: -1} ${@: 1:$#-1}
Mike
73

Jawaban paling sederhana untuk bash 3.0 atau lebih tinggi adalah

_last=${!#}       # *indirect reference* to the $# variable
# or
_last=$BASH_ARGV  # official built-in (but takes more typing :)

Itu dia.

$ cat lastarg
#!/bin/bash
# echo the last arg given:
_last=${!#}
echo $_last
_last=$BASH_ARGV
echo $_last
for x; do
   echo $x
done

Output adalah:

$ lastarg 1 2 3 4 "5 6 7"
5 6 7
5 6 7
1
2
3
4
5 6 7
Kevin Little
sumber
1
$BASH_ARGVtidak berfungsi di dalam fungsi bash (kecuali saya melakukan sesuatu yang salah).
Big McLargeHuge
1
BASH_ARGV memiliki argumen ketika bash dipanggil (atau ke suatu fungsi) bukan daftar argumen posisi saat ini.
Isaac
Perhatikan juga apa yang BASH_ARGVakan menghasilkan Anda adalah nilai yang diberikan arg terakhir, alih-alih sekadar "nilai terakhir". Misalnya !: jika Anda memberikan satu argumen tunggal, maka Anda memanggil shift, tidak ${@:$#}akan menghasilkan apa-apa (karena Anda menggeser argumen satu-satunya!), Namun, BASH_ARGVmasih akan memberi Anda argumen terakhir (sebelumnya).
Steven Lu
30

Gunakan pengindeksan dikombinasikan dengan panjang:

echo ${@:${#@}} 

Perhatikan bahwa ini hanya untuk bash.

Mark Byers
sumber
24
Lebih pendek:echo ${@:$#}
Dijeda sampai pemberitahuan lebih lanjut.
1
echo $ (@ & $; & ^ $ &% @ ^! @% ^ # ** # & @ * # @ * # @ (& * # _ ** ^ @ ^ & (^ @ & * & @)
Atul
29

Berikut ini akan bekerja untuk Anda. @ Adalah untuk berbagai argumen. : berarti pada. $ # adalah panjang dari array argumen. Jadi hasilnya adalah elemen terakhir:

${@:$#} 

Contoh:

function afunction{
    echo ${@:$#} 
}
afunction -d -o local 50
#Outputs 50

Perhatikan bahwa ini hanya untuk bash.

Tahsin Turkoz
sumber
Sementara contohnya adalah untuk suatu fungsi, skrip juga bekerja dengan cara yang sama. Saya suka jawaban ini karena jelas dan ringkas.
Jonah Braun
1
Dan itu bukan hacky. Ini menggunakan fitur eksplisit bahasa, bukan efek samping atau qwerks khusus. Ini harus menjadi jawaban yang diterima.
musicin3d
25

Menemukan ini ketika mencari untuk memisahkan argumen terakhir dari semua argumen sebelumnya. Sementara beberapa jawaban mendapatkan argumen terakhir, mereka tidak banyak membantu jika Anda membutuhkan semua argumen lainnya juga. Ini bekerja lebih baik:

heads=${@:1:$#-1}
tail=${@:$#}

Perhatikan bahwa ini hanya untuk bash.

AgileZebra
sumber
3
The Steven Penny jawabannya adalah sedikit lebih bagus: digunakan ${@: -1}untuk terakhir dan ${@: -2:1}untuk detik terakhir (dan seterusnya ...). Contoh: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6cetakan 6. Untuk tetap menggunakan pendekatan AgileZebra saat ini, gunakan ${@:$#-1:1}untuk mendapatkan yang kedua terakhir . Contoh: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6cetakan 5. (dan ${@:$#-2:1}untuk mendapatkan yang terakhir ketiga dan seterusnya ...)
olibre
4
Jawaban AgileZebra menyediakan cara untuk mendapatkan semua kecuali argumen terakhir sehingga saya tidak akan mengatakan jawaban Steven menggantikannya. Namun, sepertinya tidak ada alasan untuk menggunakan $((...))untuk mengurangi angka 1, Anda cukup menggunakan ${@:1:$# - 1}.
dkasak
Terima kasih dkasak. Diperbarui untuk mencerminkan penyederhanaan Anda.
AgileZebra
22

Ini bekerja di semua shell yang kompatibel dengan POSIX:

eval last=\${$#}

Sumber: http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html

poiuz
sumber
2
Lihat komentar ini terlampir pada jawaban identik yang lebih tua.
Dijeda sampai pemberitahuan lebih lanjut.
1
Solusi portabel paling sederhana yang saya lihat di sini. Yang ini tidak memiliki masalah keamanan, @DennisWilliamson, mengutip secara empiris tampaknya dilakukan dengan benar, kecuali ada cara untuk mengatur $#ke string sewenang-wenang (saya tidak berpikir begitu). eval last=\"\${$#}\"juga berfungsi dan jelas benar. Jangan melihat mengapa kutipan tidak diperlukan.
Palec
1
Untuk bash , zsh , dash dan ksh eval last=\"\${$#}\" baik-baik saja. Tapi untuk csh dan tcsh digunakan eval set last=\"\${$#}\". Lihat contoh ini: tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
olibre
12

Inilah solusi tambang:

  • cukup portabel (semua POSIX sh, bash, ksh, zsh) harus berfungsi
  • tidak menggeser argumen asli (menggeser salinan).
  • tidak menggunakan kejahatan eval
  • tidak mengulangi seluruh daftar
  • tidak menggunakan alat eksternal

Kode:

ntharg() {
    shift $1
    printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$@"`
Michał Šrajer
sumber
1
Jawaban bagus - pendek, portabel, aman. Terima kasih!
Ján Lalinský
2
Ini adalah ide yang bagus, tapi saya punya beberapa saran: Pertama mengutip harus ditambahkan di sekitar "$1"dan "$#"(lihat jawaban yang bagus ini unix.stackexchange.com/a/171347 ). Kedua, echosayangnya non-portabel (terutama untuk -n), jadi printf '%s\n' "$1"sebaiknya digunakan.
joki
terima kasih @joki Saya bekerja dengan banyak sistem unix yang berbeda dan saya juga tidak akan percaya echo -n, namun saya tidak mengetahui sistem posix mana pun yang echo "$1"akan gagal. Bagaimanapun, printfmemang lebih mudah ditebak - diperbarui.
Michał Šrajer
@ MichałŠrajer mempertimbangkan kasus di mana "$ 1" adalah "-n" atau "-", jadi misalnya ntharg 1 -natau ntharg 1 --dapat menghasilkan hasil yang berbeda di berbagai sistem. Kode yang Anda miliki sekarang aman!
joki
10

Dari solusi terlama hingga yang lebih baru:

Solusi paling portabel, bahkan lebih tua sh(bekerja dengan spasi dan karakter gumpal) (tanpa loop, lebih cepat):

eval printf "'%s\n'" "\"\${$#}\""

Sejak versi 2.01 dari bash

$ set -- The quick brown fox jumps over the lazy dog

$ printf '%s\n'     "${!#}     ${@:(-1)} ${@: -1} ${@:~0} ${!#}"
dog     dog dog dog dog

Untuk ksh, zsh dan bash:

$ printf '%s\n' "${@: -1}    ${@:~0}"     # the space beetwen `:`
                                          # and `-1` is a must.
dog   dog

Dan untuk "selanjutnya":

$ printf '%s\n' "${@:~1:1}"
lazy

Menggunakan printf untuk menyelesaikan masalah dengan argumen yang dimulai dengan tanda hubung (seperti -n ).

Untuk semua shell dan untuk yang lebih tua sh(bekerja dengan spasi dan karakter glob) adalah:

$ set -- The quick brown fox jumps over the lazy dog "the * last argument"

$ eval printf "'%s\n'" "\"\${$#}\""
The last * argument

Atau, jika Anda ingin mengatur lastvar:

$ eval last=\${$#}; printf '%s\n' "$last"
The last * argument

Dan untuk "selanjutnya":

$ eval printf "'%s\n'" "\"\${$(($#-1))}\""
dog
Ishak
sumber
8

Jika Anda menggunakan Bash> = 3.0

echo ${BASH_ARGV[0]}
dusan
sumber
Juga untuk di samping argumen terakhir, lakukanecho ${BASH_ARGV[1]}
Steven Penny
1
ARGVsingkatan dari "vector argumen."
Serge Stroobandt
5

Sebab bash, komentar ini menyarankan yang sangat elegan:

echo "${@:$#}"

Sebagai bonus, ini juga berfungsi di zsh.

Tom Hale
sumber
3
shift `expr $# - 1`
echo "$1"

Ini menggeser argumen dengan jumlah argumen minus 1, dan mengembalikan argumen pertama (dan hanya) yang tersisa, yang akan menjadi yang terakhir.

Saya hanya menguji dalam bash, tetapi harus bekerja di sh dan ksh juga.

Laurence Gonsalves
sumber
11
shift $(($# - 1))- tidak perlu untuk utilitas eksternal. Bekerja di Bash, ksh, zsh dan dash.
Dijeda sampai pemberitahuan lebih lanjut.
@ Dennis: Bagus! Saya tidak tahu tentang $((...))sintaks.
Laurence Gonsalves
Anda ingin menggunakan printf '%s\n' "$1"untuk menghindari perilaku yang tidak terduga dari echo(misalnya untuk -n).
joki
2

Jika Anda ingin melakukannya dengan cara yang tidak merusak, salah satu caranya adalah meneruskan semua argumen ke suatu fungsi dan mengembalikan yang terakhir:

#!/bin/bash

last() {
        if [[ $# -ne 0 ]] ; then
            shift $(expr $# - 1)
            echo "$1"
        #else
            #do something when no arguments
        fi
}

lastvar=$(last "$@")
echo $lastvar
echo "$@"

pax> ./qq.sh 1 2 3 a b
b
1 2 3 a b

Jika Anda tidak peduli tentang mempertahankan argumen lain, Anda tidak memerlukannya dalam suatu fungsi tetapi saya kesulitan memikirkan situasi di mana Anda tidak akan pernah ingin menyimpan argumen lain kecuali mereka sudah diproses, dalam hal ini saya akan menggunakan proses / shift / proses / shift / ... metode pemrosesan secara berurutan.

Saya berasumsi di sini bahwa Anda ingin menyimpannya karena Anda belum mengikuti metode berurutan. Metode ini juga menangani kasus di mana tidak ada argumen, mengembalikan "". Anda dapat dengan mudah menyesuaikan perilaku itu dengan memasukkan elseklausul yang dikomentari .

paxdiablo
sumber
2

Untuk tcsh:

set X = `echo $* | awk -F " " '{print $NF}'`
somecommand "$X"

Saya cukup yakin ini akan menjadi solusi portabel, kecuali untuk penugasan.

Perfect64
sumber
Saya tahu Anda memposting ini selamanya, tetapi solusi ini hebat - senang ada yang diposting tcsh!
user3295674
2

Saya menemukan jawaban @ AgileZebra (ditambah komentar @ starfry) yang paling berguna, tetapi disetel headsmenjadi skalar. Array mungkin lebih berguna:

heads=( "${@:1:$(($# - 1))}" )
tail=${@:${#@}}

Perhatikan bahwa ini hanya untuk bash.

Kehidupan Akhir
sumber
Bagaimana Anda mengubah kepala menjadi array?
Stephane
Saya sudah melakukannya dengan menambahkan tanda kurung, misalnya echo "${heads[-1]}"mencetak elemen terakhir di heads. Atau apakah saya melewatkan sesuatu?
EndlosSchleife
1

Solusi menggunakan eval:

last=$(eval "echo \$$#")

echo $last
Mikael S
sumber
7
eval untuk referensi tidak langsung adalah berlebihan, belum lagi praktik buruk, dan masalah keamanan besar (nilainya tidak mengutip dalam gema atau di luar $ (). Bash memiliki sintaks bawaan untuk referensi tidak langsung, untuk var apa pun: !Jadi last="${!#}"akan menggunakan hal yang sama pendekatan (referensi tidak langsung pada $ #) dengan cara yang jauh lebih aman, kompak, builtin, waras. Dan dikutip dengan benar
MestreLion
Lihat cara yang lebih bersih untuk melakukan hal yang sama di jawaban lain .
Palec
Kutipan @MestreLion tidak diperlukan pada RHS dari =.
Tom Hale
1
@ TomHale: benar untuk kasus tertentu ${!#}, tetapi tidak secara umum: kutipan masih diperlukan jika konten mengandung spasi putih literal, seperti last='two words'. Hanya ruang $()kosong yang aman terlepas dari konten.
MestreLion
1

Setelah membaca jawaban di atas saya menulis skrip shell Q & D (harus bekerja pada sh dan bash) untuk menjalankan g ++ di PGM.cpp untuk menghasilkan gambar PGM yang dapat dieksekusi. Ini mengasumsikan bahwa argumen terakhir pada baris perintah adalah nama file (.cpp adalah opsional) dan semua argumen lainnya adalah opsi.

#!/bin/sh
if [ $# -lt 1 ]
then
    echo "Usage: `basename $0` [opt] pgm runs g++ to compile pgm[.cpp] into pgm"
    exit 2
fi
OPT=
PGM=
# PGM is the last argument, all others are considered options
for F; do OPT="$OPT $PGM"; PGM=$F; done
DIR=`dirname $PGM`
PGM=`basename $PGM .cpp`
# put -o first so it can be overridden by -o specified in OPT
set -x
g++ -o $DIR/$PGM $OPT $DIR/$PGM.cpp
David E.
sumber
1

LASTArgumen berikut akan diatur ke terakhir tanpa mengubah lingkungan saat ini:

LAST=$({
   shift $(($#-1))
   echo $1
})
echo $LAST

Jika argumen lain tidak lagi diperlukan dan dapat digeser, dapat disederhanakan menjadi:

shift $(($#-1))
echo $1

Untuk alasan portabilitas berikut:

shift $(($#-1));

dapat diganti dengan:

shift `expr $# - 1`

Mengganti juga $() dengan backquotes yang kami dapatkan:

LAST=`{
   shift \`expr $# - 1\`
   echo $1
}`
echo $LAST
Paweł Nadolski
sumber
1
echo $argv[$#argv]

Sekarang saya hanya perlu menambahkan beberapa teks karena jawaban saya terlalu pendek untuk dikirim. Saya perlu menambahkan lebih banyak teks untuk diedit.

frank1rizzo
sumber
Di mana shell ini seharusnya bekerja? Bukan bash. Bukan ikan (memiliki $argvtetapi tidak $#argv- $argv[(count $argv)]bekerja pada ikan).
Beni Cherniavsky-Paskin
1

Ini adalah bagian dari fungsi salin saya:

eval echo $(echo '$'"$#")

Untuk digunakan dalam skrip, lakukan ini:

a=$(eval echo $(echo '$'"$#"))

Penjelasan (paling bersarang terlebih dahulu):

  1. $(echo '$'"$#")mengembalikan di $[nr]mana [nr]jumlah parameter. Misalnya string $123(tidak diperluas).
  2. echo $123 mengembalikan nilai parameter 123, ketika dievaluasi.
  3. evalhanya memperluas $123ke nilai parameter, mis last_arg. Ini ditafsirkan sebagai string dan dikembalikan.

Bekerja dengan Bash pada pertengahan 2015.

Esavier
sumber
Pendekatan eval telah disajikan di sini berkali-kali, tetapi yang ini memiliki penjelasan tentang cara kerjanya. Bisa lebih ditingkatkan, tetapi tetap layak dipertahankan.
Palec
0
#! /bin/sh

next=$1
while [ -n "${next}" ] ; do
  last=$next
  shift
  next=$1
done

echo $last
Craig Trader
sumber
Ini akan gagal jika argumen adalah string kosong, tetapi akan bekerja di 99,9% dari kasus.
Thomas
0

Coba skrip di bawah ini untuk menemukan argumen terakhir

 # cat arguments.sh
 #!/bin/bash
 if [ $# -eq 0 ]
 then
 echo "No Arguments supplied"
 else
 echo $* > .ags
 sed -e 's/ /\n/g' .ags | tac | head -n1 > .ga
 echo "Last Argument is: `cat .ga`"
 fi

Keluaran:

 # ./arguments.sh
 No Arguments supplied

 # ./arguments.sh testing for the last argument value
 Last Argument is: value

Terima kasih.

Ranjithkumar T
sumber
Saya menduga ini akan gagal dengan ./arguments.sh "nilai terakhir"
Thomas
Terima kasih telah memeriksa Thomas, saya telah mencoba melakukan skrip as seperti Anda yang didaftarkan # ./arguments.sh "nilai terakhir" Argumen Terakhir adalah: nilai berfungsi dengan baik sekarang. # ./arguments.sh "pemeriksaan nilai terakhir dengan dobel" Argumen Terakhir adalah: dobel
Ranjithkumar T
Masalahnya adalah bahwa argumen terakhir adalah 'nilai terakhir', dan bukan nilai. Kesalahan ini disebabkan oleh argumen yang berisi spasi.
Thomas
0

Ada cara yang jauh lebih ringkas untuk melakukan ini. Argumen untuk skrip bash dapat dibawa ke dalam array, yang membuat berurusan dengan elemen lebih sederhana. Skrip di bawah ini akan selalu mencetak argumen terakhir yang dilewatkan ke skrip.

  argArray=( "$@" )                        # Add all script arguments to argArray
  arrayLength=${#argArray[@]}              # Get the length of the array
  lastArg=$((arrayLength - 1))             # Arrays are zero based, so last arg is -1
  echo ${argArray[$lastArg]}

Output sampel

$ ./lastarg.sh 1 2 buckle my shoe
shoe
tekbot
sumber
0

Menggunakan ekspansi parameter (hapus awal yang cocok):

args="$@"
last=${args##* }

Juga mudah untuk mendapatkan semua sebelum yang terakhir:

prelast=${args% *}
Jurij
sumber
Kemungkinan duplikat stackoverflow.com/a/37601842/1446229
Xerz
Ini hanya berfungsi jika argumen terakhir tidak mengandung spasi.
Palec
Prelast Anda gagal jika argumen terakhir adalah kalimat yang dilampirkan dalam tanda kutip ganda, dengan kalimat tersebut seharusnya menjadi satu argumen.
Stephane
0

Untuk mengembalikan argumen terakhir dari perintah yang terakhir digunakan, gunakan parameter khusus:

$_

Dalam hal ini, ini akan berfungsi jika digunakan dalam skrip sebelum perintah lain dipanggil.

Thain
sumber
Ini bukan pertanyaannya
retnikt
-1

Gunakan saja !$.

$ mkdir folder
$ cd !$ # will run: cd folder
roga
sumber
Ini bukan pertanyaannya
retnikt