Как разбить строку в оболочке и получить последнее поле

Предположим, у меня есть строка 1:2:3:4:5, и я хочу получить ее последнее поле (в данном случае 5). Как мне это сделать с помощью Bash? Я пробовал cut, но не знаю, как указать последнее поле с помощью -f.


person cd1    schedule 01.07.2010    source источник


Ответы (17)


Вы можете использовать строковые операторы:

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

Это с жадностью обрезает все от лицевой стороны до символа ":".

${foo  <-- from variable foo
  ##   <-- greedy front trim
  *    <-- matches anything
  :    <-- until the last ':'
 }
person Stephen    schedule 02.07.2010
comment
Хотя это работает для данной проблемы, ответ Уильяма ниже (stackoverflow.com/a/3163857/520162) также возвращает 5, если строка равна 1:2:3:4:5: (при использовании строковых операторов возвращается пустой результат). Это особенно удобно при анализе путей, которые могут содержать (или не содержать) завершающий символ /. - person eckes; 23.01.2013
comment
Как бы вы тогда поступили наоборот? для вывода "1: 2: 3: 4:"? - person Dobz; 25.06.2014
comment
А как сохранить часть перед последним разделителем? Видимо, используя ${foo%:*}. # - с начала; % - с конца. #, % - кратчайшее совпадение; ##, %% - самое длинное совпадение. - person Mihai Danila; 09.07.2014
comment
Если я хочу получить последний элемент из пути, как мне его использовать? echo ${pwd##*/} не работает. - person Putnik; 12.02.2016
comment
@Putnik, эта команда видит pwd как переменную. Попробуйте dir=$(pwd); echo ${dir##*/}. Работает для меня! - person Stan Strum; 17.12.2017
comment
@Stephen Как использовать точку (.) В качестве разделителя? - person Manoj; 27.06.2018
comment
@Stan еще короче echo ${$(pwd)##*/} - person Cadoiz; 16.06.2021

Другой способ - поменять местами до и после cut:

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

Это позволяет очень легко получить предпоследнее поле или любой диапазон полей, пронумерованных с конца.

person a3nm    schedule 03.02.2012
comment
Этот ответ хорош, потому что в нем используется слово «вырезать», которое автору (предположительно) уже известно. Кроме того, мне нравится этот ответ, потому что Я использую слово «вырезать» и у меня возник именно такой вопрос, поэтому я нашел эту ветку через поиск. - person Dannid; 15.01.2013
comment
Немного вырезки и вставки для людей, использующих пробелы в качестве разделителей: echo "1 2 3 4" | rev | cut -d " " -f1 | rev - person funroll; 12.08.2013
comment
рев | вырезать -d -f1 | rev такой умный! Спасибо! Мне очень помогло (мой вариант использования был rev | -d '' -f 2- | rev - person EdgeCaseBerg; 08.09.2013
comment
Я всегда забываю о rev, это было именно то, что мне было нужно! cut -b20- | rev | cut -b10- | rev - person shearn89; 17.08.2017
comment
Я закончил с этим решением, моя попытка вырезать пути к файлам с помощью awk -F / '{print $ NF}' несколько сломалась для меня, так как имена файлов, включая пробелы, также были разделены - person THX; 26.02.2018
comment
Осторожно: rev небезопасен с многобайтовыми символами Юникода! Поэтому некоторые угловые случаи могут не работать с rev. - person t0r0X; 02.03.2018
comment
@ t0r0X: Ты уверен? На моей машине с LC_ALL=en_US.utf8 запуск echo 'hé' | rev правильно возвращает éh. Мне нужно запустить echo 'hé' | LC_ALL=C rev, чтобы получить сообщение об ошибке: rev: stdin: Invalid or incomplete multibyte or wide character. - person a3nm; 02.03.2018
comment
за исключением того, что: -sh: rev: command not found на моем NAS кажется, что rev не так распространен, в противном случае я согласен, что он лучше отвечает на вопрос о сокращении - person papo; 19.11.2018
comment
Фантастика! Я хотел получить только домены верхнего и второго уровня из доменного имени. С cut я могу превратить www.google.com в google.com! - person b_laoshi; 29.11.2018
comment
Если вы хотите извлечь TLD из списка доменов: cat domains.txt | rev | cut -d. -f2 | rev | sort | uniq -c | sort -rn - person gies0r; 30.04.2020

Трудно получить последнее поле с помощью cut, но вот несколько решений на awk и perl

echo 1:2:3:4:5 | awk -F: '{print $NF}'
echo 1:2:3:4:5 | perl -F: -wane 'print $F[-1]'
person William Pursell    schedule 02.07.2010
comment
большое преимущество этого решения перед принятым ответом: оно также сопоставляет пути, содержащие или не содержащие завершающий символ /: /a/b/c/d и /a/b/c/d/ дают одинаковый результат (d) при обработке pwd | awk -F/ '{print $NF}'. Принятый ответ дает пустой результат в случае /a/b/c/d/ - person eckes; 23.01.2013
comment
@eckes В случае решения AWK, в GNU bash, версия 4.3.48 (1) -release, это неверно, так как это имеет значение, если у вас есть завершающая косая черта или нет. Проще говоря, AWK будет использовать / в качестве разделителя, а если ваш путь - /my/path/dir/, он будет использовать значение после последнего разделителя, которое является просто пустой строкой. Так что лучше избегать косой черты в конце, если вам нужно сделать что-то подобное, как это делаю я. - person stamster; 21.05.2018
comment
Как мне получить подстроку ДО последнего поля? - person blackjacx; 09.06.2020
comment
@blackjacx Есть некоторые причуды, но что-то вроде awk '{$NF=""; print $0}' FS=: OFS=: часто работает достаточно хорошо. - person William Pursell; 09.06.2020

Предполагая довольно простое использование (например, без экранирования разделителя), вы можете использовать grep:

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

Разбивка - найдите все символы, кроме разделителя ([^:]) в конце строки ($). -o печатает только соответствующую часть.

person Nicholas M T Elliott    schedule 01.07.2010
comment
-E означает использование расширенного синтаксиса; [^ ...] означает что угодно, кроме перечисленных символов; + один или несколько таких совпадений (будет иметь максимально возможную длину для шаблона; этот элемент является расширением GNU) - например, разделительные символы - это двоеточие. - person Alexander Stohr; 17.10.2019

В одну сторону:

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

Другой, используя массив:

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

Еще один с массивом:

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

Использование регулярных выражений Bash (версия> = 3.2):

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

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

Просто переведите разделитель в новую строку и выберите последнюю запись с tail -1.

person user3133260    schedule 24.12.2013
comment
Он завершится ошибкой, если последний элемент содержит \n, но в большинстве случаев это наиболее удобочитаемое решение. - person Yajo; 30.07.2014

Вы можете попробовать что-то вроде этого, если хотите использовать cut:

echo "1:2:3:4:5" | cut -d ":" -f5

Вы также можете использовать grep вот так:

echo " 1:2:3:4:5" | grep -o '[^:]*$'
person Abdallah_98    schedule 06.02.2021
comment
Ваша вторая команда мне пригодилась. Не могли бы вы сломать это, чтобы я мог лучше понять? Спасибо. - person John; 02.03.2021
comment
Второй работает как шарм! Спасибо - person borja garcia; 15.04.2021

Использование 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
person Rafael    schedule 10.11.2016
comment
учитывая, что вывод многих утилит имеет форму исходного имени файла, за которым следует двоеточие (:), за которым следует вывод утилиты ($ {path}: $ {output}), это невероятно полезно для добавления вашего собственного управляющего символа, такого как TAB $ '\ t' или разделитель единиц $ '\ 037' и т. д. после последнего двоеточия. пример добавления TAB в последнее двоеточие вывода файла: file ~ / yourPath / * | sed s / \ (. *: \) \ (. * \) / \ 1 $ '\ t' \ 2 / - person spioter; 03.09.2020

Если ваше последнее поле состоит из одного символа, вы можете сделать это:

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

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

Проверьте обработку строк в bash.

person Ab Irato    schedule 13.11.2013
comment
Это не работает: это дает последний символ a, а не последнее поле. - person gniourf_gniourf; 13.11.2013
comment
Правда, в том-то и дело, если вы знаете длину последнего поля, это хорошо. Если нет, вам нужно использовать что-то еще ... - person Ab Irato; 25.11.2013

Здесь есть много хороших ответов, но я все же хочу поделиться этим, используя basename:

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

Однако он завершится ошибкой, если в вашей строке уже есть "/". Если косая черта / является вашим разделителем, вам просто нужно (и следует) использовать базовое имя.

Это не лучший ответ, но он просто показывает, как вы можете проявить творческий подход, используя команды bash.

person 021    schedule 26.04.2016

Используя Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0
person ghostdog74    schedule 02.07.2010
comment
Можно было использовать echo ${!#} вместо eval echo \$${#}. - person Rafa; 28.04.2017

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

Сначала используйте xargs, разделите его с помощью ":", - n1 означает, что каждая строка имеет только одну часть. Затем, pring последнюю часть.

person Crytis    schedule 07.12.2016

Сопоставление регулярных выражений в sed является жадным (всегда идет до последнего вхождения), что вы можете использовать здесь в своих интересах:

$ foo=1:2:3:4:5
$ echo ${foo} | sed "s/.*://"
5
person slushy    schedule 29.01.2019

Решение с использованием встроенного чтения:

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

Или, чтобы сделать его более общим:

echo "${fields[-1]}" # prints the last item
person baz    schedule 24.11.2017

Для тех, кто знаком с Python, хорошим выбором будет https://github.com/Russell91/pythonpy. чтобы решить эту проблему.

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

Из справки pythonpy: -x treat each row of stdin as x.

С помощью этого инструмента легко написать код Python, который применяется к входным данным.

Изменить (декабрь 2020 г.): Pythonpy больше не в сети. Вот альтернатива:

$ echo "a:b:c:d:e" | python -c 'import sys; sys.stdout.write(sys.stdin.read().split(":")[-1])'

он содержит больше шаблонного кода (т.е. sys.stdout.read/write), но требует только std-библиотек из python.

person Christoph Böddeker    schedule 19.02.2018

Если вам нравится python и у вас есть возможность установить пакет, вы можете использовать эту утилиту python.

# install pythonp
pythonp -m pip install pythonp

echo "1:2:3:4:5" | pythonp "l.split(':')[-1]"
5
person bombs    schedule 05.01.2019
comment
python может сделать это напрямую: echo "1:2:3:4:5" | python -c "import sys; print(list(sys.stdin)[0].split(':')[-1])" - person MortenB; 06.03.2019
comment
@MortenB Вы ошибаетесь. Вся цель пакета pythonp - заставить вас делать то же самое, что и python -c, с меньшим количеством символов. Пожалуйста, посмотрите README в репозитории. - person bombs; 08.03.2019

person    schedule
comment
Это приводит к проблемам, если в любом из полей есть пробелы. Кроме того, он не решает напрямую вопрос о получении поля last. - person chepner; 22.06.2012