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


293

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

Ответы:


430

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

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

Это урезает все от фронта до «:», жадно.

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

7
В то время как это работает для данной проблемы, ответ Уильяма ниже ( stackoverflow.com/a/3163857/520162 ) также возвращает, 5если строка 1:2:3:4:5:(при использовании строковых операторов выдает пустой результат). Это особенно удобно при разборе путей, которые могут содержать (или не содержать) завершающий /символ.
Eckes

9
Как бы вы тогда поступили наоборот? повторить '1: 2: 3: 4:'?
Добз

12
И как сохранить деталь перед последним разделителем? Видимо с помощью ${foo%:*}. #- с начала; %- с конца. #, %- кратчайший матч; ##, %%- самый длинный матч.
Михай Данила

1
Если я хочу получить последний элемент из пути, как я должен его использовать? echo ${pwd##*/}не работает.
Путник

2
@Putnik, который команда видит pwdкак переменную. Попробуй dir=$(pwd); echo ${dir##*/}. Работает для меня!
Стэн Струм

344

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

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

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


17
Этот ответ хорош, потому что он использует «вырезать», что автор (предположительно) уже знаком. Кроме того, мне нравится этот ответ, потому что я использую 'cut' и у меня был именно этот вопрос, поэтому я нашел эту тему через поиск.
Даннид

6
echo "1 2 3 4" | rev | cut -d " " -f1 | rev
Небольшой

2
рев | cut -d -f1 | Rev такой умный! Спасибо! Помогла мне куча (мой вариант использования был rev | -d '' -f 2- | rev
EdgeCaseBerg

1
Я всегда забываю о том rev, что мне было нужно! cut -b20- | rev | cut -b10- | rev
shearn89

Я закончил с этим решением, моя попытка обрезать пути к файлам с помощью "awk -F" / "'{print $ NF}'" несколько сработала для меня, так как имена файлов, включая пробелы, также были разделены
THX

76

Трудно получить последнее поле, используя 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]'

5
большое преимущество этого решения перед принятым ответом: оно также соответствует путям, которые содержат или не содержат завершающий /символ: /a/b/c/dи /a/b/c/d/дают тот же результат ( d) при обработке pwd | awk -F/ '{print $NF}'. Принятый ответ приводит к пустому результату в случае/a/b/c/d/
eckes

@eckes В случае решения AWK, в GNU bash, версия 4.3.48 (1) - выпустите, что это не так, поскольку это важно, когда у вас есть косая черта или нет. Проще говоря, AWK будет использовать в /качестве разделителя, и если ваш путь будет, /my/path/dir/он будет использовать значение после последнего разделителя, который является просто пустой строкой. Поэтому лучше избегать косой черты, если вам нужно сделать такую ​​вещь, как я.
Stamster

Как бы получить подстроку до последнего поля?
blackjacx

1
@blackjacx Есть некоторые причуды, но что-то подобное awk '{$NF=""; print $0}' FS=: OFS=:часто работает достаточно хорошо.
Уильям Перселл

29

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

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

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


-E означает использование расширенного синтаксиса; [^ ...] означает что угодно, кроме перечисленных символов; + одно или несколько таких попаданий (займет максимально возможную длину для шаблона; этот элемент является расширением GNU) - например, разделяющий символ (ы) - это двоеточие.
Александр Стор

18

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

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]}

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

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


Он потерпит неудачу, если последний элемент содержит \n, но в большинстве случаев это наиболее читаемое решение.
Яхо

7

Использование 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

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

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

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

Проверьте манипуляции со строками в bash .


Это не работает: он дает последний символ из a, а не последнего поля .
gniourf_gniourf

1
Правда, это идея, если вы знаете длину последнего поля, это хорошо. Если нет, вы должны использовать что-то еще ...
Аб Ирато

4

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

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

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

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


2

Используя Bash.

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

Мог бы использовать echo ${!#}вместо eval echo \$${#}.
Рафа

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

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


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

2
Это сталкивается с проблемами, если в любом из полей есть пробел. Кроме того, он не имеет прямого отношения к вопросу получения последнего поля.
chepner

1

Для тех, кто знаком с 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, который будет применен к вводу.


1

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

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

Важно отметить, однако, что при использовании этого метода и числах больше, чем 1 цифра (или больше, чем один символ в любых обстоятельствах), вам нужно будет выполнить еще одну команду 'rev' для конвейерного вывода.

[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

Надеюсь, я могу помочь, ура


1

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

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

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

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

0

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

# install pythonp
pythonp -m pip install pythonp

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

Python может сделать это напрямую: echo "1:2:3:4:5" | python -c "import sys; print(list(sys.stdin)[0].split(':')[-1])"
MortenB

@ MortenB Вы ошибаетесь. Цель pythonpпакета - заставить вас делать то же самое, что и python -cпри меньшем наборе символов. Пожалуйста, посмотрите на README в репозитории.
бомбы

0

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

$ foo=1:2:3:4:5
$ echo ${foo} | sed "s/.*://"
5
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.