【问题标题】:Remove a fixed prefix/suffix from a string in Bash从 Bash 中的字符串中删除固定的前缀/后缀
【发布时间】:2013-05-18 11:37:50
【问题描述】:

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

例如,假设我有以下值:

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

我如何得到以下结果?

result="o-wor"

【问题讨论】:

  • 在链接到所谓的高级 Bash 脚本指南时要非常小心;它包含了好的建议和糟糕的建议。

标签: bash


【解决方案1】:
$ prefix="hell"
$ suffix="ld"
$ string="hello-world"
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

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

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

单词被扩展以产生一个模式并根据下面描述的规则进行匹配(参见Pattern Matching)。如果模式匹配参数扩展值的开头,则扩展结果是具有最短匹配模式(# 情况)或最长匹配模式(## 情况)的参数扩展值被删除. […]

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

单词被扩展以产生一个模式并根据下面描述的规则进行匹配(参见Pattern Matching)。如果模式匹配参数扩展值的尾随部分,则扩展结果是具有最短匹配模式(% 情况)或最长匹配模式(%% 情况)的参数值被删除. […]

【讨论】:

  • 还有 ## 和 %% ,如果 $prefix 或 $suffix 包含通配符,则尽可能删除。
  • 有没有办法将两者结合在一行中?我试过${${string#prefix}%suffix},但它不起作用。
  • @static_rtti 不,很遗憾,您不能像这样嵌套参数替换。我知道,这是一种耻辱。
  • @AdrianFrühwirth : 整个语言都是一种耻辱,但它非常有用:)
  • Nvm,Google 中的“bash 替换”找到了我想要的。
【解决方案2】:

使用 sed:

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

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

Adrian Frühwirth 在下面的 cmets 中提出了一些优点,但 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$//

...但只是因为:

  1. echo 不在乎它的参数列表中有多少个字符串,并且
  2. $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 的分隔符的限制意味着您无法处理包含该字符的文件。
【解决方案3】:

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

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 太酷了!我不知道!

【讨论】:

    【解决方案4】:
    $ 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:在您的代码示例中,您在字符串后面还有一个正斜杠/,这是为了什么?
    • / 分隔当前字符串和子字符串。这里的子字符串是已发布问题的后缀。
    【解决方案5】:

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

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

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

    【讨论】:

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

    使用=~ operator

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

    【讨论】:

      【解决方案7】:

      小型通用解决方案:

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

      【讨论】:

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

      使用@Adrian Frühwirth 回答:

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

      这样使用

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

      【讨论】:

        【解决方案9】:

        注意:不确定这在 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
        

        【讨论】:

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

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

        $ 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
        

        【讨论】:

          猜你喜欢
          • 2015-03-27
          • 2011-05-08
          • 2023-04-05
          • 2021-07-18
          • 2016-12-30
          • 1970-01-01
          • 1970-01-01
          • 2014-05-06
          相关资源
          最近更新 更多