AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 722939
Accepted
Richie Thomas
Richie Thomas
Asked: 2022-10-30 09:21:50 +0800 CST2022-10-30 09:21:50 +0800 CST 2022-10-30 09:21:50 +0800 CST

bash参数扩展-如何用“字符串”替换“模式”的*所有*实例?

  • 772

我已经阅读了GNU 的“Shell Parameter Expansion”文档,给出了以下语法:

${parameter//pattern/string}

该模式被扩展以产生一个模式,就像在文件名扩展中一样。参数被扩展,模式 与其值的最长匹配被替换为字符串......如果有两个斜杠分隔参数和模式......,所有匹配的模式都被替换为字符串。

鉴于上面的短语“模式的所有匹配项都替换为字符串”,我希望在一个操作中一次将模式的多个实例全部替换为字符串 ,类似于标志如何与全局搜索和替换一起使用正则表达式中的命令。/g

我有一段开源代码(一个名为 的函数remove_from_path),其实现如下:

remove_from_path() {
  local path_to_remove="$1"
  local path_before
  local result=":${PATH//\~/$HOME}:"
  local counter=0
  while [ "$path_before" != "$result" ]; do
    counter+=1
    echo "counter: $counter"
    path_before="$result"
    result="${result//:$path_to_remove:/:}"
  done
  result="${result%:}"
  echo "${result#:}"
}

原始代码不包含该变量——我添加了该变量以检查循环将执行counter多少次迭代。while

正如我们所看到的,该行result="${result//:$path_to_remove:/:}"使用了 GNU 文档中提到的相同的双正斜杠语法。鉴于此,我希望while循环只会执行一次,因为path_to_remove应该result一次性删除所有实例。

然而,情况似乎并非如此。在bashshell ( version 3.2.57) 中,我将 my 更新$PATH为以下内容:

bash-3.2$ PATH="/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz"

然后我将上述函数复制/粘贴到我的 shell 中,然后运行它。我看到以下内容:

bash-3.2$ remove_from_path "/foo/bar/baz"
counter: 01
counter: 011
counter: 0111
buzz

请忽略计数器的递增没有按我预期的方式工作的事实,因为我们仍然可以看到while循环的 3 次执行。如果${parameter//pattern/string}双正斜杠语法确实用字符串替换了所有匹配的模式,为什么没有在循环的一次迭代中完成呢?为什么我们需要 3 次迭代?while

bash
  • 3 3 个回答
  • 80 Views

3 个回答

  • Voted
  1. Edgar Magallon
    2022-10-30T10:35:35+08:002022-10-30T10:35:35+08:00

    我想我已经找到了问题,但如果我错了,请让我现在。

    在$result变量中你有这个字符串:

    :/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz:
    

    当您申请时,您result="${result//:$path_to_remove:/:}"将所有出现的 替换:/foo/bar/baz:为:。但是鉴于该模式,第二条路径并不真正匹配,因为:. 例如:

    :/foo/bar/baz: /foo/bar/baz :/foo/bar/baz:嗡嗡声:
    

    粗体路径是您的模式的出现。

    您可以尝试使用以下方法进行测试:

    result=':/foo/bar/baz1:/foo/bar/baz2:/foo/bar/baz3:buzz:'
    echo "${result//:'/foo/bar/baz'?:/:}"
    #Output:
    :/foo/bar/baz2:buzz:
    

    正如您在上面看到的,第二条路径 ( /for/bar/baz2) 不受您使用的模式的影响。

    所以你可以用参数扩展做的是这样的:

    echo "${r//'/foo/bar/baz':/}" # The firsy ':' in the pattern was removed
    #and instead of replace the pattern with ':' I'm replacing with nothing.
    

    所以你的remove_from_path函数应该是这样的:

    remove_from_path() {
      local path_to_remove="$1"
      local path_before
      local result=":${PATH//\~/$HOME}:"
      local counter=0
      while [ "$path_before" != "$result" ]; do
        counter+=1
        echo "counter: $counter"
        path_before="$result"
        result="${result//$path_to_remove:/}"
      done
      result="${result%:}"
      echo "${result#:}"
    }
    

    但是,根据您在函数中的逻辑,循环 while 将执行两次。这是因为变量是在使用参数扩展path_before为变量设置另一个值之前设置的。result

    • 1
  2. Best Answer
    Stéphane Chazelas
    2022-10-30T23:32:01+08:002022-10-30T23:32:01+08:00

    ksh93中的${var//pattern/replacement}运算符(zsh、bash 和 mksh 也支持)仅替换模式的非重叠出现。

    ${var//xxx/y}变成,如果它变成xxxxxx替换其中的 4 个重叠出现,那将是相当混乱的。yyyyyyxxx

    这里$PATH表示一个目录列表(~在这方面是~当前工作目录的子目录,改成$HOME是错误的)

    许多 shell(csh、tcsh、zsh、fish、yash)将其映射到它们的数组变量之一。

    例如,在 zsh 中,从$PATH(映射到$path数组,如 in csh)中删除所有出现的目录只是一个问题:

    path=( ${path:#$dir} )
    

    (或path=( "${path[@]:#$dir}" )保留空元素,但您不希望 中的空元素$PATH)。

    bash不这样做,但您可以$PATH使用 split+glob 运算符在此处转换为数组:

    set -o noglob
    IFS=:
    path=( $PATH'' )
    

    在 bash 中,就像在 ksh93 或 zsh 中一样,${var//pattern/replacement}可以使用语法应用于数组的所有元素"${array[@]//pattern/replacement}",但这无济于事,因为它无法删除元素,只需修改它们即可。

    因此,在 中bash,您只需要循环遍历元素:

    remove_from_PATH() {
      local - IFS=: dir to_remove result
      set -o noglob
      for dir in $PATH''; do
        for to_remove do
          if [[ $dir = "$to_remove" ]]; then
            continue 2
          fi
        done
        result+=( "$dir" )
      done
      PATH="${result[*]}"
    }
    

    (local -,从 Almquist shell 复制以更改set -o函数本地的选项(如 noglob)需要相对较新版本的 bash,不适用于您似乎正在使用的古老 3.2 版本)。


    要通过修改:存储在 -separated 列表中的元素来删除元素$PATH,您需要每个$to_remove:

    • 用空字符串替换$to_remove:在开头找到的 a。
    • 用:$to_remove:_ :_:
    • 以空字符串:$to_remove结尾
    • 如果$PATH仅包含$to_remove,那么您将无法选择,因为空$PATH意味着在当前目录中搜索命令,这是您想要的最后一件事。最好将其作为错误处理,或者可以将其视为在现实生活中通常不会发生的病态案例(如上)。或者您可以/dev/null确保$PATH查找找不到任何内容。

    所以:

    remove_from_PATH() {
      local to_remove dir newpath="$PATH" prev_newpath
      for to_remove do
        while
          prev_newpath=$newpath
          newpath=${newpath#"$to_remove:"}
          newpath=${newpath%":$to_remove"}
          newpath=${newpath//":$to_remove:"/:}
          [[ $newpath != "$prev_newpath" ]]
        do
          continue
        done
      done
      if [[ -n $newpath ]]; then
        PATH=$newpath
      else
        echo >&2 'Refusing to make $PATH empty'
        return 1
      fi
    }
    
    • 1
  3. RudiC
    2022-10-30T10:58:54+08:002022-10-30T10:58:54+08:00

    你的前导冒号太多了。尝试不:

    result="/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz"
    echo ${result//$path_to_remove:/:}
    :::buzz
    

    你会看到它一次性删除了所有出现的事件,不需要循环。请注意,摆弄PATH系统变量可能会使您的会话无法使用!

    • 0

相关问题

  • 通过命令的标准输出以编程方式导出环境变量[重复]

  • 从文本文件传递变量的奇怪问题

  • 虽然行读取保持转义空间?

  • `tee` 和 `bash` 进程替换顺序

  • 运行一个非常慢的脚本直到它成功

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve