Bash

使用帶有變數的 sed 命令苦苦掙扎

  • April 27, 2021

所以我在 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_commit變數替換new_commit為 yml 文件?

再次感謝 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 
...

您不應使用sedYAML 文件或以任何結構化文件格式(YAML、JSON、XML 等)編寫的文件。相反,應使用為該文件格式編寫的解析器。

對於 YAML,有兩個為 shell 編寫的好工具,都被混淆地稱為yq. 來自https://kislyuk.github.io/yq/是一個易於使用的yq包裝器(並且是一個 JSON 解析器)。jq``jq

對於這個答案的一部分,我將假設您已經定義了兩個 shell 變數old_commit,並且new_commit與您在問題中的程式碼中顯示的完全一樣。您稍後顯示的輸出告訴我,您實際上並沒有將它們分配為靜態字元串,而是通過其他一些您沒有提及的過程(其中$old_commit似乎有一個尾隨換行符),所以我不能說太多關於它,並會忽略它。

要使用此實現更改 under 、 underimage鍵的值,您可以使用back``services``yq

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

請注意,我們使用創建變數將 shell 變數的值導入到表達式中*,*而不是將其直接注入到表達式字元串中。這樣我們可以確保值被正確編碼,並且還可以確保我們的程式碼中沒有任何程式碼注入漏洞。--arg``yq``$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表達式可以與此答案頂部的任何表達式交換,具有相同的語義。

引用自:https://unix.stackexchange.com/questions/646851