如何在Shell中拆分字符串并获取最后一个字段

395
假设我有一个字符串 1:2:3:4:5,我想要获取它的最后一个字段(在这种情况下是5)。在Bash中如何实现?我尝试使用cut,但我不知道如何使用-f指定最后一个字段。
17个回答

581

你可以使用字符串运算符

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

这个函数从开头一直贪婪地截取到冒号(':')。

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

8
虽然这种方法对于给定的问题有效,但是威廉在下面的答案(https://dev59.com/wHA75IYBdhLWcg3wrrJS#3163857)中给出的答案,如果字符串是 1:2:3:4:5:,也会返回 5(而使用字符串操作符则会产生空结果)。当解析可能包含(或不包含)结尾 / 字符的路径时,这种方法特别方便。 - eckes
12
那么如何做与此相反的操作?输出'1:2:3:4:'? - Dobz
20
如何保留最后一个分隔符前的部分?可以使用${foo%:*}#表示从开头匹配,%表示从结尾匹配。#%表示最短匹配,而##%%表示最长匹配。 - Mihai Danila
1
如果我想获取路径的最后一个元素,应该如何使用它?echo ${pwd##*/} 不起作用。 - Putnik
2
@Putnik 这个命令将 pwd 视为一个变量。尝试使用 dir=$(pwd); echo ${dir##*/}。对我来说有效! - Stan Strum
显示剩余3条评论

410

另一种方法是在cut之前和之后进行反转:

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

这使得获取倒数第二个字段或任何从末尾编号的字段范围非常容易。


21
这个回答不错,因为它使用了“cut”,而作者(大概)已经熟悉了这个术语。此外,我喜欢这个答案是因为正在使用“cut”,而且正好有这个问题,所以通过搜索找到了这个帖子。 - Dannid
6
一些适用于使用空格作为分隔符的复制粘贴代码:echo "1 2 3 4" | rev | cut -d " " -f1 | rev - funroll
2
the rev | cut -d -f1 | rev 是如此聪明!谢谢!帮了我很多(我的用例是 rev | -d ' ' -f 2- | rev)。 - EdgeCaseBerg
1
我总是忘记 rev,这正是我需要的!cut -b20- | rev | cut -b10- | rev - shearn89
1
我最终采用了这个解决方案。我的尝试使用“awk -F '/' '{print $NF}'”来截取文件路径在某种程度上出现了问题,因为包含空格的文件名也被切割开了。 - THX
显示剩余6条评论

112

使用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]'

7
这种解决方案相对于已接受答案的优点在于:它还可以匹配包含或不包含结尾/字符的路径:当处理 pwd | awk -F/ '{print $NF}' 时, /a/b/c/d/a/b/c/d/ 将产生相同的结果(d)。而已接受的答案在 /a/b/c/d/ 的情况下会产生空结果。 - eckes
在AWK解决方案的情况下,在GNU bash,版本4.3.48(1)-release上,这是不正确的,因为它取决于您是否有尾随斜杠。简而言之,AWK将使用“/”作为分隔符,如果您的路径为“/my/path/dir/”,它将使用最后一个分隔符之后的值,也就是一个空字符串。因此,如果您需要做像我一样的事情,最好避免尾随斜杠。 - stamster
我该如何获取最后一个字段之前的子字符串? - blackjacx
1
@blackjacx 有一些小问题,但是类似于 awk '{$NF=""; print $0}' FS=: OFS=: 这样的东西通常足够好用。 - William Pursell

40

假设使用相对简单(例如没有转义分隔符),可以使用grep:

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

找出 Breakdown - 在每行结尾的所有非分隔符 ([^:]) 字符。 -o 参数只打印匹配部分。


1
-E 表示使用扩展语法;[^...] 表示除了列出的字符之外的任何字符;+ 表示一个或多个这样的匹配项(将采用模式的最大可能长度;此项是 GNU 扩展)- 对于示例,分隔符是冒号。 - Alexander Stohr

28

如果您想使用cut,可以尝试以下内容:

echo "1:2:3:4:5" | cut -d ":" -f5
你也可以像这样使用grep
echo " 1:2:3:4:5" | grep -o '[^:]*$'

你的第二个命令对我很有用。你能把它分解一下,让我更好地理解吗?谢谢。 - John

20

一种方法:

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

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

只需要将分隔符翻译成换行符,并使用tail -1选择最后一个条目。


3
如果最后一个项目包含\n,它将失败,但对于大多数情况来说,这是最可读的解决方案。 - Yajo

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

由于许多实用程序的输出形式是原始文件名后跟冒号(:)再跟实用程序的输出(${path}:${output}),因此在最终冒号之后添加自己的控制字符非常有用,例如TAB $'\t'或单元分隔符$'\037'等。以下是在文件输出的最终冒号处添加TAB的示例:file ~/yourPath/* | sed "s/(.*:)(.*)/\1"$'\t'"\2/" - spioter

4

这里有很多好的答案,但我还是想分享一个使用 basename 的答案:

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

然而,如果您的字符串中已经存在一些斜杠 /,则此方法将失败。如果斜杠 / 是您的分隔符,则只需(应该)使用 basename 命令。

这可能不是最好的答案,但它展示了您如何在使用 bash 命令时有创意。


3
如果您的最后一个字段只有一个字符,可以采取以下操作:
a="1:2:3:4:5"

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

请查看bash中的字符串处理


1
这不起作用:它会给出 a 的最后一个 _字符_,而不是最后一个 _字段_。 - gniourf_gniourf
1
是的,这就是想法,如果您知道最后一个字段的长度,那就太好了。如果不知道,您就必须使用其他方法... - Ab Irato

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接