Dostęp do ostatnich x znaków ciągu w Bash

125

Dowiedziałem się, że za pomocą ${string:0:3}jednego można uzyskać dostęp do pierwszych 3 znaków ciągu. Czy istnieje równie łatwa metoda uzyskania dostępu do ostatnich trzech znaków?

aldorado
źródło

Odpowiedzi:

236

Ostatnie trzy znaki string:

${string: -3}

lub

${string:(-3)}

(zwróć uwagę na odstęp między :iw -3pierwszej formie).

Proszę odnieść się do Rozszerzenia parametrów powłoki w podręczniku referencyjnym :

${parameter:offset}
${parameter:offset:length}

Expands to up to length characters of parameter starting at the character
specified by offset. If length is omitted, expands to the substring of parameter
starting at the character specified by offset. length and offset are arithmetic
expressions (see Shell Arithmetic). This is referred to as Substring Expansion.

If offset evaluates to a number less than zero, the value is used as an offset
from the end of the value of parameter. If length evaluates to a number less than
zero, and parameter is not ‘@’ and not an indexed or associative array, it is
interpreted as an offset from the end of the value of parameter rather than a
number of characters, and the expansion is the characters between the two
offsets. If parameter is ‘@’, the result is length positional parameters
beginning at offset. If parameter is an indexed array name subscripted by ‘@’ or
‘*’, the result is the length members of the array beginning with
${parameter[offset]}. A negative offset is taken relative to one greater than the
maximum index of the specified array. Substring expansion applied to an
associative array produces undefined results.

Note that a negative offset must be separated from the colon by at least one
space to avoid being confused with the ‘:-’ expansion. Substring indexing is
zero-based unless the positional parameters are used, in which case the indexing
starts at 1 by default. If offset is 0, and the positional parameters are used,
$@ is prefixed to the list.

Ponieważ ta odpowiedź ma kilka regularnych wyświetleń, pozwolę sobie dodać możliwość odniesienia się do komentarza Johna Rixa ; jak wspomina, jeśli twój łańcuch ma długość mniejszą niż 3, ${string: -3}rozwija się do pustego łańcucha. Jeśli w tym przypadku chcesz rozbudować string, możesz użyć:

${string:${#string}<3?0:-3}

Używa ?:potrójnego operatora if, który może być użyty w arytmetyce powłoki ; ponieważ zgodnie z dokumentacją offset jest wyrażeniem arytmetycznym, jest to poprawne.

gniourf_gniourf
źródło
5
Zauważ, że to nie zadziała, jeśli twój łańcuch jest krótszy niż ujemne przesunięcie, które podałeś. W takich przypadkach otrzymujesz po prostu pusty ciąg.
John Rix,
2
@gniourf_gniourf Jak można tego użyć wraz z podstawianiem poleceń? Próbuję wyodrębnić trzy ostatnie znaki mojej nazwy hosta deppfx@localhost:/tmp$ echo ${$(hostname): -3} -bash: ${$(hostname): -3}: bad substitution
deppfx
3
@deppfx: nie możesz w Bash. Użyć zmiennej czasowej: temp=$(hostname); echo "${temp: -3}". Bash ma również HOSTNAMEzmienną (która może, ale nie musi, różnić się od wyniku hostname). Jeśli chcesz go użyć, po prostu zrób echo "${HOSTNAME: -3}".
gniourf_gniourf
Jak możesz to wykorzystać na wyjściu potoku? Na przykład some func | echo ${string: -3}- jak przypisać wyjście some funcdo string?
Michael Hays
1
@MichaelHays: Po prostu przypisz wyjście swojej funkcji: string=$(some func)i wtedy echo "${string: -3}".
gniourf_gniourf
48

Możesz użyć tail:

$ foo="1234567890"
$ echo -n $foo | tail -c 3
890

Dość okrężnym sposobem na uzyskanie trzech ostatnich znaków byłoby powiedzenie:

echo $foo | rev | cut -c1-3 | rev
devnull
źródło
2
„Ogon” jest również przydatny w myślniku, gdy bash jest niedostępny. Np. Sekcja skryptu startowego.
vskubriev
13

Innym obejściem jest użycie grep -oniewielkiej magii wyrażeń regularnych, aby uzyskać trzy znaki, po których następuje koniec wiersza:

$ foo=1234567890
$ echo $foo | grep -o ...$
890

Aby opcjonalnie uzyskać od 1 do 3 ostatnich znaków, w przypadku łańcuchów zawierających mniej niż 3 znaki, możesz użyć egreptego wyrażenia regularnego:

$ echo a | egrep -o '.{1,3}$'
a
$ echo ab | egrep -o '.{1,3}$'
ab
$ echo abc | egrep -o '.{1,3}$'
abc
$ echo abcd | egrep -o '.{1,3}$'
bcd

Możesz także użyć różnych zakresów, na przykład 5,10uzyskać od pięciu do dziesięciu ostatnich znaków.

Aurelio Jargas
źródło
7

Aby uogólnić pytanie i odpowiedź gniourf_gniourf (ponieważ właśnie tego szukałem), jeśli chcesz wyciąć zakres znaków od, powiedzmy, 7 od końca do 3 od końca, możesz użyć tej składni:

${string: -7:4}

Gdzie 4 to długość kursu (7-3).

Ponadto, chociaż rozwiązanie gniourf_gniourf jest oczywiście najlepsze i najładniejsze, chciałem tylko dodać alternatywne rozwiązanie wykorzystujące cięcie :

echo $string | cut -c $((${#string}-2))-$((${#string}))

Jest to bardziej czytelne, jeśli zrobisz to w dwóch wierszach, definiując długość $ {# string} jako oddzielną zmienną.

Adrian Tompkins
źródło
Wariantem niewymienionym w drugiej odpowiedzi jest połączenie tego cutpodejścia polegającego na obliczeniu początku / zatrzymania najpierw, a następnie po prostu przy użyciu tych zmiennych w rozwinięciu parametrów (warto również wspomnieć, że cuti przesunięcia basha zaczynają się odpowiednio od 1 i zera, więc wymagałoby to do uwzględnienia w obliczeniach, których tutaj nie robię): start=$((${#string}-3)); stop=$((${#string}));a następnie echo ${string: $start : $stop}vsecho $string | cut -c "$start"-"$stop"
michael
(och, nieważne - widzę, że mój komentarz nie rozwiązuje problemu zbyt krótkiego ciągu, a następnie nie powoduje żadnego wyjścia - zarówno wycięcia, jak i rozwinięcia parametrów ciągu. Mimo to, podzielenie go na zmienne ( bez konieczności wywoływania dodatkowych poleceń) ułatwia czytanie i nadal byłby dobrym pomysłem.)
michael