ChatGPT解决这个技术问题 Extra ChatGPT

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

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

例如,假设我有以下值:

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

我如何得到以下结果?

result="o-wor"
链接到所谓的高级 Bash 脚本指南时要非常小心;它包含了好的建议和糟糕的建议。

c
cosbor11
$ prefix="hell"
$ suffix="ld"
$ string="hello-world"
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

这记录在手册的 Shell Parameter Expansion 部分:

${parameter#word} ${parameter##word} 单词被扩展以产生一个模式并根据下面描述的规则进行匹配(参见模式匹配)。如果模式匹配参数扩展值的开头,则扩展结果是删除了最短匹配模式(#case)或最长匹配模式(##case)的parameter扩展值。 […] ${parameter%word} ${parameter%%word} 这个词被扩展以产生一个模式并根据下面描述的规则进行匹配(参见模式匹配)。如果模式匹配参数扩展值的尾随部分,则扩展的结果是删除了最短匹配模式(% 情况)或最长匹配模式(%% 情况)的参数值。 […]


有没有办法将两者结合在一条线上?我尝试了 ${${string#prefix}%suffix},但它不起作用。
@static_rtti 不,不幸的是,您不能像这样嵌套参数替换。我知道,这是一种耻辱。
@AdrianFrühwirth:整个语言很可惜,但它非常有用:)
这在高级 Bash 脚本指南的参数替换部分中有记录:tldp.org/LDP/abs/html/parameter-substitution.html
@static_rtti ,有一个解决方法: echo basename ${string/hell} ld (灰色部分在反引号之间)
M
Mike S

使用 sed:

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

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

Adrian Frühwirth 在下面的评论中提出了一些很好的观点,但为此目的,sed 可能非常有用。 $prefix 和 $suffix 的内容被 sed 解释的事实可能好也可能不好 - 只要你注意,你应该没问题。美妙之处在于,您可以执行以下操作:

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

这可能是您想要的,并且比 bash 变量替换更漂亮、更强大。如果你记得强大的力量伴随着巨大的责任(正如蜘蛛侠所说),你应该没问题。

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

关于 shell 及其对字符串的使用的说明:

对于给出的特定示例,以下内容也可以使用:

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

...但仅仅是因为:

echo 不关心它的参数列表中有多少个字符串,并且 $prefix 和 $suffix 中没有空格

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

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


不幸的是,这是一个不好的建议,原因如下: 1) 未引用,$string 会受到分词和通配符的影响。 2) $prefix$suffix 可以包含 sed 将解释的表达式,例如正则表达式或用作分隔符的字符会破坏整个命令。 3) 不需要调用 sed 两次(你可以用 -e 's///' -e '///' 代替),也可以避免管道。例如,考虑 string='./ *' 和/或 prefix='./' 并看到它由于 1)2) 而严重损坏。
有趣的提示:sed 几乎可以将任何东西作为分隔符。在我的例子中,因为我从路径中解析前缀目录,所以我不能使用 /,所以我使用了 sed "s#^$prefix##。 (脆弱性:文件名不能包含 #。因为我控制了文件,所以我们在那里很安全。)
@Olie 文件名可以包含除斜杠和空字符之外的任何字符,因此除非您可以控制,否则您不能假设文件名不包含某些字符。
是的,不知道我在想什么。 iOS可能吗?不知道。文件名当然可以包含“#”。不知道我为什么这么说。 :)
@Olie:据我了解您的原始评论,您是说您选择使用 # 作为 sed 的分隔符的限制意味着您无法处理包含该字符的文件。
V
Vijayendar Gururaja
$ 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:在您的代码示例中,您在字符串后面还有一个正斜杠/,这是为了什么?
/ 分隔当前字符串和子字符串。这里的子字符串是已发布问题的后缀。
t
tommy.carstensen

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

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

或更笼统地说:

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

但是 solution from Adrian Frühwirth 太酷了!我不知道!


d
dipdapdop

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

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

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


grep -P 是非标准扩展。如果您的平台支持它,则为您提供更多功能,但如果您的代码需要合理可移植,这是一个可疑的建议。
@tripleee 确实。但我认为安装了 GNU Bash 的系统也有一个支持 PCRE 的 grep。
不,例如 MacOS 有开箱即用的 Bash,但没有 GNU grep。早期版本实际上有来自 BSD grep-P 选项,但他们删除了它。
M
Martin - マーチン

使用 =~ operator

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ [[ "$string" =~ ^$prefix(.*)$suffix$ ]] && echo "${BASH_REMATCH[1]}"
o-wor

T
Tosi Do

小而通用的解决方案:

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

如果您使用的是 Bash,那么您可能根本不应该使用 expr。在最初的 Bourne shell 时代,它是一种某种方便的厨房水槽实用工具,但现在已经过了最佳使用日期。
呃,为什么? expr 已旧,但从未更改,并且可能始终可用。只要您调用外部二进制文件(而不是使用 BASH 表达式),grep、sed 或 expr 就几乎是等价的(perl / awk 会更昂贵)。
m
math2001

使用@Adrian Frühwirth 回答:

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

像这样使用它

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

m
markp-fuso

注意:不确定这在 2013 年是否可行,但今天(2021 年 10 月 10 日)肯定可行,因此添加另一个选项...

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

输入:

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

计划:

bash 子字符串语法:${string::}

跳过 prefix="hell" 意味着我们的 将是 4

将是字符串的总长度 (${#string}) 减去我们的固定长度字符串的长度(地狱为 4 / ld 为 2)

这给了我们:

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

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

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

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

不错的选择!值得一提的是:此解决方案与其他解决方案之间的关键区别在于,如果源字符串不以前缀开头或不以后缀结尾,那么其他解决方案将不会剪切任何内容,而此解决方案将剪切掉后缀的长度。这不一定是问题,只是需要注意的限制。如果您不确定字符串是否以前缀/后缀开头或结尾,只需将此语句包装在适当的 if 语句中以在修剪前检查。
B
Bayou

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

$ 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