使用bash/cut/split提取字符串的一部分

183

我有一个类似这样的字符串:

/var/cpanel/users/joebloggs:DNS9=domain.example

我需要从此字符串中提取用户名(joebloggs)并将其存储在一个变量中。

字符串的格式始终相同,只有joebloggsdomain.example可能会不同,因此我认为可以使用cut两次拆分字符串?

第一次拆分将通过:进行拆分,并将第一部分存储在一个变量中,以便传递给第二个拆分函数。

第二次拆分将通过/进行拆分,并将最后一个单词(joebloggs)存储到一个变量中。

我知道如何在PHP中使用数组和拆分来完成这个操作,但在bash中有点迷糊。

7个回答

476

使用参数扩展在bash中从此字符串中提取joebloggs,而不需要任何额外的进程...

MYVAR="/var/cpanel/users/joebloggs:DNS9=domain.example"

NAME=${MYVAR%:*}  # retain the part before the colon
NAME=${NAME##*/}  # retain the part after the last slash
echo $NAME

不依赖于 joebloggs 在路径中的特定深度。


摘要

一些参数扩展模式的概述,供参考...

${MYVAR#pattern}     # delete shortest match of pattern from the beginning
${MYVAR##pattern}    # delete longest match of pattern from the beginning
${MYVAR%pattern}     # delete shortest match of pattern from the end
${MYVAR%%pattern}    # delete longest match of pattern from the end

所以#表示从开头匹配(比如注释行),而%表示从结尾匹配。一个实例表示最短匹配,两个实例表示最长匹配。

您可以使用数字基于位置获取子字符串:

${MYVAR:3}   # Remove the first three chars (leaving 4..end)
${MYVAR::3}  # Return the first three characters
${MYVAR:3:5} # The next five characters after removing the first 3 (chars 4-9)

您也可以使用以下方法替换特定字符串或模式:
${MYVAR/search/replace}

pattern的格式与文件名匹配相同,因此通常使用*(任意字符),后面跟着一个特定的符号,如/.

示例:

给定变量如下:

MYVAR="users/joebloggs/domain.example"

去除路径,只保留文件名(删除斜杠前的所有字符):

echo ${MYVAR##*/}
domain.example

删除文件名,只保留路径(删除最后一个 / 后面的字符):

echo ${MYVAR%/*}
users/joebloggs

获取文件扩展名(删除最后一个句点之前的所有内容):

echo ${MYVAR##*.}
example

注意:要执行两个操作,不能将它们组合在一起,而是必须分配给一个中间变量。因此,要获取不带路径或扩展名的文件名:

NAME=${MYVAR##*/}      # remove part before last slash
echo ${NAME%.*}        # from the new var remove the part after the last period
domain

2
太棒了!而且它是在执行 shell 内完成的,因此比使用其他命令的方式更快。 - stolsvik
3
你需要将通配符移到冒号之前,并使用#代替%。如果你只想获取最后一个冒号后面的部分,可以使用${MYVAR##*:};如果想要获取第一个冒号后面的部分,可以使用${MYVAR#*:}。请注意不要改变原意。 - beroe
7
朋友,你不知道我回来看过这个回答多少次了。谢谢! - Joel B
1
很棒的答案!问题:如果我的模式是一个变量,我应该像这样输入${RET##*$CHOP}还是像这样${RET##*CHOP}(或者其他方式)? 编辑:看起来应该是前者${RET##*$CHOP} - Ctrl S
2
请注意,在zsh中${MYVAR::3}表达式无法工作。会返回“zsh:需要闭合括号”的错误信息。相反,:3:5 或仅使用 :3 可以正常工作。 - dimisjim
显示剩余6条评论

65

像这样定义一个函数:

getUserName() {
    echo $1 | cut -d : -f 1 | xargs basename
}

将字符串作为参数传递:

userName=$(getUserName "/var/cpanel/users/joebloggs:DNS9=domain.example")
echo $userName

2
这个答案帮助我实现了我来这里的目的。没有被接受的答案,但这个答案因其简洁而得到了我的支持。 - harperville
1
我在上面的命令中唯一需要更正的是删除“:”,像这样echo $1 | cut -d -f 1 | xargs。对于简单而整洁的答案加1。 - Bhushan
请描述您回答中的 cut -d : -f 1 | xargs basename 部分,以便对有类似用例的其他人有所帮助。 - Peyman Mohamadpour

28

那 sed 呢?用一个命令就可以搞定:

sed 's#.*/\([^:]*\).*#\1#' <<<$string
  • 由于字符串中包含/,所以#被用作正则表达式分隔符。
  • .*/获取到最后一个反斜杠之前的所有字符。
  • \( .. \)标记了一个捕获组。这里是\([^:]*\)
    • [^:]表示除了冒号以外的任意字符,*代表零个或多个。
  • .*表示匹配剩余行的所有内容。
  • \1表示用第一个(也是唯一一个)捕获组找到的内容进行替换。这是名称。

以下是将字符串与正则表达式相匹配的详细描述:

        /var/cpanel/users/           joebloggs  :DNS9=domain.example joebloggs
sed 's#.*/                          \([^:]*\)   .*              #\1       #'

超级好的分析! - kyb

18

使用单个Awk:

... | awk -F '[/:]' '{print $5}'
那就是说,使用 /: 作为字段分隔符,用户名始终在第5个字段中。 要将其存储在变量中:
username=$(... | awk -F '[/:]' '{print $5}')

使用更灵活的 sed 实现,不需要将用户名作为第五个字段:

... | sed -e s/:.*// -e s?.*/??

也就是说,删除从 : 开始的所有内容,然后删除最后一个 / 之前的所有内容。使用 sed 可能比使用 awk 快得多,因此这种替代方法肯定更好。


12

使用单个 sed

echo "/var/cpanel/users/joebloggs:DNS9=domain.example" | sed 's/.*\/\(.*\):.*/\1/'

3

我喜欢使用带有 -F 参数设置的不同定界符将 awk 链接在一起。首先,将字符串拆分为 /users/,然后再拆分为 :

txt="/var/cpanel/users/joebloggs:DNS9=domain.com"
echo $txt | awk -F"/users/" '{print$2}' | awk -F: '{print $1}'

$2 提供定界符后的文本,$1 则提供定界符前的文本。


2

我知道我来晚了,已经有很好的答案了,但是这是我做类似事情的方法。

DIR="/var/cpanel/users/joebloggs:DNS9=domain.example"
echo ${DIR} | rev | cut -d'/' -f 1 | rev | cut -d':' -f1

2
迟到总比不到好,我的朋友。 - Craig Edmonds

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