如何交换基于模式的文本一次与sed?

假设我有'abbc'字符串,我想替换:

  • Ab -> BC
  • BC -> ab

如果我尝试两次替换,结果不是我想要的:

echo 'abbc' | sed 's/ab/bc/g;s/bc/ab/g'
abab

那么我可以像下面这样使用什么sed命令来替换?

echo abbc | sed SED_COMMAND
bcab
< p > 编辑: 实际上,文本可以有超过2个模式,我不知道我需要多少替换。由于有一个答案说sed是一个流编辑器,它的替换是贪婪的,我认为我将需要使用一些脚本语言
460536 次浏览

也许是这样的:

sed 's/ab/~~/g; s/bc/ab/g; s/~~/bc/g'

~替换为一个你知道不会在字符串中的字符。

sed是一个流编辑器。它贪婪地搜索和替换。实现您所要求的唯一方法是使用中间替换模式,并在最后将其更改回来。

echo 'abcd' | sed -e 's/ab/xy/;s/cd/ab/;s/xy/cd/'

这可能为你工作(GNU sed):

sed -r '1{x;s/^/:abbc:bcab/;x};G;s/^/\n/;:a;/\n\n/{P;d};s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/;ta;s/\n(.)/\1\n/;ta' file

它使用一个查找表,该表准备好并保存在保留空间(HS)中,然后附加到每一行。一个唯一的标记(在本例中是\n)被前置在行开始之前,并用作在整行长度内进行搜索的方法。一旦标记到达行尾,该过程就完成了,并打印出查找表,标记被丢弃。

注意:查找表在一开始就准备好了,并选择第二个唯一标记(在本例中为:),以避免与替换字符串冲突。

以下是一些评论:

sed -r '
# initialize hold with :abbc:bcab
1 {
x
s/^/:abbc:bcab/
x
}


G        # append hold to patt (after a \n)


s/^/\n/  # prepend a \n


:a


/\n\n/ {
P      # print patt up to first \n
d      # delete patt & start next cycle
}


s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/
ta       # goto a if sub occurred


s/\n(.)/\1\n/  # move one char past the first \n
ta       # goto a if sub occurred
'

这个表格是这样的:

   **   **   replacement
:abbc:bcab
**   **     pattern

下面是一个基于oogas sedawk

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab

Tcl对此有一个内装式

$ tclsh
% string map {ab bc bc ab} abbc
bcab

它的工作原理是每次遍历字符串的一个字符,从当前位置开始进行字符串比较。

在perl中:

perl -E '
sub string_map {
my ($str, %map) = @_;
my $i = 0;
while ($i < length $str) {
KEYS:
for my $key (keys %map) {
if (substr($str, $i, length $key) eq $key) {
substr($str, $i, length $key) = $map{$key};
$i += length($map{$key}) - 1;
last KEYS;
}
}
$i++;
}
return $str;
}
say string_map("abbc", "ab"=>"bc", "bc"=>"ab");
'
bcab

下面是ooga的回答的一个变体,它适用于多个搜索和替换对,而无需检查值如何重用:

sed -i '
s/\bAB\b/________BC________/g
s/\bBC\b/________CD________/g
s/________//g
' path_to_your_files/*.txt

这里有一个例子:

之前:

some text AB some more text "BC" and more text.

后:

some text BC some more text "CD" and more text.

注意,\b表示单词边界,这是防止________干扰搜索的原因(我在Ubuntu上使用GNU sed 4.2.2)。如果不使用单词边界搜索,则此技术可能不起作用。

还要注意,这与删除s/________//g并将&& sed -i 's/________//g' path_to_your_files/*.txt附加到命令末尾的结果相同,但不需要两次指定路径。

如果你知道你的文件中没有空值正如jthill所建议的,那么这个函数的一个通用变体就是使用\x0_\x0_来代替________

我总是使用多个带有"-e"的语句

$ sed -e 's:AND:\n&:g' -e 's:GROUP BY:\n&:g' -e 's:UNION:\n&:g' -e 's:FROM:\n&:g' file > readable.sql

这将在所有AND, GROUP BY, UNION和FROM之前添加一个'\n',而'&'表示匹配的字符串,'\n&'表示您想在'matched'之前用'\n'替换匹配的字符串。

对于单个模式的出现可能是一个更简单的方法,您可以尝试如下: Echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'

我的输出:

 ~# echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'
bcab

对于pattern的多次出现:

sed 's/\(ab\)\(bc\)/\2\1/g'

例子

~# cat try.txt
abbc abbc abbc
bcab abbc bcab
abbc abbc bcab


~# sed 's/\(ab\)\(bc\)/\2\1/g' try.txt
bcab bcab bcab
bcab bcab bcab
bcab bcab bcab

希望这能有所帮助!!

echo "C:\Users\San.Tan\My Folder\project1" | sed -e 's/C:\\/mnt\/c\//;s/\\/\//g'

