从Bash中的字符串中删除固定的前缀/后缀


484

在我的bash脚本中,我有一个字符串及其前缀/后缀。我需要从原始字符串中删除前缀/后缀。

例如,假设我具有以下值:

string="hello-world"
prefix="hell"
suffix="ld"

如何获得以下结果?

result="o-wor"


14
链接到所谓的“高级Bash脚本指南”时要特别小心;它包含好的建议和糟糕的混合。
人间

Answers:


717
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

40
还有##和%%,如果$ prefix或$ suffix包含通配符,它​​们会尽可能地删除。
pts

27
有没有办法将两者合而为一?我试过了,${${string#prefix}%suffix}但是没有用。
static_rtti 2014年

28
@static_rtti不,不幸的是,您不能像这样嵌套参数替换。我知道,真可惜。
AdrianFrühwirth2014年

87
@AdrianFrühwirth:整个语言都是一种耻辱,但是它是如此有用:)
static_rtti 2014年

8
Nvm,Google中的“ bash替换”找到了我想要的。
泰勒2014年

89

使用sed:

$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

在sed命令中,^字符匹配以开头的文本$prefix,结尾$匹配以结尾的文本$suffix

AdrianFrühwirth在下面的评论中指出了一些要点,但sed为此可能非常有用。$ prefix和$ suffix的内容由sed解释的事实可以是好是坏-只要注意,就可以了。美丽之处在于,您可以执行以下操作:

$ prefix='^.*ll'
$ suffix='ld$'
$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

这可能是您想要的,并且比bash变量替换更出色,功能更强大。如果您还记得,强大的力量伴随着重大的责任(如蜘蛛侠所说),那么您应该没事。

有关sed的快速介绍,请访问http://evc-cit.info/cit052/sed_tutorial.html。

关于外壳及其字符串使用的说明:

对于给定的特定示例,以下内容同样适用:

$ echo $string | sed -e s/^$prefix// -e s/$suffix$//

...但仅因为:

  1. echo不在乎参数列表中有多少个字符串,并且
  2. $ prefix和$ suffix中没有空格

通常,在命令行上用引号引起来是一个好习惯,因为即使它包含空格,它也会作为单个参数显示给命令。出于相同的原因,我们引用$ prefix和$ suffix:sed的每个编辑命令将作为一个字符串传递。我们使用双引号,因为它们允许变量插值。如果我们使用单引号,则sed命令将得到一个文字$prefix$suffix这当然不是我们想要的。

还要注意,在设置变量prefix和时我使用单引号suffix。我们当然不希望字符串中的任何内容被解释,因此我们单引号将它们括起来,因此不会进行插值。同样,在此示例中可能没有必要,但这是一个很好的习惯。


8
不幸的是,由于以下几个原因,这是一个糟糕的建议:1)不加引号,$string容易被单词拆分和修饰。2),$prefix并且$suffix可以包含sed将解释的表达式,例如正则表达式或用作定界符的字符,将破坏整个命令。3)sed不需要调用两次(您可以调用-e 's///' -e '///'),也可以避免使用管道。例如,考虑string='./ *'和/或prefix='./'看到它由于1)和严重损坏2)
2014年

有趣的提示:sed几乎可以将任何内容用作分隔符。就我而言,由于我是在路径之外解析前缀目录/,因此我无法使用,所以我使用sed "s#^$prefix##来代替。(脆弱性:文件名不能包含#。由于我控制文件,因此我们很安全。)
Olie

@Olie文件名可以包含除斜杠和空字符之外的任何字符,因此除非您有控制权,否则不能假设文件名不包含某些字符。
AdrianFrühwirth,2015年

是的,不知道我在想什么。iOS也许?不知道。文件名当然可以包含“#”。不知道为什么我这么说。:)
Olie

@Olie:据我了解您的原始评论,您说的是,选择#用作sed分隔符的限制意味着您无法处理包含该字符的文件。
P Daddy

17

您知道前缀和后缀的长度吗?在您的情况下:

result=$(echo $string | cut -c5- | rev | cut -c3- | rev)

或更笼统:

result=$(echo $string | cut -c$((${#prefix}+1))- | rev | cut -c$((${#suffix}+1))- | rev)

但是AdrianFrühwirth解决方案很酷!我不知道!


14

我使用grep从路径中删除前缀(不能很好地处理sed):

echo "$input" | grep -oP "^$prefix\K.*"

\K 从匹配项中删除所有之前的字符。


grep -P是非标准扩展。如果您的平台支持它,则将为您提供更多功能,但是如果您的代码需要合理地移植,那么这是可疑的建议。
三人房

@tripleee确实。但是我认为安装了GNU Bash的系统也有一个支持PCRE的grep。
弗拉基米尔·佩特拉科维奇

1
不,例如,MacOS的Bash开箱即用,而GNU则没有grep。早期版本实际上具有-PBSD 的选项,grep但他们将其删除。
三人

9
$ string="hello-world"
$ prefix="hell"
$ suffix="ld"

$ #remove "hell" from "hello-world" if "hell" is found at the beginning.
$ prefix_removed_string=${string/#$prefix}

$ #remove "ld" from "o-world" if "ld" is found at the end.
$ suffix_removed_String=${prefix_removed_string/%$suffix}
$ echo $suffix_removed_String
o-wor

笔记:

#$ prefix:添加#确保仅在开头找到子字符串“ hell”。%$ suffix:添加%可以确保仅在结尾找到子字符串“ ld”。

没有这些,子字符串“ hell”和“ ld”将在所有位置删除,即使在中间也可以找到。


感谢您的注释!qq:在您的代码示例中/,字符串后也有一个正斜杠,这是什么意思?
DiegoSalazar

1
/分隔当前字符串和子字符串。这里的子字符串是问题的后缀。
维杰瓦特(Vijay Vat)


6

小型通用解决方案:

expr "$string" : "$prefix\(.*\)$suffix"

1
如果您使用的是Bash,则可能根本不使用expr。这是一个排序的原Bourne Shell的日子方便厨房的水槽实用性回来,但现在是过去的方式其日期最前。
三人房

5

使用@AdrianFrühwirth答案:

function strip {
    local STRING=${1#$"$2"}
    echo ${STRING%$"$2"}
}

这样使用

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello

0

我会在正则表达式中使用捕获组:

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ set +H # Disables history substitution, can be omitted in scripts.
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/" <<< $string
o-wor
$ string1=$string$string
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/g" <<< $string1
o-woro-wor

((?:(?!(${suffix})).)*)确保将的内容${suffix}从捕获组中排除。就示例而言,它等于的字符串[^A-Z]*。否则,您将获得:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.