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.

cd1
źródło

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 ':'
 }
Stephen
źródło
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.

a3nm
źródło
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]'
William Pursell
źródło
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ęść.

Nicholas MT Elliott
źródło
-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]}
Wstrzymano do odwołania.
źródło
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.

użytkownik3133260
źródło
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
Rafael
źródło
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 .

Ab Irato
źródło
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.

021
źródło
2

Korzystanie z Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0
ghostdog74
źródło
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ęść.

Crytis
źródło
1
for x in `echo $str | tr ";" "\n"`; do echo $x; done
Nahid Akbar
źródło
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.

Christoph Böddeker
źródło
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

Chris
źródło
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
baz
źródło
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
bomby
źródło
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
grząski
źródło