Sed 动态搜索带和不带“+”字符的字符串
Sed dynamically search for string with and without '+' character
所以我有一个被各种脚本解析的配置文件,所以格式是不能改的,不过只要严格遵守格式,内容是可以的。此文件默认包含
等字符串
multiconfig:nmb-devel:nmb-trs-devel
multiconfig:nmb-deploy:nmb-trs-deploy
multiconfig:nmb-deploy:nmb-trs-deploy+
multiconfig:ijk-devel:ijk-trs-devel
multiconfig:ijk-deploy:ijk-trs-deploy
multiconfig:ijk-deploy:ijk-trs-deploy+
multiconfig:qrs-devel:qrs-trs-devel
multiconfig:qrs-deploy:qrs-trs-deploy
multiconfig:qrs-deploy:qrs-trs-deploy+
目前,我有一个脚本可以将这些配置(multiconfig:...)解析成一个数组,以及一个配置数组来替换这些原来的配置。
例如
忽略后面的尾部操作,ACTUAL conf.txt 包含另一个要跳过的匹配项,这就是成功完成的。
TARGETS="multiconfig:new-devel:new-trs-devel multiconfig:newer-devel:newer-trs-devel multiconfig:newest-devel:newest-trs-devel"
NEW_TARGETS_ARR=( $TARGETS )
OLD_TARGETS_ARR=($(sed -n '/multiconfig/p' conf.txt | tail -n +2 | awk '!seen[[=11=]]++'))
注意: 不是有问题的sed操作
这些工作正常,并产生适当的数组,例如:
NEW_TARGETS_ARR: multiconfig:new-devel:new-trs-devel, multiconfig:newer-devel:newer-trs-devel, multiconfig:newest-devel:newest -trs-开发
OLD_TARGETS_ARR: multiconfig:nmb-devel:nmb-trs-devel, multiconfig:nmb-deploy:nmb-trs-deploy, multiconfig:nmb-deploy:nmb-trs -deploy+, multiconfig:ijk-devel:ijk-trs-devel, multiconfig:ijk-deploy:ijk-trs-deploy, multiconfig:ijk-deploy:ijk-trs-deploy+, multiconfig:qrs -devel:qrs-trs-devel, multiconfig:qrs-deploy:qrs-trs-deploy, multiconfig:qrs-deploy:qrs-trs-deploy+
我的objective是用新配置替换旧配置
我现在执行此操作的方法是遍历 OLD_TARGETS_ARR 并将每个 OLD_TARGETS_ARR[index] 替换为 NEW_TARGETS_ARR[index]:
for i in ${!OLD_TARGETS_ARR[@]}
do
if [ "${NEW_TARGETS_ARR[$i]}" = "" ]; then
NEW_TARGETS_ARR[$i]="[EMPTY]"
fi
echo $i
echo "OLD TARGET $i: ${OLD_TARGETS_ARR[$i]}"
echo "NEW TARGET $i: ${NEW_TARGETS_ARR[$i]}"
echo "sed -i \"s/${OLD_TARGETS_ARR[$i]}/${NEW_TARGETS_ARR[$i]}/g\" conf.txt"
sed -i "s/${OLD_TARGETS_ARR[$i]}/${NEW_TARGETS_ARR[$i]}/g" conf.txt
done
现在,理论上这应该(或我想要的)结果如下
conf.txt
multiconfig:new-devel:new-trs-devel
multiconfig:newer-devel:newer-trs-devel
multiconfig:newest-devel:newest-trs-devel
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
虽然实际结果如下
conf.txt
multiconfig:new-devel:new-trs-devel
[EMPTY]
[EMPTY]+
[EMPTY]
[EMPTY]
[EMPTY]+
[EMPTY]
[EMPTY]
[EMPTY]+
虽然我还没有弄清楚为什么多于必要的条目被替换为“[EMPTY]”,但我发现 'deploy+' 上的“+”没有被解析,并且必须以某种方式、形状或形式转义。鉴于这些 sed 操作是动态的,我不能简单地在“+”前面添加一个 \。首先,如何确保“+”被解析为要在 sed 操作中搜索的字符串的一部分?
其次,我可能误解了 sed 的一些基本部分,或者我的循环,尽管为什么除了第一个匹配之外的所有内容都被替换为“[EMPTY]”?
感谢任何人提供的任何意见,
先谢谢大家了!
假设您正在尝试匹配 nmb
,使用您的 NEW_TARGETS
数组,您可以尝试这个实现
#!/usr/bin/env bash
NEW_TARGETS=(multiconfig:new-devel:new-trs-devel multiconfig:newer-devel:newer-trs-devel multiconfig:newest-devel:newest-trs-devel)
i=0
while read -r line; do
sed "/^multiconfig:nmb/s/.*/${NEW_TARGETS[$i]}/;/new\|^$/! c\[EMPTY]" <<< $line
i=$((i+1))
done < input_file
输出
multiconfig:new-devel:new-trs-devel
multiconfig:newer-devel:newer-trs-devel
multiconfig:newest-devel:newest-trs-devel
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
使用 sed
替换字符串文字的主要问题是您需要对这些字符串进行转义,以使它们在此上下文中不具有任何特殊含义(例如,参见 Escape a string for a sed replace pattern)。
另一个特定于您的代码的问题是您在 shell 循环中调用 sed
。这非常慢,应尽可能避免。您可以通过构建一个包含所有 sub-commands 的 sed
命令来解决这个问题(例如 sed 's/old1/new1/;s/old2/new2/;...')
但那是 sub-optimal 因为每一行都将由每个 sub-command ,并且也容易出错(例如,当 new1
是以下 sub-command 中替换的目标时)。
考虑到所有这些,sed
似乎并不是完成这项工作的最佳工具,尤其是当您有其他标准工具(如 awk
可以高效地完成此工作时:
#!/bin/bash
NEW_TARGETS_ARR=(
multiconfig:new-devel:new-trs-devel
multiconfig:newer-devel:newer-trs-devel
multiconfig:newest-devel:newest-trs-devel
)
OLD_TARGETS_ARR=(
$(
sed -n '/multiconfig/p' conf.txt |
tail -n +2 |
awk '!seen[[=10=]]++'
)
)
for (( i = ${#NEW_TARGETS_ARR[@]}; i < ${#OLD_TARGETS_ARR[@]}; i++))
do
NEW_TARGETS_ARR[i]='[EMPTY]'
done
awk -v from="${OLD_TARGETS_ARR[*]}" -v to="${NEW_TARGETS_ARR[*]}" '
BEGIN{
fromCount = split(from,fromArr)
toCount = split(to,toArr)
for (i=1; i<=fromCount; i++)
tr[fromArr[i]] = toArr[i]
}
in tr { = tr[] }
1
' conf.txt
multiconfig:new-devel:new-trs-devel
multiconfig:newer-devel:newer-trs-devel
multiconfig:newest-devel:newest-trs-devel
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
解释
填充 OLD_TARGETS_ARR
和 NEW_TARGETS_ARR
后,我将它们作为参数传递给 awk
(作为 space 分隔字符串):
在 BEGIN { ... }
块中,我将 re-split 字符串参数转换为数组并构建一个关联数组,用于将 old 值转换为它的新值。
in tr { = tr[] }
表示如果当前行的第一列是替换目标,那么我就进行替换。
1
打印当前行。
这可能对你有用(GNU sed & bash):
sed -E '1{x;s/.*/echo '"${NEW_TARGETS_ARR[*]}"'/e;x}
/multiconfig/!b;g;//!{s/.*/[EMPTY]/p;d};s/ .*//;x;s/\S+ ?//;x' file
将新目标放入 sed hold space(space 分开)。
仅关注包含 multiconfig
.
的行
用新目标替换当前行。
如果当前行为空,则替换为[EMPTY]
,打印后删除。
否则,移除除第一个目标以外的所有目标,切换到保留 space 并通过移除第一个目标及其后续 space 为下一个目标做准备,然后切换回当前行并打印出来。
所以我有一个被各种脚本解析的配置文件,所以格式是不能改的,不过只要严格遵守格式,内容是可以的。此文件默认包含
等字符串multiconfig:nmb-devel:nmb-trs-devel
multiconfig:nmb-deploy:nmb-trs-deploy
multiconfig:nmb-deploy:nmb-trs-deploy+
multiconfig:ijk-devel:ijk-trs-devel
multiconfig:ijk-deploy:ijk-trs-deploy
multiconfig:ijk-deploy:ijk-trs-deploy+
multiconfig:qrs-devel:qrs-trs-devel
multiconfig:qrs-deploy:qrs-trs-deploy
multiconfig:qrs-deploy:qrs-trs-deploy+
目前,我有一个脚本可以将这些配置(multiconfig:...)解析成一个数组,以及一个配置数组来替换这些原来的配置。 例如
忽略后面的尾部操作,ACTUAL conf.txt 包含另一个要跳过的匹配项,这就是成功完成的。
TARGETS="multiconfig:new-devel:new-trs-devel multiconfig:newer-devel:newer-trs-devel multiconfig:newest-devel:newest-trs-devel"
NEW_TARGETS_ARR=( $TARGETS )
OLD_TARGETS_ARR=($(sed -n '/multiconfig/p' conf.txt | tail -n +2 | awk '!seen[[=11=]]++'))
注意: 不是有问题的sed操作
这些工作正常,并产生适当的数组,例如:
NEW_TARGETS_ARR: multiconfig:new-devel:new-trs-devel, multiconfig:newer-devel:newer-trs-devel, multiconfig:newest-devel:newest -trs-开发
OLD_TARGETS_ARR: multiconfig:nmb-devel:nmb-trs-devel, multiconfig:nmb-deploy:nmb-trs-deploy, multiconfig:nmb-deploy:nmb-trs -deploy+, multiconfig:ijk-devel:ijk-trs-devel, multiconfig:ijk-deploy:ijk-trs-deploy, multiconfig:ijk-deploy:ijk-trs-deploy+, multiconfig:qrs -devel:qrs-trs-devel, multiconfig:qrs-deploy:qrs-trs-deploy, multiconfig:qrs-deploy:qrs-trs-deploy+
我的objective是用新配置替换旧配置
我现在执行此操作的方法是遍历 OLD_TARGETS_ARR 并将每个 OLD_TARGETS_ARR[index] 替换为 NEW_TARGETS_ARR[index]:
for i in ${!OLD_TARGETS_ARR[@]}
do
if [ "${NEW_TARGETS_ARR[$i]}" = "" ]; then
NEW_TARGETS_ARR[$i]="[EMPTY]"
fi
echo $i
echo "OLD TARGET $i: ${OLD_TARGETS_ARR[$i]}"
echo "NEW TARGET $i: ${NEW_TARGETS_ARR[$i]}"
echo "sed -i \"s/${OLD_TARGETS_ARR[$i]}/${NEW_TARGETS_ARR[$i]}/g\" conf.txt"
sed -i "s/${OLD_TARGETS_ARR[$i]}/${NEW_TARGETS_ARR[$i]}/g" conf.txt
done
现在,理论上这应该(或我想要的)结果如下
conf.txt
multiconfig:new-devel:new-trs-devel
multiconfig:newer-devel:newer-trs-devel
multiconfig:newest-devel:newest-trs-devel
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
虽然实际结果如下
conf.txt
multiconfig:new-devel:new-trs-devel
[EMPTY]
[EMPTY]+
[EMPTY]
[EMPTY]
[EMPTY]+
[EMPTY]
[EMPTY]
[EMPTY]+
虽然我还没有弄清楚为什么多于必要的条目被替换为“[EMPTY]”,但我发现 'deploy+' 上的“+”没有被解析,并且必须以某种方式、形状或形式转义。鉴于这些 sed 操作是动态的,我不能简单地在“+”前面添加一个 \。首先,如何确保“+”被解析为要在 sed 操作中搜索的字符串的一部分?
其次,我可能误解了 sed 的一些基本部分,或者我的循环,尽管为什么除了第一个匹配之外的所有内容都被替换为“[EMPTY]”?
感谢任何人提供的任何意见,
先谢谢大家了!
假设您正在尝试匹配 nmb
,使用您的 NEW_TARGETS
数组,您可以尝试这个实现
#!/usr/bin/env bash
NEW_TARGETS=(multiconfig:new-devel:new-trs-devel multiconfig:newer-devel:newer-trs-devel multiconfig:newest-devel:newest-trs-devel)
i=0
while read -r line; do
sed "/^multiconfig:nmb/s/.*/${NEW_TARGETS[$i]}/;/new\|^$/! c\[EMPTY]" <<< $line
i=$((i+1))
done < input_file
输出
multiconfig:new-devel:new-trs-devel
multiconfig:newer-devel:newer-trs-devel
multiconfig:newest-devel:newest-trs-devel
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
使用 sed
替换字符串文字的主要问题是您需要对这些字符串进行转义,以使它们在此上下文中不具有任何特殊含义(例如,参见 Escape a string for a sed replace pattern)。
另一个特定于您的代码的问题是您在 shell 循环中调用 sed
。这非常慢,应尽可能避免。您可以通过构建一个包含所有 sub-commands 的 sed
命令来解决这个问题(例如 sed 's/old1/new1/;s/old2/new2/;...')
但那是 sub-optimal 因为每一行都将由每个 sub-command ,并且也容易出错(例如,当 new1
是以下 sub-command 中替换的目标时)。
考虑到所有这些,sed
似乎并不是完成这项工作的最佳工具,尤其是当您有其他标准工具(如 awk
可以高效地完成此工作时:
#!/bin/bash
NEW_TARGETS_ARR=(
multiconfig:new-devel:new-trs-devel
multiconfig:newer-devel:newer-trs-devel
multiconfig:newest-devel:newest-trs-devel
)
OLD_TARGETS_ARR=(
$(
sed -n '/multiconfig/p' conf.txt |
tail -n +2 |
awk '!seen[[=10=]]++'
)
)
for (( i = ${#NEW_TARGETS_ARR[@]}; i < ${#OLD_TARGETS_ARR[@]}; i++))
do
NEW_TARGETS_ARR[i]='[EMPTY]'
done
awk -v from="${OLD_TARGETS_ARR[*]}" -v to="${NEW_TARGETS_ARR[*]}" '
BEGIN{
fromCount = split(from,fromArr)
toCount = split(to,toArr)
for (i=1; i<=fromCount; i++)
tr[fromArr[i]] = toArr[i]
}
in tr { = tr[] }
1
' conf.txt
multiconfig:new-devel:new-trs-devel
multiconfig:newer-devel:newer-trs-devel
multiconfig:newest-devel:newest-trs-devel
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
[EMPTY]
解释
填充 OLD_TARGETS_ARR
和 NEW_TARGETS_ARR
后,我将它们作为参数传递给 awk
(作为 space 分隔字符串):
在
BEGIN { ... }
块中,我将 re-split 字符串参数转换为数组并构建一个关联数组,用于将 old 值转换为它的新值。in tr { = tr[] }
表示如果当前行的第一列是替换目标,那么我就进行替换。1
打印当前行。
这可能对你有用(GNU sed & bash):
sed -E '1{x;s/.*/echo '"${NEW_TARGETS_ARR[*]}"'/e;x}
/multiconfig/!b;g;//!{s/.*/[EMPTY]/p;d};s/ .*//;x;s/\S+ ?//;x' file
将新目标放入 sed hold space(space 分开)。
仅关注包含 multiconfig
.
用新目标替换当前行。
如果当前行为空,则替换为[EMPTY]
,打印后删除。
否则,移除除第一个目标以外的所有目标,切换到保留 space 并通过移除第一个目标及其后续 space 为下一个目标做准备,然后切换回当前行并打印出来。