Jak uzyskać ostatni znak łańcucha w powłoce?

125

Napisałem następujące wiersze, aby uzyskać ostatni znak ciągu:

str=$1
i=$((${#str}-1))
echo ${str:$i:1}

Działa dla abcd/:

$ bash last_ch.sh abcd/
/

Nie działa dlaabcd* :

$ bash last_ch.sh abcd*
array.sh assign.sh date.sh dict.sh full_path.sh last_ch.sh

Zawiera listę plików w bieżącym folderze .

myśliciel3
źródło

Odpowiedzi:

97

To jeden z powodów, dla których musisz cytować swoje zmienne:

echo "${str:$i:1}"

W przeciwnym razie bash rozwija zmienną iw tym przypadku wykonuje globbing przed wydrukowaniem. Lepiej też zacytować parametr do skryptu (w przypadku, gdy masz pasującą nazwę pliku):

sh lash_ch.sh 'abcde*'

Zobacz także kolejność rozszerzeń w podręczniku bash . Zmienne są rozwijane przed rozwinięciem nazwy pliku.

Aby uzyskać ostatni znak, należy po prostu użyć go -1jako indeksu, ponieważ indeksy ujemne liczą się od końca ciągu:

echo "${str: -1}"

Spacja po dwukropku ( :) jest WYMAGANA.

Takie podejście nie zadziała bez przestrzeni.

perreal
źródło
62
Swoją drogą, ujemne indeksy liczą się od prawej, więc "${1: -1}"wystarczy.
choroba
7
Powinieneś odpowiedzieć jako formalne rozwiązanie, a nie tutaj w komentarzach.
BMW,
FYI: możesz to również zrobić w „jednej linijce” jako echo "${str:$((${#str}-1)):1}". Pozwala to również na zmianę zwracanych znaków końcowych. Ta praca dla mnie w bash v4.1.0 (1)
SaxDaddy
16
Odpowiedź @ choroby: Uwaga na tę cholerną przestrzeń! To zawsze mnie "${1:-1}
denerwuje
192

Według @perreal, cytowanie zmiennych jest ważne, ale ponieważ przeczytałem ten post 5 razy, zanim w komentarzach znajdę prostsze podejście do pytania ...

str='abcd/'
echo "${str: -1}"

Wynik: /

str='abcd*'
echo "${str: -1}"

Wynik: *

Dziękuję wszystkim, którzy brali w tym udział powyżej; Odpowiednio dodałem +1 w całym wątku!

quickshiftin
źródło
25
Uwaga: zwróć uwagę na przestrzeń wewnątrz ${std: -1}, bez miejsca to nie zadziała. Wpadłem na to i stwierdziłem, że ${std:~0}również działa (ze spacją lub bez). Nie jestem jednak pewien, czy jest to udokumentowane zachowanie, ale nie zawracałem sobie głowy sprawdzaniem tego.
Bart,
4
jest udokumentowane. man bash mówi: Wyrażenia arytmetyczne zaczynające się od - muszą być oddzielone spacjami od poprzedniego: aby je odróżnić od rozszerzenia Użyj wartości domyślnych
mighq
3
To niesamowite, dzięki za udostępnienie. Strona podręcznika BASH jest jednak prawie przytłaczająca do czytania, byłem tam kilka razy. Myślę, że mogliby zrobić kurs w college'u na temat skryptów powłoki lol.
quickshift w
3
To rozwiązanie nie działa w .shskrypcie podczas próby przypisania pobranego znaku do zmiennej. Na przykład declare LAST=$(echo "${str: -1}") spowoduje to nieprzyjemny czerwony pasek pokazujący błąd składni zaczynający się od:
krb686
3
jeśli sprawdzanie składni edytora nie odpowiada tej spacji, spróbuj${str:0-1}
ttt
36

Wiem, że to bardzo stary wątek, ale nikt nie wspomniał, która dla mnie jest najczystsza:

echo -n $str | tail -c 1

Zwróć uwagę, że -njest tak, aby echo nie zawierało znaku nowej linii na końcu.

user5394399
źródło
15
Nie jest nawet blisko bycia czystszym niż $ {str: -1}
shrewmouse
8
Jest czystszy, ponieważ nie opiera się na bashizmie, więc będzie działać z każdą powłoką Posix.
John Eikenberry,
Co jest najbardziej „bashist”? „$ {str: -1}” I… Co jest najczystsze? "$ {str: -1}" Bash to besht! :-)
Roger
Dla każdego, kto próbował wydrukować ostatnie znaki z dużego pliku, zrobiło to za mnie: cat user / folder / file.json | tail -c 1 Możesz zastąpić 1 liczbą znaków, które chcesz wydrukować.
Diego Serrano
5

inne rozwiązanie wykorzystujące skrypt awk:

ostatni 1 znak:

echo $str | awk '{print substr($0,length,1)}'

ostatnie 5 znaków:

echo $str | awk '{print substr($0,length-5,5)}'
mr.baby123
źródło
1
Nie to, o co prosił OP, ale jest to najlepsze rozwiązanie, które znalazłem do użytku z plikiem. Większość innych podejść nie będzie działać w przypadku plików, na przykład: cat file | awk '{print substr($0,length,1)}'Dzięki!
xmar
4

Pojedyncza linia:

${str:${#str}-1:1}

Teraz:

echo "${str:${#str}-1:1}"
Eduardo Cuomo
źródło
1
Dlaczego używasz dwóch par nawiasów? Ponadto od wersji 4.2 możesz używać ujemnych przesunięć, jak pokazano w odpowiedzi quickshiftin: echo "${str: -1}"wystarczy (pamiętaj o spacji między :i -).
gniourf_gniourf
W operacjach arytmetycznych używane są dwie pary nawiasów
Eduardo Cuomo
Nie. Zapoznaj się z odpowiednią częścią podręcznika, w której przeczytasz: długość i przesunięcie to wyrażenia arytmetyczne (zobacz Arytmetyka powłoki ); stąd jesteś już w arytmetyce powłoki i nie potrzebujesz w ogóle żadnych nawiasów. Poza tym, gdyby to, co powiedziałeś, było prawdą, bardziej logiczne byłoby rozszerzenie arytmetyczne z $((...)).
gniourf_gniourf
@gniourf_gniourf masz rację! Teraz rozumiem twoje poprzednie pytanie. Odpowiedź zaktualizowana
Eduardo Cuomo
4

Każda dotychczasowa odpowiedź sugeruje, że słowo „powłoka” w pytaniu jest równoznaczne z Bash.

Oto jak można to zrobić w standardowej powłoce Bourne'a:

printf $str | tail -c 1
phep
źródło
1
echo -nzgodnie z propozycją user5394399 pozwala uniknąć problemów, gdy $strzawiera znaki formatu specjalnego.
Conrad Meyer
-nPrzełącznik jest składnią Basha. Nie będzie działać w standardowej powłoce Bourne'a jako myślnik, itp ... I był to punkt mojej odpowiedzi.
phep
1
Niezupełnie bashism, ale POSIX rozpoznaje, że jest zdefiniowany jako implementacja ("Jeśli pierwszy operand to -n, lub jeśli którykolwiek z operandów zawiera znak <backlash>, wyniki są zdefiniowane przez implementację"). Zamiast tego można by poprawić swoją odpowiedź printf "%s" "$str" | ...:-).
Conrad Meyer
4

Próbować:

"${str:$((${#str}-1)):1}"

Na przykład:

someone@mypc:~$ str="A random string*"; echo "$str"
A random string*
someone@mypc:~$ echo "${str:$((${#str}-1)):1}"
*
someone@mypc:~$ echo "${str:$((${#str}-2)):1}"
g
mark_infinite
źródło
-2
echo $str | cut -c $((${#str}))

to dobre podejście

pradeep
źródło