Sed: изменение значений свойств среды в файле .yml
У меня есть .yml файл, который настраивает свойства среды приложения, например:
env1:
:prop1: "value1"
:prop2: "value2"
...
:propn: "valuen"
env2:
:prop1: "value1"
:prop2: "value2"
:prop3: "value3"
...
:propn: "valuen"
...
envn:
:prop1: "value1"
:prop2: "value2"
...
:propn: "valuen"
Я хотел бы создать bash script со следующим интерфейсом:
$ change_env.sh <environment> <property> <new value> <file.yml>
Пример:
$ change_env.sh env2 prop3 "this value was changed" file.yml
Выход будет:
env1:
:prop1: "value1"
:prop2: "value2"
...
:propn: "valuen"
env2:
:prop1: "value1"
:prop2: "value2"
:prop3: "this value was changed"
...
:propn: "valuen"
...
envn:
:prop1: "value1"
:prop2: "value2"
...
:propn: "valuen"
Я нашел этот пост, однако я не мог сделать это для моего дела.
Заменить значение элемента XML? Регулярное выражение Sed?
Я также пробовал это: (он терпит неудачу, потому что изменяет все свойства)
sed 's/\(:pro3:\).*/\1 "new value"/'
Спасибо заранее!
- Лоренко.
Ответы
Ответ 1
(очень хороший первый пост!)
Попробуйте это
cat change_env.sh
#!/bin/bash
# spec : change_env.sh <environment> <property> <new value> <file.yml>
case ${#} in [!4] )
echo "usage: change_env.sh <environment> <property> <new value> <file.yml>" 1>&2
exit 1
;;
esac
env="$1" prop="$2" new="$3" file="$4"
bakFile="${file}".bak
mv "$file" "$bakFile"
sed '/^'"${env}"'/,/^[ ]*$/{ # [ spaceChar tabChar ]
/'"${prop}"'/s/\('"${prop}"'\)\(.*$\)/\1'"${new}"'/
}' "$bakFile" > "$file"
изменить
Обратите внимание, что если вы ожидаете, что ввод содержит пробел в значениях, которые вы хотите изменить script, чтобы указать все переменные ( "$ 1", "$ 2"...). (Я сделал это сейчас, так как это лучшая практика с оболочкой).
/env/,/^[{space,tab}]*$/
- это адрес диапазона для sed. Он читает блок текста, содержащий настройки среды. Я предполагаю, что ваш пример ввода правильный и что каждый env отделен пустой строкой. Хм... это будет включать в себя последний файл.
** редактировать **
Благодаря @posdef для указания некоторых проблем с этим ответом. Код обновляется для решения конкретного случая.
Даже после исправления я заметил, что с учетом ввода типа
change_env.sh env2 prop2 "new value" file.yml
Соответствующий вывод был
:prop2new value
Таким образом, без hardcoding дополнительных :
и пробелов в подстановке это означает, что вам нужно быть очень подробным в том, как вы называете значение <property>
И <new value>
, т.е.
change_env.sh env2 ":prop2: " "\"new value\"" file.yml
# note extra cruft-^^-----^^^--^^---------^^--------------
соответствующий выход
env2:
:prop1: "value1"
:prop2: "new value"
:prop3: "value3"
...
:propn: "valuen"
IHTH
Ответ 2
Я бы использовал awk:
#!/bin/sh
if [ $# -ne 4 ]; then
echo "usage: $0 env prop value file" >&2
exit 1
fi
awk -F : -v env="$1" -v prop="$2" -v val=" \"$3\"" '
BEGIN {OFS = FS}
$1 == env {in_env = 1}
NF == 0 {in_env = 0}
in_env && $2 == prop {$3 = val}
{print}
' "$4"
Ответ 3
Этот ответ основан на Glenn Jackman AWK script, который в моем тестировании терпит неудачу из-за проблем с отступом, присущих типу ввода OP (и вашего по-настоящему).
В частности, условие проверки того, находимся ли мы в желаемой среде, будет происходить на другой итерации, чем проверка того, есть ли у нас желаемое свойство, поскольку они обычно будут на разных линиях. Таким образом, in_env && $2 == prop
никогда не вернет true, считая, что пара property : value
будет считана как $1 : $2
на отдельной строке.
Кроме того, сравнение $2 == prop
будет страдать от ведущего пробела, который нужно обрезать. Я добавил пару приятных однострочных описанных здесь, чтобы сделать script более понятным для человека.
Наконец, исходные script жестко закодированные двойные кавычки вокруг нового значения, что является проблемой, если вы вставляете числовые значения.
Я изменил script следующим образом, который хорошо работает в моем тестовом примере. Я предоставляю здесь, если он полезен другим.
#!/bin/sh
if [ $# -ne 4 ]; then
echo "usage: $0 env prop value file" >&2
exit 1
fi
awk -F : -v env="$1" -v prop="$2" -v val=" $3" '
function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
function trim(s) { return rtrim(ltrim(s)); }
BEGIN {OFS = FS}
$1 == env {in_env = 1}
NF == 0 {in_env = 0}
in_env && trim($1) == prop {$2 = val}
{print}
' "$4"
Ответ 4
Bash script
#!/bin/bash
# tested with bash 4
if [ $# -ne 4 ];then
echo "Usage: .... "
exit
fi
env=$1
prop=$2
text="$3"
file=$4
while read -r line
do
case "$line" in
"$env"* )
toggle=1
;;
esac
if [ "$toggle" = 1 ];then
if [[ $line =~ "$prop" ]] ;then
line="${line%%\"*}\"$text\""
toggle=0
fi
fi
echo "$line"
done < $file > t
mv t $file
Ответ 5
Вам понадобится хотя бы одно утверждение. Есть много способов сделать это, но вы должны использовать что-то, что умеет разбирать это.
s/(\s*$env:\s*(?:(?!\s*[^\W_]+:)[^\n]*\s*)*\s*:$prop:[^\S\n]*")[^\n]*(")/$1$replacement$2/g
Легко протестирован в perl:
use strict;
use warnings;
my $str = '
env1:
:prop1: "value1"
:prop2: "value2"
...
:propn: "valuen"
env2:
:prop1: "value1"
...
:prop2: "value2"
:prop3: "value3"
:propn: "valuen"
...
envn:
:prop1: "value1"
...
:prop3: "value3"
:propn: "valuen"
';
my ($env, $prop, $replacement) = ('(?:env2|envn)', 'prop1', 'this changed');
if ( $str =~ s/
(
\s*$env:\s*
(?: (?!\s*[^\W_]+:) [^\n]*\s* )*
\s*:$prop:[^\S\n]*
"
) [^\n]*
( " )
/$1$replacement$2/xg )
{
print "Found it!\n";
print $str;
}
Вывод:
Found it!
env1:
:prop1: "value1"
:prop2: "value2"
...
:propn: "valuen"
env2:
:prop1: "this changed"
...
:prop2: "value2"
:prop3: "value3"
:propn: "valuen"
...
envn:
:prop1: "this changed"
...
:prop3: "value3"
:propn: "valuen"