Jak podzielić ciąg w powłoce i uzyskać ostatnie pole


293

Załóżmy, że mam ciąg znaków 1:2:3:4:5i chcę uzyskać jego ostatnie pole ( 5w tym przypadku). Jak to zrobić za pomocą Bash? Próbowałem cut, ale nie wiem, jak określić ostatnie pole -f.

Odpowiedzi:


430

Możesz użyć operatorów łańcuchowych :

$ foo=1:2:3:4:5
$ echo ${foo##*:}
5

To łapczywie przycina wszystko od przodu do „:”.

${foo  <-- from variable foo
  ##   <-- greedy front trim
  *    <-- matches anything
  :    <-- until the last ':'
 }

7
Podczas gdy działa to na dany problem, odpowiedź Williama poniżej ( stackoverflow.com/a/3163857/520162 ) zwraca również, 5jeśli ciąg jest 1:2:3:4:5:(podczas korzystania z operatorów ciąg daje pusty wynik). Jest to szczególnie przydatne podczas analizowania ścieżek, które mogą zawierać (lub nie) postać końcową /.
eckes

9
Jak więc zrobiłbyś coś przeciwnego? echo „1: 2: 3: 4:”?
Dobz

12
Jak zachować tę część przed ostatnim separatorem? Najwyraźniej za pomocą ${foo%:*}. #- od początku; %- od końca. #, %- najkrótszy mecz; ##, %%- najdłuższy mecz.
Mihai Danila

1
Jeśli chcę pobrać ostatni element ze ścieżki, jak go użyć? echo ${pwd##*/}nie działa.
Putnik

2
@Putnik to polecenie widzi pwdjako zmienną. Spróbować dir=$(pwd); echo ${dir##*/}. Pracuje dla mnie!
Stan Strum,

344

Innym sposobem jest odwrócenie przed i po cut:

$ echo ab:cd:ef | rev | cut -d: -f1 | rev
ef

Ułatwia to uzyskanie przedostatniego pola lub dowolnego zakresu pól ponumerowanych od końca.


17
Ta odpowiedź jest miła, ponieważ używa „cut”, którą autor (prawdopodobnie) już zna. Plus, lubię tę odpowiedź, bo ja używam „Wytnij” i miał dokładnie na to pytanie, a więc znalezienie tego wątku w wyszukiwarce.
Dannid

6
Trochę pasty do wycinania i wklejania dla osób używających spacji jako ograniczników:echo "1 2 3 4" | rev | cut -d " " -f1 | rev
funroll

2
rev | cut -d -f1 | rev jest taki sprytny! Dzięki! Pomógł mi kilka (mój przypadek użycia to rev | -d '' -f 2- | rev
EdgeCaseBerg

1
Zawsze o czym zapominam rev, właśnie tego potrzebowałem! cut -b20- | rev | cut -b10- | rev
shearn89

Skończyło się na tym rozwiązaniu, moja próba wycięcia ścieżek plików za pomocą „awk -F” / „{print $ NF}” ”trochę się zepsuła, ponieważ nazwy plików, w tym spacje, również zostały podzielone
THX

76

Trudno jest uzyskać ostatnie pole za pomocą cut, ale oto kilka rozwiązań w awk i perl

echo 1:2:3:4:5 | awk -F: '{print $NF}'
echo 1:2:3:4:5 | perl -F: -wane 'print $F[-1]'

5
wielka przewaga tego rozwiązania nad przyjętą odpowiedzią: pasuje również do ścieżek zawierających /znak końcowy lub nie : /a/b/c/di /a/b/c/d/daje ten sam wynik ( d) podczas przetwarzania pwd | awk -F/ '{print $NF}'. Przyjęta odpowiedź powoduje, że wynik jest pusty w przypadku/a/b/c/d/
eckes

@eckes W przypadku rozwiązania AWK, na GNU bash, wersja 4.3.48 (1) - to nieprawda, ponieważ ma to znaczenie, gdy masz ukośnik, czy nie. Po prostu AWK użyje /jako separatora, a jeśli twoja ścieżka jest /my/path/dir/, użyje wartości po ostatnim separatorze, który jest po prostu pustym ciągiem. Więc najlepiej unikać końcowego slashowania, jeśli musisz zrobić coś takiego jak ja.
stamster

Jak uzyskać podciąg do ostatniego pola?
blackjacx

1
@blackjacx Istnieją pewne dziwactwa, ale coś takiego awk '{$NF=""; print $0}' FS=: OFS=:często działa wystarczająco dobrze.
William Pursell

29

Zakładając dość proste użycie (na przykład bez ucieczki z separatora), możesz użyć grep:

$ echo "1:2:3:4:5" | grep -oE "[^:]+$"
5

Podział - znajdź wszystkie znaki nie ogranicznik ([^:]) na końcu wiersza ($). -o drukuje tylko pasującą część.


-E oznacza użycie rozszerzonej składni; [^ ...] oznacza wszystko oprócz wymienionych znaków; + jedno lub więcej takich trafień (zajmie maksymalną możliwą długość wzoru; ten element jest rozszerzeniem GNU) - na przykład znak (-y) oddzielający to dwukropek.
Alexander Stohr

18

Jednokierunkowa:

var1="1:2:3:4:5"
var2=${var1##*:}

Inny, używając tablicy:

var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
var2=${var2[@]: -1}

Jeszcze inny z tablicą:

var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
count=${#var2[@]}
var2=${var2[$count-1]}

Używanie wyrażeń regularnych Bash (wersja> = 3.2):

var1="1:2:3:4:5"
[[ $var1 =~ :([^:]*)$ ]]
var2=${BASH_REMATCH[1]}

11
$ echo "a b c d e" | tr ' ' '\n' | tail -1
e

Po prostu przetłumacz ogranicznik na nowy wiersz i wybierz ostatni wpis za pomocą tail -1.


Nie powiedzie się, jeśli ostatni element zawiera \n, ale w większości przypadków jest to najbardziej czytelne rozwiązanie.
Yajo

7

Używanie sed:

$ echo '1:2:3:4:5' | sed 's/.*://' # => 5

$ echo '' | sed 's/.*://' # => (empty)

$ echo ':' | sed 's/.*://' # => (empty)
$ echo ':b' | sed 's/.*://' # => b
$ echo '::c' | sed 's/.*://' # => c

$ echo 'a' | sed 's/.*://' # => a
$ echo 'a:' | sed 's/.*://' # => (empty)
$ echo 'a:b' | sed 's/.*://' # => b
$ echo 'a::c' | sed 's/.*://' # => c

4

Jeśli twoje ostatnie pole to pojedynczy znak, możesz to zrobić:

a="1:2:3:4:5"

echo ${a: -1}
echo ${a:(-1)}

Sprawdź manipulację ciągiem w bash .


To nie działa: daje ostatni znak z a, nie ostatni pola .
gniourf_gniourf

1
To prawda, że ​​jeśli znasz długość ostatniego pola, to dobrze. Jeśli nie, musisz użyć czegoś innego ...
Ab Irato,

4

Tutaj jest wiele dobrych odpowiedzi, ale nadal chcę udostępnić tę za pomocą basename :

 basename $(echo "a:b:c:d:e" | tr ':' '/')

Jednak nie powiedzie się, jeśli w ciągu jest już trochę „/” . Jeśli slash / jest twoim ogranicznikiem, musisz (i powinieneś) użyć basename.

To nie jest najlepsza odpowiedź, ale pokazuje tylko, jak możesz być kreatywny za pomocą poleceń bash.


2

Korzystanie z Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0

Mógł użyć echo ${!#}zamiast eval echo \$${#}.
Rafa

2
echo "a:b:c:d:e"|xargs -d : -n1|tail -1

Najpierw użyj xargs, podziel go za pomocą „:”, - n1 oznacza, że ​​każda linia ma tylko jedną część.


1
for x in `echo $str | tr ";" "\n"`; do echo $x; done

2
Powoduje to problemy, jeśli w jednym z pól występuje spacja. Ponadto nie rozwiązuje bezpośrednio problemu pobrania ostatniego pola.
chepner

1

Dla tych, którzy dobrze znają Python, https://github.com/Russell91/pythonpy to dobry wybór, aby rozwiązać ten problem.

$ echo "a:b:c:d:e" | py -x 'x.split(":")[-1]'

Z pomocą pythonpy: -x treat each row of stdin as x.

Za pomocą tego narzędzia łatwo jest napisać kod Pythona, który zostanie zastosowany do danych wejściowych.


1

Odpowiedź może być nieco spóźniona, chociaż prostym rozwiązaniem jest odwrócenie kolejności ciągu wejściowego. To pozwoli ci zawsze zdobyć ostatni przedmiot bez względu na długość.

[chris@desktop bin]$ echo 1:2:3:4:5 | rev | cut -d: -f1
5

Należy jednak pamiętać, że jeśli używasz tej metody, a liczby są większe niż 1 cyfra (lub w każdym przypadku większe niż jeden znak), będziesz musiał uruchomić inną komendę „rev” na wyjściu potokowym.

[chris@desktop bin]$ echo 1:2:3:4:5:8:24 | rev | cut -d: -f1
42
[chris@desktop bin]$ echo 1:2:3:4:5:8:24 | rev | cut -d: -f1 | rev
24

Mam nadzieję, że mogę pomóc, na zdrowie


1

Rozwiązanie wykorzystujące wbudowane czytanie:

IFS=':' read -a fields <<< "1:2:3:4:5"
echo "${fields[4]}"

Lub, aby uczynić go bardziej ogólnym:

echo "${fields[-1]}" # prints the last item

0

Jeśli podoba Ci się Python i masz opcję instalacji pakietu, możesz użyć tego narzędzia Python .

# install pythonp
pythonp -m pip install pythonp

echo "1:2:3:4:5" | pythonp "l.split(':')[-1]"
5

python może to zrobić bezpośrednio: echo "1:2:3:4:5" | python -c "import sys; print(list(sys.stdin)[0].split(':')[-1])"
MortenB

@MortenB Mylisz się. Głównym celem pythonppakietu jest zrobienie tego samego, co python -cprzy mniejszej liczbie znaków. Proszę spojrzeć na README w repozytorium.
bomby

0

Dopasowywanie wyrażeń regularnych sedjest zachłanne (zawsze przechodzi do ostatniego wystąpienia), co możesz wykorzystać na swoją korzyść tutaj:

$ foo=1:2:3:4:5
$ echo ${foo} | sed "s/.*://"
5
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.