Bắt đối số cuối cùng được chuyển sang tập lệnh shell


272

$1là đối số đầu tiên.
$@là tất cả trong số họ.

Làm thế nào tôi có thể tìm thấy đối số cuối cùng được chuyển đến một tập lệnh shell?


Tôi đã sử dụng bash, nhưng giải pháp càng di động càng tốt.
Thomas


10
sử dụng cũng có thể sử dụng $ {! #}
Prateek Joshi

11
Chỉ với bash , câu trả lời của Kevin Little đề xuất đơn giản ${!#}. Kiểm tra nó bằng cách sử dụng bash -c 'echo ${!#}' arg1 arg2 arg3. Đối với bash , kshzsh , câu trả lời của Dennis Williamson đề xuất ${@: -1}. Hơn nữa ${*: -1}cũng có thể được sử dụng. Kiểm tra nó bằng cách sử dụng zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Nhưng điều đó không làm việc cho dash , cshtcsh .
olibre

2
${!#}, không giống như ${@: -1}, cũng hoạt động với mở rộng tham số. Bạn có thể kiểm tra nó với bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton

Câu trả lời:


177

Đây là một chút của một hack:

for last; do true; done
echo $last

Cái này cũng khá di động (một lần nữa, nên hoạt động với bash, ksh và sh) và nó không thay đổi các đối số, có thể tốt.

Nó sử dụng thực tế là forcác vòng lặp ngầm đối với các đối số nếu bạn không nói cho nó biết vòng lặp nào và thực tế là đối với các biến vòng lặp không nằm trong phạm vi: chúng giữ giá trị cuối cùng mà chúng được đặt.


10
@ MichałŠrajer, tôi nghĩ bạn có nghĩa là dấu hai chấm chứ không phải dấu phẩy;)
Paweł Nadolski


4
Với một Solaris cũ, với vỏ bourne cũ (không phải POSIX), tôi phải viết "cuối cùng trong" $ @ "; làm đúng; thực hiện"
mcoolive

8
@mcoolive @LaurenceGolsalves bên cạnh việc dễ mang theo hơn, for last in "$@"; do :; donecũng làm cho ý định rõ ràng hơn nhiều.
Adrian Günter

1
@mcoolive: nó thậm chí hoạt động trong vỏ bourne un7 v7 từ năm 1979. bạn không thể di động nhiều hơn nếu nó chạy trên cả v7 sh và POSIX sh :)
Dominik R

291

Đây chỉ là Bash:

echo "${@: -1}"

43
Đối với những người (như tôi) tự hỏi tại sao không gian cần thiết, man bash có điều này để nói về nó:> Lưu ý rằng phần bù âm phải được tách ra khỏi dấu hai chấm bằng ít nhất một khoảng trắng để tránh bị nhầm lẫn với: - mở rộng.
foo

1
Tôi đã sử dụng cái này và nó chỉ bị hỏng trong bash MSYS2 trong windows. Kỳ quái.
Steven Lu

2
Lưu ý: Câu trả lời này hoạt động cho tất cả các mảng Bash, không giống như ${@:$#}chỉ hoạt động trên $@. Nếu bạn đã sao chép $@vào một mảng mới với arr=("$@"), ${arr[@]:$#}sẽ không được xác định. Điều này là do $@có phần tử thứ 0 không được bao gồm trong các bản "$@"mở rộng.
Ông Llama

@ Mr.Llama: Một nơi khác cần tránh $#là khi lặp qua các mảng vì trong Bash, các mảng rất thưa thớt và trong khi $#sẽ hiển thị số phần tử trong mảng thì không nhất thiết phải chỉ đến phần tử cuối cùng (hoặc phần tử + 1). Nói cách khác, người ta không nên làm for ((i = 0; i++; i < $#)); do something "${array[$i]}"; donevà thay vào đó làm for element in "${array[@]}"; do something "$element"; donehoặc lặp lại các chỉ số: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; donenếu bạn cần làm gì đó với các giá trị của các chỉ mục.
Tạm dừng cho đến khi có thông báo mới.

80
$ set quick brown fox jumps

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

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

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

Không gian là cần thiết để nó không được hiểu là một giá trị mặc định .

Lưu ý rằng đây là chỉ bash.


9
Câu trả lời tốt nhất, vì nó cũng bao gồm các đối số tiếp theo cuối cùng. Cảm ơn!
e40

1
Steven, tôi không biết bạn đã làm gì để hạ cánh trong Hộp hình phạt, nhưng tôi yêu công việc của bạn ở đây.
Bruno Bronosky

4
Đúng. đơn giản là tốt nhất. tất cả trừ lệnh và đối số cuối cùng ${@: 1:$#-1}
Dyno Fu

1
@DynoFu cảm ơn bạn vì điều đó, bạn đã trả lời câu hỏi tiếp theo của tôi. Vì vậy, một sự thay đổi có thể trông giống như : echo ${@: -1} ${@: 1:$#-1}, nơi cuối cùng trở thành đầu tiên và phần còn lại trượt xuống
Mike

73

Câu trả lời đơn giản nhất cho bash 3.0 trở lên là

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

Đó là nó.

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

Đầu ra là:

$ lastarg 1 2 3 4 "5 6 7"
5 6 7
5 6 7
1
2
3
4
5 6 7

1
$BASH_ARGVkhông hoạt động bên trong hàm bash (trừ khi tôi làm sai).
Big McLUNDHuge

1
BASH_ARGV có các đối số khi bash được gọi (hoặc đến một hàm) không phải là danh sách hiện tại của các đối số vị trí.
Isaac

Cũng lưu ý những gì BASH_ARGVsẽ mang lại cho bạn là giá trị mà đối số cuối cùng được đưa ra, thay vì chỉ đơn giản là "giá trị cuối cùng". Ví dụ!: Nếu bạn cung cấp một đối số duy nhất, thì bạn gọi shift, ${@:$#}sẽ không tạo ra gì (vì bạn đã loại bỏ đối số duy nhất và duy nhất!), Tuy nhiên, BASH_ARGVvẫn sẽ cung cấp cho bạn đối số cuối cùng (trước đây).
Steven Lu


29

Sau đây sẽ làm việc cho bạn. @ Là cho mảng các đối số. : có nghĩa là tại. $ # là độ dài của mảng đối số. Vì vậy, kết quả là yếu tố cuối cùng:

${@:$#} 

Thí dụ:

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

Lưu ý rằng đây là chỉ bash.


Trong khi ví dụ là cho một hàm, các kịch bản cũng hoạt động theo cách tương tự. Tôi thích câu trả lời này vì nó rõ ràng và súc tích.
Jonah Braun

1
Và nó không phải là hack. Nó sử dụng các tính năng rõ ràng của ngôn ngữ, không phải tác dụng phụ hoặc qwerks đặc biệt. Đây phải là câu trả lời được chấp nhận.
musicin3d

25

Tìm thấy điều này khi tìm cách tách đối số cuối cùng khỏi tất cả (các) đối số trước đó. Trong khi một số câu trả lời nhận được tranh luận cuối cùng, chúng sẽ không giúp ích nhiều nếu bạn cũng cần tất cả các đối số khác. Điều này hoạt động tốt hơn nhiều:

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

Lưu ý rằng đây là chỉ bash.


3
Các câu trả lời Steven Penny là một chút đẹp hơn: sử dụng ${@: -1}cho cuối cùng${@: -2:1}cho giây cuối cùng (và vân vân ...). Ví dụ: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6bản in 6. Để theo cách tiếp cận hiện tại của AgileZebra, hãy sử dụng ${@:$#-1:1}để có lần thứ hai cuối cùng . Ví dụ: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6bản in 5. (và ${@:$#-2:1}để có được lần thứ ba cuối cùng và cứ thế ...)
olibre

4
Câu trả lời của AgileZebra cung cấp một cách để có được tất cả trừ những tranh luận cuối cùng vì vậy tôi sẽ không nói câu trả lời của Steven thay thế nó. Tuy nhiên, dường như không có lý do để sử dụng $((...))để trừ đi 1, bạn chỉ có thể sử dụng ${@:1:$# - 1}.
dkasak

Cảm ơn dkasak. Cập nhật để phản ánh sự đơn giản hóa của bạn.
AgileZebra

22

Điều này hoạt động trong tất cả các vỏ tương thích POSIX:

eval last=\${$#}

Nguồn: http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html


2
Xem bình luận này kèm theo một câu trả lời giống hệt cũ hơn.
Tạm dừng cho đến khi có thông báo mới.

1
Giải pháp di động đơn giản nhất tôi thấy ở đây. Điều này không có vấn đề bảo mật, @DennisWilliamson, việc trích dẫn theo kinh nghiệm dường như được thực hiện đúng, trừ khi có một cách để đặt $#thành một chuỗi tùy ý (tôi không nghĩ vậy). eval last=\"\${$#}\"cũng hoạt động và rõ ràng là chính xác. Đừng xem tại sao các trích dẫn là không cần thiết.
Palec

1
Đối với bash , zsh , dashksh eval last=\"\${$#}\" là tốt. Nhưng để sử dụng cshtcsheval set last=\"\${$#}\" . Xem ví dụ này : tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
olibre

12

Đây là giải pháp của tôi:

  • khá di động (tất cả POSIX sh, bash, ksh, zsh) nên hoạt động
  • không thay đổi đối số ban đầu (thay đổi một bản sao).
  • không dùng ác eval
  • không lặp qua toàn bộ danh sách
  • không sử dụng các công cụ bên ngoài

Mã số:

ntharg() {
    shift $1
    printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$@"`

1
Câu trả lời tuyệt vời - ngắn gọn, di động, an toàn. Cảm ơn!
Ján Lalinský

2
Đây là một ý tưởng tuyệt vời, nhưng tôi có một vài gợi ý: Đầu tiên nên trích dẫn cả hai xung quanh "$1""$#"(xem câu trả lời tuyệt vời này unix.stackexchange.com/a/171347 ). Thứ hai, echođáng buồn là không di động (đặc biệt là -n), vì vậy printf '%s\n' "$1"nên được sử dụng thay thế.
joki

cảm ơn @joki Tôi đã làm việc với nhiều hệ thống unix khác nhau và tôi cũng không tin echo -n, tuy nhiên tôi không biết về bất kỳ hệ thống posix nào echo "$1"sẽ thất bại. Dù sao, printfthực sự là dễ dự đoán hơn - cập nhật.
Michał Šrajer

@ MichałŠrajer xem xét trường hợp "$ 1" là "-n" hoặc "-", vì vậy, ví dụ ntharg 1 -nhoặc ntharg 1 --có thể mang lại kết quả khác nhau trên các hệ thống khác nhau. Mã bạn có bây giờ là an toàn!
joki

10

Từ giải pháp cũ nhất đến mới hơn:

Giải pháp di động nhất, thậm chí cũ hơn sh(hoạt động với khoảng trắng và ký tự toàn cầu) (không lặp, nhanh hơn):

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

Kể từ phiên bản 2.01 của bash

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

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

Đối với ksh, zsh và bash:

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

Và cho "bên cạnh cuối cùng":

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

Sử dụng printf để giải quyết bất kỳ vấn đề nào với các đối số bắt đầu bằng dấu gạch ngang (như -n).

Đối với tất cả hệ vỏ và cũ hơn sh(hoạt động với dấu cách và ký tự toàn cầu) là:

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

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

Hoặc, nếu bạn muốn đặt lastvar:

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

Và cho "bên cạnh cuối cùng":

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


5

Đối với bash, bình luận này cho thấy rất thanh lịch:

echo "${@:$#}"

Như một phần thưởng, điều này cũng hoạt động trong zsh.


3
shift `expr $# - 1`
echo "$1"

Điều này sẽ dịch chuyển các đối số theo số lượng đối số trừ đi 1 và trả về đối số đầu tiên (và duy nhất) còn lại, sẽ là đối số cuối cùng.

Tôi chỉ thử nghiệm trong bash, nhưng nó cũng hoạt động trong sh và ksh.


11
shift $(($# - 1))- không cần một tiện ích bên ngoài. Hoạt động trong Bash, ksh, zsh và dash.
Tạm dừng cho đến khi có thông báo mới.

@Dennis: Tốt đẹp! Tôi không biết về $((...))cú pháp.
Laurence Gonsalves

Bạn muốn sử dụng printf '%s\n' "$1"để tránh hành vi không mong muốn từ echo(ví dụ -n:).
joki

2

Nếu bạn muốn làm điều đó theo cách không phá hủy, một cách là chuyển tất cả các đối số cho một hàm và trả về cái cuối cùng:

#!/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

Nếu bạn không thực sự quan tâm đến việc giữ các đối số khác, bạn không cần nó trong một hàm nhưng tôi gặp khó khăn khi nghĩ về một tình huống mà bạn sẽ không bao giờ muốn giữ các đối số khác trừ khi chúng đã được xử lý, trong trường hợp đó tôi sẽ sử dụng phương thức process / shift / process / shift / ... để xử lý tuần tự chúng.

Tôi giả sử ở đây rằng bạn muốn giữ chúng bởi vì bạn đã không theo phương pháp tuần tự. Phương pháp này cũng xử lý trường hợp không có đối số, trả về "". Bạn có thể dễ dàng điều chỉnh hành vi đó bằng cách chèn elsemệnh đề nhận xét .


2

Đối với tcsh:

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

Tôi khá chắc chắn đây sẽ là một giải pháp di động, ngoại trừ việc chuyển nhượng.


Tôi biết bạn đã đăng bài này mãi mãi, nhưng giải pháp này rất tuyệt - rất vui vì ai đó đã đăng một bài tcsh!
dùng3295674

2

Tôi thấy câu trả lời của @ AgileZebra (cộng với bình luận của @ starfry) là hữu ích nhất, nhưng nó được đặt headsthành vô hướng. Một mảng có thể hữu ích hơn:

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

Lưu ý rằng đây là chỉ bash.


Làm thế nào bạn sẽ chuyển đổi các đầu thành một mảng?
Stephane

Tôi đã làm bằng cách thêm dấu ngoặc đơn, ví dụ echo "${heads[-1]}"in phần tử cuối cùng vào heads. Hay tôi đang thiếu một cái gì đó?
EndlosSchleife

1

Một giải pháp sử dụng eval:

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

echo $last

7
eval cho tham chiếu gián tiếp là quá mức cần thiết, chưa kể đến thực tiễn xấu và mối quan tâm bảo mật rất lớn (giá trị không được trích dẫn trong echo hoặc bên ngoài $ (). Bash có một cú pháp dựng sẵn cho các tham chiếu gián tiếp, cho bất kỳ var: !Vì vậy, last="${!#}"sẽ sử dụng tương tự cách tiếp cận (tham chiếu gián tiếp trên $ #) theo cách an toàn hơn, nhỏ gọn, tích hợp, lành mạnh. Và được trích dẫn chính xác.
MestreLion

Xem một cách sạch hơn để thực hiện tương tự trong một câu trả lời khác .
Palec

Báo giá @MestreLion không cần thiết trên RHS của =.
Tom Hale

1
@TomHale: đúng cho trường hợp cụ thể của ${!#}, nhưng không nói chung: trích dẫn vẫn cần thiết nếu nội dung chứa khoảng trắng theo nghĩa đen, chẳng hạn như last='two words'. Chỉ $()là khoảng trắng an toàn bất kể nội dung.
MestreLion

1

Sau khi đọc các câu trả lời ở trên, tôi đã viết một kịch bản shell Q & D (nên hoạt động trên sh và bash) để chạy g ++ trên PGM.cpp để tạo PGM hình ảnh thực thi. Nó giả định rằng đối số cuối cùng trên dòng lệnh là tên tệp (.cpp là tùy chọn) và tất cả các đối số khác là tùy chọn.

#!/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

1

Sau đây sẽ được đặt thành LASTđối số cuối cùng mà không thay đổi môi trường hiện tại:

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

Nếu các đối số khác không còn cần thiết và có thể được thay đổi, nó có thể được đơn giản hóa thành:

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

Vì lý do tính di động như sau:

shift $(($#-1));

có thể được thay thế bằng:

shift `expr $# - 1`

Thay thế cũng $()bằng backquote chúng tôi nhận được:

LAST=`{
   shift \`expr $# - 1\`
   echo $1
}`
echo $LAST

1
echo $argv[$#argv]

Bây giờ tôi chỉ cần thêm một số văn bản vì câu trả lời của tôi quá ngắn để đăng. Tôi cần thêm nhiều văn bản để chỉnh sửa.


Cái vỏ này được suppossed để làm việc trong? Không bash. Không phải cá (có $argvnhưng không $#argv- $argv[(count $argv)]hoạt động trong cá).
Beni Cherniavsky-Paskin

1

Đây là một phần của chức năng sao chép của tôi:

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

Để sử dụng trong tập lệnh, hãy làm điều này:

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

Giải thích (hầu hết lồng nhau trước):

  1. $(echo '$'"$#")trả về $[nr]nơi [nr]là số lượng tham số. Ví dụ: chuỗi $123(chưa được mở rộng).
  2. echo $123 trả về giá trị của tham số 123, khi được đánh giá.
  3. evalchỉ mở rộng $123đến giá trị của tham số, vd last_arg. Điều này được hiểu là một chuỗi và trả lại.

Làm việc với Bash vào giữa năm 2015.


Cách tiếp cận eval đã được trình bày ở đây nhiều lần rồi, nhưng cách này có một lời giải thích về cách thức hoạt động của nó. Có thể được cải thiện hơn nữa, nhưng vẫn có giá trị giữ.
Palec

0
#! /bin/sh

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

echo $last

Điều này sẽ thất bại nếu một đối số là chuỗi rỗng, nhưng sẽ hoạt động trong 99,9% trường hợp.
Thomas

0

Hãy thử đoạn script dưới đây để tìm đối số cuối cùng

 # 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

Đầu ra:

 # ./arguments.sh
 No Arguments supplied

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

Cảm ơn.


Tôi nghi ngờ điều này sẽ thất bại với ./argument.sh "giá trị cuối cùng"
Thomas

Cảm ơn bạn đã kiểm tra Thomas, tôi đã cố gắng thực hiện tập lệnh như bạn đã đề cập # ./argument.sh "giá trị cuối cùng" Đối số cuối cùng là: giá trị hiện đang hoạt động tốt. # ./argument.sh "kiểm tra giá trị cuối cùng với gấp đôi" Đối số cuối cùng là: double
Ranjithkumar T

Vấn đề là đối số cuối cùng là 'giá trị cuối cùng' chứ không phải giá trị. Lỗi được gây ra bởi đối số có chứa một khoảng trắng.
Thomas

0

Có một cách ngắn gọn hơn nhiều để làm điều này. Các đối số cho một tập lệnh bash có thể được đưa vào một mảng, điều này làm cho việc xử lý các phần tử đơn giản hơn nhiều. Kịch bản bên dưới sẽ luôn in đối số cuối cùng được truyền cho một tập lệnh.

  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]}

Sản lượng mẫu

$ ./lastarg.sh 1 2 buckle my shoe
shoe

0

Sử dụng mở rộng tham số (xóa bắt đầu khớp):

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

Cũng dễ dàng để có được tất cả trước khi cuối cùng:

prelast=${args% *}

Có thể (kém) bản sao của stackoverflow.com/a/37601842/1446229
Xerz 17/03/18

Điều này chỉ hoạt động nếu đối số cuối cùng không chứa khoảng trắng.
Palec

Lời mở đầu của bạn không thành công nếu đối số cuối cùng là một câu được đặt trong dấu ngoặc kép, với câu nói được cho là một đối số.
Stephane

0

Để trả về đối số cuối cùng của lệnh được sử dụng gần đây nhất, hãy sử dụng tham số đặc biệt:

$_

Trong trường hợp này, nó sẽ hoạt động nếu nó được sử dụng trong tập lệnh trước khi lệnh khác được gọi.


Đây không phải là những gì câu hỏi đang hỏi
retnikt

-1

Chỉ cần sử dụng !$.

$ mkdir folder
$ cd !$ # will run: cd folder

Đây không phải là những gì câu hỏi đang hỏi
retnikt
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.