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

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

例如,假设我有以下值:

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

如何得到以下结果?

result="o-wor"
627155 次浏览
$ prefix="hell"
$ suffix="ld"
$ string="hello-world"
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

这记录在手册的外壳参数扩展部分:

${parameter#word}
${parameter##word}

单词被扩展以产生模式并根据下面描述的规则进行匹配(参见模式匹配)。如果模式匹配参数扩展值的开头,则扩展的结果是参数的扩展值,其中删除了最短匹配模式(# case)或最长匹配模式(## case)。[…]

${parameter%word}
${parameter%%word}

单词被扩展以产生模式并根据下面描述的规则进行匹配(参见模式匹配)。如果模式匹配参数扩展值的尾部,则扩展的结果是删除了最短匹配模式(% case)或最长匹配模式(%% case)的参数值。[…]

使用Sed:

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

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

Adrian Frühwirth在下面的评论中提出了一些很好的观点,但是sed在这方面可以非常有用。$precon和$后缀的内容被se解释的事实可以是好的也可以是坏的——只要你注意,你应该没事。美妙的是,你可以这样做:

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

这可能是你想要的,并且比bash变量替换更花哨也更强大。如果你记得权力越大责任越大(如蜘蛛侠所说),你应该没事。

可以在http://evc-cit.info/cit052/sed_tutorial.html上找到对se的快速介绍

关于shell及其字符串使用的注释:

对于给定的特定示例,以下方法也有效:

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

但仅仅是因为:

  1. Echo不关心它的参数列表中有多少字符串,并且
  2. $前缀和$后缀中没有空格

在命令行上引用字符串通常是一种很好的做法,因为即使它包含空格,它也会作为单个参数呈现给命令。我们引用$pre发生和$后缀是出于同样的原因:每个编辑命令将作为一个字符串传递给ed。我们使用双引号是因为它们允许变量插值;如果我们使用单引号,ses命令会得到文字$prefix$suffix,这当然不是我们想要的。

还要注意,我在设置变量prefixsuffix时使用了单引号。我们当然不希望字符串中的任何内容被解释,所以我们单引号它们,所以不进行插值。同样,在这个例子中可能没有必要,但这是一个非常好的习惯。

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

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

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

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

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

更一般的:

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

但是来自Adrian Frühwirth的解决方案太酷了!我不知道!

使用@Adrian Frühwirth回答:

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

像这样使用它

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

小而通用的解决方案:

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

使用=~运算符

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ [[ "$string" =~ ^$prefix(.*)$suffix$ ]] && echo "${BASH_REMATCH[1]}"
o-wor
$ 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

备注:

#$前缀:添加#确保只有在开头找到子串“地狱”时才会删除它。 %$后缀:添加%确保子字符串“ld”仅在end中找到时才被删除。

如果没有这些,子串“地狱”和“ld”将在任何地方被删除,即使它位于中间。

我将使用regex中的捕获组:

$ 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

注:不确定这在2013年是否可能,但今天(2021年10月10日)肯定有可能,所以添加另一个选项…


由于我们正在处理已知的固定长度字符串(prefixsuffix),我们可以使用bash子字符串通过单个操作获得所需的结果。

输入:

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

计划:

  • bash子字符串语法:${string:<start>:<length>}
  • 跳过prefix="hell"意味着我们的<start>将是4
  • <length>将是string${#string})的总长度减去固定长度字符串的长度(4表示hell/2表示ld

这给了我们:

$ echo "${string:4:(${#string}-4-2)}"
o-wor

注:可以删除括号并仍然获得相同的结果


如果prefixsuffix的值未知,或者可能会有所不同,我们仍然可以使用相同的操作,但将42分别替换为${#prefix}${#suffix}

$ echo "${string:${#prefix}:${#string}-${#prefix}-${#suffix}}"
o-wor