Uzyskanie ostatniego argumentu przekazanego do skryptu powłoki

272

$1to pierwszy argument.
$@to wszystko.

Jak mogę znaleźć ostatni argument przekazany do skryptu powłoki?

Tomasz
źródło
Korzystałem z bash, ale im bardziej przenośne rozwiązanie, tym lepiej.
Thomas
10
use może również użyć $ {! #}
Prateek Joshi
11
Tylko dla bashu odpowiedź Kevina Little'a proponuje proste ${!#}. Przetestuj za pomocą bash -c 'echo ${!#}' arg1 arg2 arg3. Dla bash , ksh i zsh , w odpowiedzi Dennis Williamsona proponuje ${@: -1}. Ponadto ${*: -1}można również użyć. Przetestuj za pomocą zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Ale to nie działa dla dash , csh i tcsh .
olibre
2
${!#}, w przeciwieństwie do ${@: -1}, działa również z rozszerzaniem parametrów. Możesz to przetestować bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton,

Odpowiedzi:

177

To jest trochę włamania:

for last; do true; done
echo $last

Ten jest również dość przenośny (znowu powinien działać z bash, ksh i sh) i nie zmienia argumentów, co może być miłe.

Wykorzystuje fakt, że forniejawnie zapętla się nad argumentami, jeśli nie powiesz, co należy zapętlić, oraz fakt, że w przypadku zmiennych pętli nie ma zasięgu: zachowują ostatnią wartość, dla której zostały ustawione.

Laurence Gonsalves
źródło
10
@ MichałŠrajer, myślę, że miałeś na myśli dwukropek, a nie przecinek;)
Paweł Nadolski
2
@ MichałŠrajer truejest częścią POSIX .
Rufflewind
4
W starym Solarisie, ze starą powłoką Bourne'a (nie POSIX), muszę napisać „na koniec w” $ @ ”; do true; done”
mcoolive
8
@mcoolive @LaurenceGolsalves, oprócz tego, for last in "$@"; do :; doneże jest bardziej przenośny, sprawia, że ​​intencja jest znacznie bardziej przejrzysta.
Adrian Günter,
1
@mcoolive: działa nawet w powłoce bourne unix v7 od 1979 roku. Nie można uzyskać większej przenośności, jeśli działa zarówno na v7 sh, jak i POSIX sh :)
Dominik R
291

To jest tylko Bash:

echo "${@: -1}"
Wstrzymano do odwołania.
źródło
43
Dla tych (jak ja) zastanawiających się, dlaczego potrzebna jest przestrzeń, bash ma do powiedzenia na ten temat:> Zauważ, że ujemne przesunięcie musi być oddzielone od dwukropka co najmniej jedną spacją, aby uniknąć pomylenia z rozszerzeniem: -.
foo
1
Korzystałem z tego i psuje się tylko w MSYS2 bash tylko w systemie Windows. Dziwaczny.
Steven Lu
2
Uwaga: Ta odpowiedź działa dla wszystkich tablic Bash, w przeciwieństwie do tych, ${@:$#}które działają tylko na $@. Jeśli miałbyś kopiować $@do nowej tablicy za pomocą arr=("$@"), ${arr[@]:$#}byłoby niezdefiniowane. Jest tak, ponieważ $@ma 0 element, który nie jest uwzględniony w "$@"rozszerzeniach.
Pan Llama,
@ Mr.Llama: Innym miejscem, którego należy unikać, $#jest iteracja po tablicach, ponieważ w Bash tablice są rzadkie i chociaż $#pokaże liczbę elementów w tablicy, niekoniecznie wskazuje na ostatni element (lub element + 1). Innymi słowy, nie należy robić, for ((i = 0; i++; i < $#)); do something "${array[$i]}"; donea zamiast tego robić for element in "${array[@]}"; do something "$element"; donelub iterować indeksów: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; donejeśli trzeba coś zrobić z wartościami indeksów.
Wstrzymano do odwołania.
80
$ set quick brown fox jumps

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

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

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

Spacja jest niezbędna, aby nie została zinterpretowana jako wartość domyślna .

Zauważ, że jest to tylko bash.

Steven Penny
źródło
9
Najlepsza odpowiedź, ponieważ zawiera również argument przedostatni. Dzięki!
e40
1
Steven, nie wiem, co zrobiłeś, żeby wylądować w Karie, ale uwielbiam twoją pracę tutaj.
Bruno Bronosky
4
tak. po prostu najlepszy. wszystko oprócz polecenia i ostatniego argumentu ${@: 1:$#-1}
Dyno Fu
1
@DynoFu dziękuję za to, odpowiedziałeś na moje następne pytanie. Zmiana może więc wyglądać następująco: echo ${@: -1} ${@: 1:$#-1}gdzie ostatni staje się pierwszy, a reszta zjeżdża w dół
Mike
73

Najprostsza odpowiedź na bash 3.0 lub nowszy to

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

Otóż ​​to.

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

Dane wyjściowe to:

$ lastarg 1 2 3 4 "5 6 7"
5 6 7
5 6 7
1
2
3
4
5 6 7
Kevin Little
źródło
1
$BASH_ARGVnie działa w funkcji bash (chyba że robię coś złego).
Big McLargeHuge,
1
BASH_ARGV ma argumenty, gdy wywołano bash (lub funkcję), a nie obecną listę argumentów pozycyjnych.
Izaak
Zauważ też, co BASH_ARGVda ci wartość, którą podał ostatni argument, zamiast po prostu „ostatniej wartości”. Na przykład !: jeśli podasz jeden argument, a następnie wywołasz shift, ${@:$#}nic nie da (bo zmieniłeś jedyny argument!), Jednak BASH_ARGVnadal da ci ten (poprzednio) ostatni argument.
Steven Lu
30

Użyj indeksowania w połączeniu z długością:

echo ${@:${#@}} 

Zauważ, że jest to tylko bash.

Mark Byers
źródło
24
Krótszy:echo ${@:$#}
Wstrzymany do odwołania.
1
echo $ (@ & $; & ^ $ &% @ ^! @% ^ # ** # & @ * # @ * # @ (& * # _ ** ^ @ ^ & (^ @ & * & @)
Atul
29

Poniższe będą działać dla Ciebie. @ Oznacza tablicę argumentów. : oznacza o. $ # to długość tablicy argumentów. Wynik jest ostatnim elementem:

${@:$#} 

Przykład:

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

Zauważ, że jest to tylko bash.

Tahsin Turkoz
źródło
Chociaż przykład dotyczy funkcji, skrypty działają również w ten sam sposób. Podoba mi się ta odpowiedź, ponieważ jest jasna i zwięzła.
Jonah Braun
1
I nie jest zuchwały. Wykorzystuje wyraźne funkcje języka, a nie efekty uboczne lub specjalne qwerki. To powinna być zaakceptowana odpowiedź.
musicin3d
25

Znaleziono to, gdy chcesz oddzielić ostatni argument od wszystkich poprzednich. Chociaż niektóre odpowiedzi mają ostatni argument, nie są zbyt pomocne, jeśli potrzebujesz wszystkich innych argumentów. Działa to znacznie lepiej:

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

Zauważ, że jest to tylko bash.

AgileZebra
źródło
3
Odpowiedź Stevena Penny'ego jest nieco ładniejsza: użyj ${@: -1}na ostatni i ${@: -2:1}na drugi ostatni (i tak dalej ...). Przykład: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6wydruki 6. Aby pozostać przy obecnym podejściu AgileZebra, użyj, ${@:$#-1:1}aby uzyskać drugie miejsce . Przykład: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6wydruki 5. (i ${@:$#-2:1}zdobyć trzeci ostatni i tak dalej ...)
olibre
4
Odpowiedź AgileZebra dostarcza sposobu na uzyskanie wszystkich argumentów oprócz ostatnich, więc nie powiedziałbym, że odpowiedź Stevena ją zastępuje. Jednak wydaje się, że nie ma powodu, aby używać $((...))odejmowania 1, możesz po prostu użyć ${@:1:$# - 1}.
dkasak
Dzięki dkasak. Zaktualizowano, aby odzwierciedlić twoje uproszczenie.
AgileZebra
22

Działa to we wszystkich powłokach zgodnych z POSIX:

eval last=\${$#}

Źródło: http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html

poiuz
źródło
2
Zobacz ten komentarz dołączony do starszej identycznej odpowiedzi.
Wstrzymano do odwołania.
1
Najprostsze przenośne rozwiązanie, jakie tu widzę. Ten nie ma problemu z bezpieczeństwem, @DennisWilliamson, cytowanie wydaje się być wykonane poprawnie, chyba że istnieje sposób na ustawienie $#dowolnego ciągu (nie sądzę). eval last=\"\${$#}\"działa również i jest oczywiście poprawne. Nie rozumiem, dlaczego cytaty nie są potrzebne.
Palec
1
Dla bash , zsh , dash i ksh eval last=\"\${$#}\" jest w porządku. Ale do użytku w csh i tcsheval set last=\"\${$#}\" . Zobacz ten przykład: tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
olibre
12

Oto moje rozwiązanie:

  • całkiem przenośne (wszystkie POSIX sh, bash, ksh, zsh) powinny działać
  • nie zmienia oryginalnych argumentów (przesuwa kopię).
  • nie używa zła eval
  • nie przechodzi przez całą listę
  • nie korzysta z zewnętrznych narzędzi

Kod:

ntharg() {
    shift $1
    printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$@"`
Michał Šrajer
źródło
1
Świetna odpowiedź - krótka, przenośna, bezpieczna. Dzięki!
Ján Lalinský
2
To świetny pomysł, ale mam kilka sugestii: Najpierw należy dodać cytowanie zarówno wokół, jak "$1"i "$#"(zobacz tę świetną odpowiedź unix.stackexchange.com/a/171347 ). Po drugie, echojest niestety nieprzenośny (szczególnie dla -n), więc printf '%s\n' "$1"należy go użyć.
joki
dzięki @joki Pracowałem z wieloma różnymi systemami uniksowymi i też nie ufałbym echo -n, jednak nie jestem świadomy żadnego systemu posix, który echo "$1"mógłby zawieść. W każdym razie printfjest rzeczywiście bardziej przewidywalny - zaktualizowany.
Michał Šrajer
@ MichałŠrajer rozważ przypadek, w którym „$ 1” to „-n” lub „-”, na przykład ntharg 1 -nlub ntharg 1 --może przynieść różne wyniki w różnych systemach. Kod, który masz teraz, jest bezpieczny!
joki,
10

Od najstarszych do nowszych rozwiązań:

Najbardziej przenośne rozwiązanie, nawet starsze sh(działa ze spacjami i znakami glob) (bez pętli, szybciej):

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

Od wersji 2.01 bash

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

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

Dla ksh, zsh i bash:

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

A dla „obok ostatniego”:

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

Za pomocą printf można obejść wszelkie problemy z argumentami rozpoczynającymi się od myślnika (np -n.).

Dla wszystkich powłok i starszych sh(działa ze spacjami i znakami glob):

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

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

Lub, jeśli chcesz ustawić lastvar:

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

A dla „obok ostatniego”:

$ eval printf "'%s\n'" "\"\${$(($#-1))}\""
dog
Izaak
źródło
8

Jeśli używasz Bash> = 3.0

echo ${BASH_ARGV[0]}
dusan
źródło
Również dla ostatniego argumentuecho ${BASH_ARGV[1]}
Steven Penny
1
ARGVoznacza „wektor argumentów”.
Serge Stroobandt
5

Dla bash, to komentarz sugeruje bardzo elegancki:

echo "${@:$#}"

Jako bonus, działa również w zsh.

Tom Hale
źródło
3
shift `expr $# - 1`
echo "$1"

Powoduje to przesunięcie argumentów o liczbę argumentów pomniejszoną o 1 i zwraca pierwszy (i jedyny) pozostały argument, który będzie ostatnim.

Testowałem tylko w bashu, ale powinien działać również w sh i ksh.

Laurence Gonsalves
źródło
11
shift $(($# - 1))- nie potrzeba zewnętrznego narzędzia. Działa w Bash, ksh, zsh i dash.
Wstrzymano do odwołania.
@Dennis: Fajnie! Nie wiedziałem o $((...))składni.
Laurence Gonsalves,
Chcesz użyć printf '%s\n' "$1", aby uniknąć nieoczekiwanego zachowania echo(np. Dla -n).
joki
2

Jeśli chcesz to zrobić w sposób nieniszczący, jednym ze sposobów jest przekazanie wszystkich argumentów do funkcji i zwrócenie ostatniego:

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

Jeśli tak naprawdę cię to nie obchodzi na zachowaniu innych argumentów, nie potrzebujesz ich w funkcji, ale trudno mi myśleć o sytuacji, w której nigdy nie chciałbyś zatrzymać innych argumentów, chyba że zostały już przetworzone, w takim przypadku użyłbym metody process / shift / process / shift / ... sekwencyjnego ich przetwarzania.

Zakładam, że chcesz je zachować, ponieważ nie zastosowałeś metody sekwencyjnej. Ta metoda obsługuje również przypadek, w którym nie ma argumentów, zwracając „”. Możesz łatwo dostosować to zachowanie, wstawiając skomentowaną elseklauzulę.

paxdiablo
źródło
2

Dla tcsh:

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

Jestem pewien, że byłoby to przenośne rozwiązanie, z wyjątkiem zadania.

Perfect64
źródło
Wiem, że opublikowałeś to na zawsze, ale to rozwiązanie jest świetne - cieszę się, że ktoś opublikował tcsh!
user3295674,
2

Uważam, że odpowiedź @ AgileZebra (plus komentarz @ starfry) jest najbardziej użyteczna, ale ustawia headssię na skalar. Tablica jest prawdopodobnie bardziej przydatna:

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

Zauważ, że jest to tylko bash.

EndlosSchleife
źródło
Jak przekonwertowałbyś głowice na tablicę?
Stephane,
Już to zrobiłem, dodając nawiasy, na przykład echo "${heads[-1]}"drukuje ostatni element w heads. A może coś mi brakuje?
EndlosSchleife,
1

Rozwiązanie wykorzystujące eval:

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

echo $last
Mikael S.
źródło
7
eval dla referencji pośrednich to przesada, nie wspominając o złych praktykach i ogromnym zagrożeniu dla bezpieczeństwa (wartość nie jest cytowana w echu lub poza $ (). Bash ma wbudowaną składnię dla referencji pośrednich, dla dowolnej zmiennej: !Więc last="${!#}"użyłby tego samego podejście (pośrednie odniesienie do $ #) w znacznie bezpieczniejszy, kompaktowy, wbudowany, zdrowy sposób i właściwie cytowane
MestreLion
Zobacz czystszy sposób na wykonanie tego samego w innej odpowiedzi .
Palec
Cytaty @MestreLion nie są potrzebne w RHS =.
Tom Hale
1
@TomHale: prawda dla konkretnego przypadku ${!#}, ale nie ogólnie: cytaty są nadal potrzebne, jeśli treść zawiera dosłowne białe znaki, takie jak last='two words'. Tylko $()białe znaki są bezpieczne bez względu na treść.
MestreLion,
1

Po przeczytaniu powyższych odpowiedzi napisałem skrypt powłoki Q&D (powinien działać na sh i bash), aby uruchomić g ++ na PGM.cpp, aby wygenerować obraz wykonywalny PGM. Zakłada się, że ostatnim argumentem w wierszu poleceń jest nazwa pliku (.cpp jest opcjonalny), a wszystkie pozostałe argumenty są opcjami.

#!/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.
źródło
1

Poniższy LASTargument ustawi ostatni argument bez zmiany bieżącego środowiska:

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

Jeśli inne argumenty nie są już potrzebne i można je przenieść, można uprościć:

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

Ze względu na przenośność:

shift $(($#-1));

można zastąpić:

shift `expr $# - 1`

Zastępując również $()cudzysłowy otrzymujemy:

LAST=`{
   shift \`expr $# - 1\`
   echo $1
}`
echo $LAST
Paweł Nadolski
źródło
1
echo $argv[$#argv]

Teraz muszę tylko dodać tekst, ponieważ moja odpowiedź była zbyt krótka, aby opublikować. Muszę dodać więcej tekstu do edycji.

frank1rizzo
źródło
W jakiej powłoce ma to działać? Nie walnij. Nie ryb (ma, $argvale nie działa $#argv- $argv[(count $argv)]u ryb).
Beni Cherniavsky-Paskin
1

To jest część mojej funkcji kopiowania:

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

Aby użyć w skryptach, wykonaj następujące czynności:

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

Objaśnienie (najpierw najbardziej zagnieżdżone):

  1. $(echo '$'"$#")zwraca $[nr]gdzie [nr]jest liczba parametrów. Np. Ciąg $123(nierozwinięty).
  2. echo $123 zwraca wartość parametru 123. po oszacowaniu.
  3. evalpo prostu rozwija $123się do wartości parametru, np last_arg. Jest to interpretowane jako ciąg znaków i zwracane.

Działa z Bash od połowy 2015 roku.

Esavier
źródło
Podejście ewaluacyjne było tu prezentowane już wiele razy, ale to wyjaśnia, jak to działa. Można jeszcze ulepszyć, ale nadal warto je zachować.
Palec,
0
#! /bin/sh

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

echo $last
Craig Trader
źródło
Nie powiedzie się, jeśli argument jest pustym ciągiem, ale zadziała w 99,9% przypadków.
Thomas
0

Wypróbuj poniższy skrypt, aby znaleźć ostatni argument

 # 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

Wynik:

 # ./arguments.sh
 No Arguments supplied

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

Dzięki.

Ranjithkumar T.
źródło
Podejrzewam, że to się nie powiedzie z „ostatnią wartością” ./arguments.sh
Thomas
Dziękuję za sprawdzenie Thomasa, próbowałem wykonać skrypt jako taki jak mentinoed # ./arguments.sh „ostatnia wartość” Ostatni argument: wartość działa teraz poprawnie. # ./arguments.sh „sprawdzenie ostatniej wartości z podwójnym” Ostatni argument to: podwójny
Ranjithkumar T
Problem polega na tym, że ostatnim argumentem była „ostatnia wartość”, a nie wartość. Błąd jest spowodowany argumentem zawierającym spację.
Thomas
0

Jest na to o wiele bardziej zwięzły sposób. Argumenty do skryptu bash można wprowadzić do tablicy, co znacznie upraszcza obsługę elementów. Poniższy skrypt zawsze wypisze ostatni argument przekazany do skryptu.

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

Próbka wyjściowa

$ ./lastarg.sh 1 2 buckle my shoe
shoe
tekbot
źródło
0

Korzystanie z rozszerzania parametrów (usuń dopasowany początek):

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

Łatwo jest też zdobyć wszystko przed ostatnim:

prelast=${args% *}
Jurij
źródło
Możliwy (słaby) duplikat stackoverflow.com/a/37601842/1446229
Xerz
Działa to tylko wtedy, gdy ostatni argument nie zawiera spacji.
Palec
Twój prelast kończy się niepowodzeniem, jeśli ostatnim argumentem jest zdanie ujęte w podwójne cudzysłowy, przy czym zdanie to powinno być jednym argumentem.
Stephane
0

Aby zwrócić ostatni argument ostatnio używanej komendy, użyj specjalnego parametru:

$_

W tym przypadku będzie działać, jeśli zostanie użyty w skrypcie przed wywołaniem innego polecenia.

Thain
źródło
To nie jest to pytanie
retnikt
-1

Po prostu użyj !$.

$ mkdir folder
$ cd !$ # will run: cd folder
r1oga
źródło
To nie jest to pytanie
retnikt