替换

C:\Users\San.Tan\My Folder\project1

mnt/c/Users/San.Tan/My Folder/project1

以防有人需要将windows路径替换为windows子系统for Linux(WSL)路径

我相信这应该能解决你的问题。我可能遗漏了一些边缘情况,如果你注意到一个,请评论。

您需要一种方法将以前的替换从未来的模式中排除,这实际上意味着使输出可区分,并将这些输出从搜索中排除,最终使输出再次不可区分。这与引用/转义过程非常相似,因此我将从中吸取教训。

  • s/\\/\\\\/g转义所有现有的反斜杠
  • s/ab/\\b\\c/g替换原ab为转义bc
  • s/bc/\\a\\b/g替换原bc为转义的ab
  • s/\\\(.\)/\1/g将所有转义的X替换为原始X

我没有解释ab或bc中的反斜杠,但直观地,我将以同样的方式逃避搜索和替换术语——\现在匹配\\,而替换的\\将显示为\

到目前为止,我一直使用反斜杠作为转义字符,但它不一定是最好的选择。几乎任何字符都可以工作,但是要注意环境中需要转义的字符,sed等,这取决于您打算如何使用结果。

如果将字符串替换为变量,解决方案将不起作用。 sed命令需要用双引号代替单引号
#sed -e "s/#replacevarServiceName#/$varServiceName/g" -e "s/#replacevarImageTag#/$varImageTag/g" deployment.yaml

到目前为止发布的每个答案似乎都同意kuriouscoder在他的以上职位中所做的陈述:

达到你要求的唯一方法就是使用中间产物 替换模式并在结束时将其更改回来

然而,如果你打算这样做,并且你的使用可能涉及到更多的琐碎字符串(可能你正在过滤数据等),sed使用的最佳字符是换行符。这是因为由于sed是100%基于行的,换行符是在获取新行时保证永远不会接收到的唯一字符(在此讨论中忘记GNU多行扩展)。

首先,这里有一个非常简单的方法来解决使用换行符作为中间分隔符的问题:

echo "abbc" | sed -E $'s/ab|bc/\\\n&/g; s/\\nab/bc/g; s/\\nbc/ab/g'

简单带来了一些权衡……如果你有多个变量,就像在你最初的文章中一样,你必须全部输入两次。性能也可能得到一点改善。

使用sed做更多的事情会非常麻烦。即使有一些更高级的功能,如分支控制和保持缓冲区(这在我看来真的很弱),你的选择也非常有限。

只是为了好玩,我想出了这个替代方案,但我不认为我有任何特别的理由推荐它胜过这篇文章前面的那个……你必须从本质上制定自己的“惯例”;用于分隔符,如果你真的想在sed中做一些奇特的事情。这对于你最初的帖子来说有点过头了,但是对于那些看到这篇文章并且有更复杂情况的人来说,它可能会激发一些想法。

我的习惯是:使用多个换行符来“保护”;或“;unprotect"你正在做的那部分。一个换行符表示一个单词边界。两个换行符表示候选替换的替代。我不会马上替换,而是在下一行列出候选替换。三个换行符意味着一个值是“锁定在”的,就像你最初的帖子试图用abbc那样。在此之后,进一步的替换将被撤消,因为它们受到换行符的保护。如果我不这么说的话,这有点复杂……! sed的真正意义并不超出基础。

# Newlines
NL=$'\\\n'
NOT_NL=$'[\x01-\x09\x0B-\x7F]'


# Delimiters
PRE="${NL}${NL}&${NL}"
POST="${NL}${NL}"


# Un-doer (if a request was made to modify a locked-in value)
tidy="s/(\\n\\n\\n${NOT_NL}*)\\n\\n(${NOT_NL}*)\\n(${NOT_NL}*)\\n\\n/\\1\\2/g; "


# Locker-inner (three newlines means "do not touch")
tidy+="s/(\\n\\n)${NOT_NL}*\\n(${NOT_NL}*\\n\\n)/\\1${NL}\\2/g;"


# Finalizer (remove newlines)
final="s/\\n//g"


# Input/Commands
input="abbc"
cmd1="s/(ab)/${PRE}bc${POST}/g"
cmd2="s/(bc)/${PRE}ab${POST}/g"


# Execute
echo ${input} | sed -E "${cmd1}; ${tidy}; ${cmd2}; ${tidy}; ${final}"

下面是SED手册的摘录:

- e脚本

——表达=脚本

将脚本中的命令添加到处理输入时要运行的命令集中。

-e选项预先每个替换,并将它们收集在一起。下面是一个适合我的例子:

sed < ../.env-turret.dist \
-e "s/\{\{ name }}/turret$TURRETS_COUNT_INIT/g" \
-e "s/\{\{ account }}/$CFW_ACCOUNT_ID/g" > ./.env.dist

这个例子还展示了如何在替换中使用环境变量。