努力使用带变量的 sed 命令

努力使用带变量的 sed 命令

所以我在 bash 脚本中定义了以下变量:

new_commit="back:h3912kk"
old_commit="back:1.0.1"

file = docker-compose.yml    

然后我有这个 yml $file

version: '3'


services:

  mongo:
    container_name: mongo
    image: mongo:3.4
    volumes:
      - /home/mongo_files:/data/db
    networks:
      my_network:


  back:
    container_name: back
    image: index.docker.io/gg/back:1.0.1
    networks:
      my_network

networks:
    my_network:

我想更换线路

image: index.docker.io/gg/back:1.0.1 

为了

image: index.docker.io/gg/back:h3912kk

sed我在 bash 脚本中使用以下命令:

echo sed -i "s/$old_commit/$new_commit/" $file

但是出现以下错误:

sed: -e expression #1, char 12: unterminated `s' command

当然 docker-compose 文件不会更新。

我究竟做错了什么?

如果我执行 echo 命令,则会显示:

sed -i s/back:1.0.1
/back:h3912kk/ docker-compose.yml

为什么 echo 不将命令显示在同一行上?

编辑:

正如@Kusalananda 所说,old_commit 变量不是静态文件,这就是我猜为什么我无法重现您提供的相关答案的主要原因$old_commit(我虽然不会假设有问题)

为了获取old_commit参数,我使用以下命令在 yml 文件中查找满足我感兴趣的条件的行,这是我的完整代码:

output='back:h3912kk'

# here I split the name of the repo and the commit by ':'
readarray -d ':' -t array_new_commit <<< "$output"

repo="${array_new_commit[0]}"

file=docker-compose.yml

# here I loop over the lines of the yml file:
while read -r line; do
  # if a line contains the repo name str and image str I generate a line with the new commit
  if [[ $line == *$repo* && $line == *"image"* ]]; then

    # old_line=$line

    readarray -d '/' -t array_old_commit <<< "$line"
    old_commit=${array_old_commit[-1]}


    # new_line="${array_old_commit[0]}:${array_old_commit[1]}:$new_commit"
  fi
done<"$file"

不知何故(不知道为什么,我是 bash 的新手)以这种方式生成这个变量实际上不允许我像静态变量一样使用它。 (我真的很想明白这一点)

您能否给我一个解决方法,将变量替换old_commitnew_commityml 文件中的变量?

再次感谢 Kusalananda 指出这个问题

PD 我正在使用 ubuntu 0.18.4 发行版

编辑: 使用第二段代码:

yq -Y --arg new "$new_commit" --arg reposit "$repo" \
'".services." + $reposit + ".image |=index.docker.io/gg/" + $new' docker-compose.yml 

我的输出是:

.services.back.image |=index.docker.io/gg/back:h3912kk 
...

答案1

您不应该使用sedYAML 文件或以任何结构化文档格式(YAML、JSON、XML 等)编写的文件,而是使用为该文档格式编写的解析器。

对于 YAML,有两个为 shell 编写的好工具,都被容易混淆地称为yq.来自yqhttps://kislyuk.github.io/yq/是一个易于使用的jq包装器(并且jq是一个 JSON 解析器)。

对于这个答案的一部分,我假设您已经定义了两个 shell 变量old_commit,并且new_commit与问题中的代码中显示的完全一样。您稍后显示的输出告诉我您实际上并未将它们分配为静态字符串,而是通过其他一些您没有提及的过程(其中$old_commit似乎获得了尾随换行符),所以我不能说太多并会忽略它。

要使用此实现更改、 under 下image键的值,您可以使用backservicesyq

yq -Y '.services.back.image |= "index.docker.io/gg/back:h3912kk"' file.yml

或者,使用您的$new_commit值:

new_commit='back:h3912kk'

yq -Y --arg new "$new_commit" \
    '.services.back.image |= 
        "index.docker.io/gg/" + $new' file.yml

请注意,我们进口--arg使用创建yq变量将 shell 变量的值添加到表达式中$new,而不是直接将其注入到表达式字符串中。这样我们就可以确保该值被正确编码yq,并且还可以确保我们的代码中没有任何代码注入漏洞。

或者,不必提及字符串index.docker.io/gg/

new_commit='back:h3912kk'

yq -Y --arg new "$new_commit" \
    '.services.back.image |=
        sub("[^/]*$"; "") + $new' file.yml

或者,为存储库使用单独的变量:

new_commit='back:h3912kk'

yq -Y --arg new "$new_commit" --arg repo 'back' \
    '.services[$repo].image |=
        sub("[^/]*$"; "") + $new' file.yml

我们还可以使用$old_commit字符串来查找要替换的正确图像,而不是使用静态路径:

old_commit='back:1.0.1'
new_commit='back:h3912kk'

yq -Y --arg old "$old_commit" --arg new "$new_commit" \
    '(.services[].image | select(endswith($old))) |=
        (rtrimstr($old) + $new)' file.yml

这里我们选择全部 .services.*.image以 中的任何字符串结尾的路径$old_commit,然后我们将其从末尾修剪掉并将其替换为$new_commit

考虑到问题中的示例 YAML 文档,上面的每个命令都会生成

version: '3'
services:
  mongo:
    container_name: mongo
    image: mongo:3.4
    volumes:
      - /home/mongo_files:/data/db
    networks:
      my_network: null
  back:
    container_name: back
    image: index.docker.io/gg/back:h3912kk
    networks: my_network
networks:
  my_network: null

您可以轻松地将输出重定向到新文件(或使用 就地编辑 YAML 文档--in-place)。

yq你从 Ubuntu 等获得的工具是snap不同的,我倾向于使用它来将 YAML 转换为 JSON(然后再转换回来):

yq -j e file.yml |
jq '.services.back.image |= "index.docker.io/gg/back:h3912kk"' |
yq -P e

给定示例 YAML 文件,这会产生与上面相同的输出。上面的表达式jq可以替换为该答案顶部的任何表达式,具有相同的语义。

答案2

我不会评判你在 yaml 文件上使用 sed。每个人都用他想要的方式来完成工作。

我前段时间遇到了同样的问题,似乎如果你在 sed 正则表达式中“支撑”你的变量,整个事情就会开始起作用:

$ cat $file
version: '3'


services:

  mongo:
    container_name: mongo
    image: mongo:3.4
    volumes:
      - /home/mongo_files:/data/db
    networks:
      my_network:


  back:
    container_name: back
    image: index.docker.io/gg/back:1.0.1
    networks:
      my_network

networks:
    my_network:

然后 :

$ sed s/${old_commit}/${new_commit}/ $file

给出这个结果:

version: '3'


services:

  mongo:
    container_name: mongo
    image: mongo:3.4
    volumes:
      - /home/mongo_files:/data/db
    networks:
      my_network:


  back:
    container_name: back
    image: index.docker.io/gg/back:h3912kk
    networks:
      my_network

networks:
    my_network:

希望这可以帮助..

在 MacOS 10.13(bash 5.1.4.1、gnu sed 4.8)和 Ubuntu 16.04(bash 4.3.48、gnu sed 4.2.2)上测试

答案3

yq -i -Y --arg new "$VERSION" '.tool.image.tag |= $new' values.yaml

相关内容