【问题标题】:Replace shortest string match in bash替换bash中的最短字符串匹配
【发布时间】:2014-12-22 10:47:05
【问题描述】:

在 bash 中,有几种有用的字符串操作模式,例如从字符串的开头/结尾删除最短/最长的子字符串:

${var#substring}
${var##substring}
${var%substring}
${var%%substring}

然后还有替换模式,它从字符串的任何部分替换子字符串:

${var/substring/replacement}

这样做的问题是它是贪婪的并且总是替换最长的匹配。例如,如果我有一个像 /a/b/foo-bar/x/y/z 这样的目录名称,并且我想将任何以 foo- 开头的子目录名称替换为 baz,那么它不会像我预期的那样工作.我希望结果是/a/b/baz/x/y/z。我尝试了以下命令:

${PWD/\/foo-*\///baz/}

本例中的结果是/a/b/baz/z,因为该模式匹配以/foo- 开头并以/ 结尾的最长子字符串。有什么方法可以在不调用sed 或任何其他外部字符串操作程序的情况下获得正确的结果?

【问题讨论】:

    标签: string bash


    【解决方案1】:

    当然,您总是可以使用扩展的 glob:

    shopt -s extglob
    
    var=/a/b/foo-bar/x/y/z/foo-bar2/1/2/3
    echo "${var//\/foo-*([^\/])\///baz/}"
    

    会愉快地输出

    /a/b/baz/x/y/z/baz/1/2/3
    

    【讨论】:

      【解决方案2】:

      在纯 BASH 中,您可以这样做(使用 BASH 正则表达式功能):

      s='/a/b/foo-bar/x/y/z'
      p="$s"
      [[ "$s" =~ ^(.*/)'foo-'[^/]*(.*)$ ]] && p="${BASH_REMATCH[1]}baz${BASH_REMATCH[2]}"
      
      echo "$p"
      /a/b/baz/x/y/z
      

      【讨论】:

      • 请注意,如果变量不包含字符串 foo,则会失败。要解决这个问题,你必须添加一些测试,最终它变得比 extglobs 更尴尬! :).
      • 好的,我更新了答案以确保仅当原始字符串中存在foo- 时才进行分配。
      • 在正则表达式之前,您可能需要p=$s,这样当s 不匹配时,p 会自动扩展为s
      • 另外,这仅执行一次替换:如果 s='/a/b/foo-bar/x/y/z/foo-bar2/1/2/3' 则仅替换第一个 foo-bar
      • @gniourf_gniourf:是的,没错,要进行多次替换,这里需要一个循环,因此extglob 是更好的选择。
      【解决方案3】:

      这是一种在纯 Bash 中执行此操作的方法,它替换路径中与模式匹配的 all 子目录。它将路径拆分为一个数组,将每个路径组件替换为一个新数组,然后使用printf 替换路径分隔符。

      name='/a/b/foo-bar/x/foo-y/z';
      IFS=$'/';
      aname=($name);
      bname=();
      for i in "${aname[@]}";
      do bname+=("${i/foo-*/baz}");done;
      printf -v newname "%s/" "${bname[@]}";
      
      printf "%s\n" "$newname"
      

      输出

      /a/b/baz/x/baz/z/
      

      (对所有;s 感到抱歉,我刚刚从外壳快速复制和粘贴)

      【讨论】:

      • 要在斜杠上分割,你可以使用这个:IFS=/ read -r -d '' -a aname < <(printf '%s\0' "$name")。这会保留所有字符(空格、换行符等)并且是路径名扩展安全的。然后,在没有循环的情况下执行替换:bname=( "${aname[@]/foo-*/baz}")。实际上,您可以仅使用这两行代码:IFS=/ read -r -d '' -a aname < <(printf '%s\0' "$name"); ( IFS=/; printf '%s\n' "${aname[*]/foo-*/baz}" )。仅当name 有斜杠时才会失败。
      • 谢谢,@gniourf_gniourf。我不知道你能做到"${aname[*]/foo-*/baz}";那很整齐!我必须承认,我什至没有想过使用read...-a 选项一定让我忘记了。 :)
      【解决方案4】:

      您不能在 bash 的内置字符串替换中使用正则表达式。如果你必须坚持内置的字符串操作,你必须先提取子字符串foo-bar,然后在你的${PWD/$match/baz}中使用它。

      但是,如果您可以使用正则表达式,您可以很容易地做到这一点。 linux/unix 下有很多方便的字符串处理程序,可以很容易地做到这一点。例如:

      kent$  sed 's#/foo-[^/]*#/baz#' <<< '/a/b/foo-bar/x/y/z'
      /a/b/baz/x/y/z
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-24
        • 1970-01-01
        • 2012-04-11
        • 2014-08-01
        • 2014-08-15
        • 2022-01-17
        • 1970-01-01
        相关资源
        最近更新 更多