如何在Bash中提取字符串的最后一部分?

68

我有这个变量:

A="Some variable has value abc.123"

我需要提取这个值,即abc.123。在bash中是否可能?

10个回答

157

最简单的方法是:

echo "$A" | awk '{print $NF}'

编辑:这是它的工作原理的解释...

awk 将输入拆分为不同的字段,默认使用空格作为分隔符。将 NF 的位置硬编码为 5 可以打印输入中的第五个字段:

echo "$A" | awk '{print $5}'

NF 是内置的 awk 变量,它给出当前记录中字段的总数。以下语句返回数字5,因为字符串 "Some variable has value abc.123" 中有5个字段:

NF是一个内置的awk变量,用于确定当前记录中的字段总数。下面的示例返回数字5,因为字符串"Some variable has value abc.123"中有5个字段:

echo "$A" | awk '{print NF}'

$NF组合在一起,可以输出字符串中的最后一个域,无论该字符串包含多少个域。


4
这是命令行的最佳答案,我想知道为什么它不太受欢迎。 - Sergey Grinev
3
"print NF" 的意思是打印单词数量,"$NF" 表示最后一个单词。请注意,翻译时需要保证内容准确无误,并且使用通俗易懂的语言,但不能改变原意。 - karsten
不,如果可以避免使用外部进程,那么使用shell内置命令会更好。为了完整起见,也许应该展示如何使用awk -F "x"来使用不同于空格的分隔符,但同样,这也可以通过shell内置命令轻松完成。 - tripleee
最简单的方法是 ${A##* },例如 extract=${A##* }echo ${A##* }。编辑:正如@mwfeamley的答案所说。 - jthill

71

21

使用参数扩展的一些示例

A="Some variable has value abc.123"
echo "${A##* }"

abc.123

在空格" "上的最长匹配

echo "${A% *}"

Some variable has value

点号上的最长匹配

echo "${A%.*}"

Some variable has value abc

" "空格的最短匹配

echo "${A%% *}"

some

阅读更多 Shell参数扩展


20

这份文档有点难以阅读,所以我用更简单的方式进行了总结。

请注意,根据您使用的是#还是%,'*'需要与' '交换位置。(*只是一个通配符,所以在阅读时可能需要摘下您的"正则表达式帽子"。)

  • ${A% *} - 删除最短的尾部 *(去除最后一个单词
  • ${A%% *} - 删除最长的尾部 *(去除最后几个单词
  • ${A#* } - 删除最短的前导* (去除第一个单词
  • ${A##* } - 删除最长的前导* (去除前几个单词

当然,在这里,一个"单词"可以包含任何不是字面上的空格的字符。

你可能经常使用这个语法来修剪文件名中的其他字符:
  • ${A##*/}从路径的开头删除所有包含的文件夹(如果有的话),例如:
    /usr/bin/git -> git
    /usr/bin/ -> 空字符串
    /usr/bin -> bin

  • ${A%/*}从路径的末尾删除最后一个文件夹/文件/斜杠(如果有的话):
    /usr/bin/git -> /usr/bin
    /usr/bin/ -> /usr/bin
    /usr/bin -> /usr/

  • ${A%.*}删除最后一个扩展名(如果有的话,但要注意像/my.path/noext这样的情况):
    archive.tar.gz -> archive.tar
    /my.path/noext -> /my(!)

为了避免最后一个问题,如果你知道的话,可以使用明确的扩展名:%.ext而不是%.*

有没有办法在路径中移除最后一个扩展名,但如果没有扩展名则不做任何操作,例如在my.path/noext这样的路径上?例如,在/etc/*.d/上会发生这种情况。 - undefined
1
你知道扩展名是什么吗?比如说是.conf,你可以使用${A%.conf}。如果字符串不是以.conf结尾,它会保持不变。 - undefined

14

如何知道值从哪里开始?如果它总是第五和第六个单词,你可以使用例如:

B=$(echo "$A" | cut -d ' ' -f 5-)

这里使用 cut 命令来截取行的一部分,使用简单的空格作为单词分隔符。


谢谢!我一直在寻找一种方法来从字段x到字符串结尾,并且无法弄清楚用于“-f”字段列表的通配符字符,然后偶然发现了这里使用“-”。我猜这是因为我看了BSD下“cut”手册页的缘故。 - Diffuser

5
正如Zedfoxus在这里所指出的那样,这是一种在所有基于Unix的系统上都有效的非常简洁的方法。此外,您不需要知道子字符串的确切位置。
A="Some variable has value abc.123"

echo "$A" | rev | cut -d ' ' -f 1 | rev                                                                                            

# abc.123

4

更多方法:

(在终端中运行以下每个命令以测试实时效果。)

对于以下所有答案,请首先在终端中输入以下内容:

A="Some variable has value abc.123"

数组示例(下面的第3个示例)是非常有用的模式,根据您想要做什么,有时是最佳选择。

1. 使用 awk,如主答案所示(链接)

echo "$A" | awk '{print $NF}'

2. 使用 grep 命令:

echo "$A" | grep -o '[^ ]*$'
  1. -o用于保留字符串中与模式匹配的部分。
  2. [^ ] 表示“不匹配空格”,即“不是空格字符”。
  3. * 的意思是:“匹配前面匹配模式(即 [^ ])的 0 或多个实例”,而 $ 的意思是“匹配行尾”。因此,这将匹配最后一个空格之后到行尾的最后一个单词;在本例中为 abc.123

3. 通过正则 Bash “索引”数组和数组索引

使用默认的 IFS(内部字段分隔符)字符(即空格),将 A 转换为数组:

  1. Option 1 (will "break in mysterious ways", as @tripleee put it in a comment here, if the string stored in the A variable contains certain special shell characters, so Option 2 below is recommended instead!):
    # Capture space-separated words as separate elements in array A_array
    A_array=($A)
    
  2. Option 2 [RECOMMENDED!]. Use the read command, as I explain in my answer here, and as is recommended by the bash shellcheck static code analyzer tool for shell scripts, in ShellCheck rule SC2206, here.
    # Capture space-separated words as separate elements in array A_array, using
    # a "herestring". 
    # See my answer here: https://dev59.com/9mAf5IYBdhLWcg3wdCd1#71575442
    IFS=" " read -r -d '' -a A_array <<< "$A"
    
然后,仅打印数组中的最后一个元素:
# Print only the last element via bash array right-hand-side indexing syntax
echo "${A_array[-1]}"  # last element only

输出:

abc.123

更进一步:

这种模式的另一个有用之处在于它还允许您轻松地执行相反操作!例如,可以像这样获取除最后一个单词以外的所有单词

array_len="${#A_array[@]}"
array_len_minus_one=$((array_len - 1))
echo "${A_array[@]:0:$array_len_minus_one}"

输出:

Some variable has value

以上的${array[@]:start:length}数组切片语法,详见我的答案:Unix & Linux: Bash: slice of positional parameters。关于bash的“算术扩展”语法的更多信息,请参见以下链接:

  1. https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Arithmetic-Expansion
  2. https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Arithmetic

如果值包含shell元字符,数组变量将以神秘的方式中断。您也无法对其进行引用,因为这会防止shell将其分成多个数组元素。(我在几个答案中添加了缺少的引号,包括在此答案中早期添加的引号。) - tripleee
1
@tripleee,感谢您修复引号。我采纳了您的建议,并通过将A_array=($A)更改为IFS=" " read -r -d '' -a A_array <<< "$A"来修复了我的答案,这是一种推荐的“选项2”,用于将多个单词的字符串拆分成数组。 - Gabriel Staples

0

你可以使用Bash正则表达式:

A="Some variable has value abc.123"
[[ $A =~ [[:blank:]]([^[:blank:]]+)$ ]] && echo "${BASH_REMATCH[1]}" || echo "no match"

输出:

abc.123

这适用于当前语言环境中的任何[:blank:]分隔符(通常为[ \t])。 如果您想更具体化:

A="Some variable has value abc.123"
pat='[ ]([^ ]+)$'
[[ $A =~ $pat ]] && echo "${BASH_REMATCH[1]}" || echo "no match"

0
echo "Some variable has value abc.123"| perl -nE'say $1 if /(\S+)$/'

0

我一直在关注类似的问题,寻找在破折号处拆分结果字符串的最佳策略。我的主要字符串是由空格或换行符分隔的窗口ID。我喜欢其中一些更聪明的答案,但最终这个平庸的解决方案似乎更高效,这让我感到惊讶,因为我在使用Python循环的经验中没有这样的表现:

A="Some variable has value abc.123"

for e in $A; do :; done; echo $e

基本上循环遍历$A,什么也不做(:),然后输出最后一个元素。如果制表符或换行符用\t和\n表示,你需要先将它们转义为:
for e in $(echo -e $A); do :; done; echo $e

如果分隔符是不同的字符,比如说冒号,那么翻译过程可能会消除简单循环提供的任何优势,但是对于空格分隔来说,它运行良好。如果我在这里漏掉了什么,请告诉我,我会很乐意听取意见。

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