ChatGPT解决这个技术问题 Extra ChatGPT

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

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

ab -> bc

公元前-> ab

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

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

那么我可以使用什么 sed 命令来替换如下?

echo abbc | sed SED_COMMAND
bcab

编辑:实际上文本可能有超过 2 种模式,我不知道我需要多少替换。由于有一个答案说 sed 是一个流编辑器并且它的替换是贪婪的,我认为我需要为此使用一些脚本语言。

您是否需要在同一行上进行多次替换?如果不只是从这两个 s/// 命令中删除 g 标志,那将起作用。
你错过了我的问题的重点。我的意思是您是否需要在同一行上多次替换 each 。原始输入中的 ab bc 是否存在不止一个匹配项。
抱歉@EtanReisner 我误解了,答案是肯定的。文本可以有多个替换。

o
ooga

也许是这样的:

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

~ 替换为您知道不会出现在字符串中的字符。


GNU sed 处理 nuls,因此您可以将 \x0 用于 ~~
g 是否必要,它有什么作用?
@Lee g 用于全局 - 它替换每行中模式的所有实例,而不仅仅是第一个(这是默认行为)。
请参阅我的答案 stackoverflow.com/a/41273117/539149,了解 ooga 答案的变体,它可以同时替换多个组合。
你知道不会在字符串中 对于生产代码,永远不要对输入做出任何假设。对于测试,好吧,测试永远不会真正证明正确性,但测试的一个好主意是:使用脚本本身作为输入。
J
Jean-François Corbett

我总是使用带有“-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&' 表示您想在匹配的字符串之前用 '\n' 替换匹配的字符串'


它返回 sed: -e: No such file or directory
如果我使用 sed -i -e 会怎样?
这并不能解决操作顺序的主要问题。只有在前一个命令运行后,每个命令才会在整个文件上运行。所以运行这个:echo 'abbc' | sed -e 's:ab:bc:g' -e 's:bc:ab:g' 仍然会导致 abab 而不是 bcab,这就是问题所要问的。
是的,ADJenks,你是对的! :) 也许您可以通过以下方式作弊:echo 'abbc' | sed -e 's:ab:xx:g' -e 's:bc:ab:g' -e 's:xx:bc:g'
@alper,它有效。也许只指定了一个 -e。在这种情况下,-e 选项应为每个语句添加前缀。
k
kuriouscoder

sed 是流编辑器。它贪婪地搜索和替换。完成您要求的唯一方法是使用中间替换模式并最终将其更改回来。

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


C
Community

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

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 附加到命令末尾的结果相同,但不需要指定两次路径。

如果您知道文件中没有出现空值 as jthill suggested,则对此的一般变化是使用 \x0_\x0_ 代替 ________


我同意 hagello 上面关于不对输入可能包含的内容做出假设的评论。因此,我个人认为这是最可靠的解决方案,除了管道 sed 相互叠加(sed 's/ab/xy/' | sed 's/cd/ab/' .....
o
ooga

这可能对您有用(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

g
glenn jackman

Tcl 为此有一个 builtin

$ 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

A
Alec Missine

以下是 SED manual 的摘录:

-e script --expression=script 将 script 中的命令添加到要在处理输入时运行的命令集中。

在每个替换前加上 -e 选项并将它们收集在一起。对我有用的例子如下:

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

此示例还显示了如何在替换中使用环境变量。


d
dst_91

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

我的输出:

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

对于多次出现的模式:

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

希望这可以帮助 !!


M
Martin Brisiak

如果用变量替换字符串,则解决方案不起作用。 sed 命令需要用双引号而不是单引号。

#sed -e "s/#replacevarServiceName#/$varServiceName/g" -e "s/#replacevarImageTag#/$varImageTag/g" deployment.yaml

J
Jotne

这是一个基于 oogas sedawk

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

S
Sandeepraj Singh

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 Subsystem for Linux(WSL) 路径


这与发布的问题无关。
是的,不是直接的。这就是为什么我将其限定为“以防万一”。如果人们像我一样,不是每个人每次在 Stack Overflow 上搜索时都会回答特定问题。但就您而言,我已将此答案放在其他地方。问题是使用 sed 将窗口更改为 Linux 路径。谢谢
您知道您也可以发布自己的问题并回答。如果人们真的在寻找那个特定的问题“如何将 Windows 路径更改为 Linux”,将会很有帮助。真正需要这个答案的人不太可能在这里找到它。
J
John P

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

您需要一种从未来模式中排除先前替换的方法,这实际上意味着使输出可区分,以及从搜索中排除这些输出,最后使输出再次无法区分。这与引用/转义过程非常相似,所以我将借鉴它。

s/\\/\\\\/g 转义所有现有的反斜杠

s/ab/\\b\\c/g 用原始 ab 代替转义 bc

s/bc/\\a\\b/g 用原始 bc 替换转义的 ab

s/\\\(.\)/\1/g 将所有转义的 X 替换为原始 X

我没有考虑 ab 或 bc 中的反斜杠,但直观地说,我会以同样的方式转义搜索和替换术语 - \ 现在匹配 \\,替换后的 \\ 将显示为 \

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


S
Sean

到目前为止发布的每个答案似乎都同意 kuriouscoder 在他的 above post 中的陈述:

完成您要求的唯一方法是使用中间替换模式并最终将其更改回来

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

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

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

简单性带来了一些权衡......如果您有多个变量,就像在您的原始帖子中一样,您必须将它们全部输入两次。性能也可能会有所提高。

使用 sed 做更多的事情变得非常讨厌。即使有一些更高级的功能,例如分支控制和保持缓冲区(IMO 真的很弱),您的选择也非常有限。

只是为了好玩,我想出了一个替代方案,但我认为我没有任何特别的理由推荐它而不是本文前面的那个......你必须基本上为定界符制定自己的“约定”如果您真的想在 sed 中做任何花哨的事情。这对于您的原始帖子来说太过分了,但它可能会为遇到此帖子并遇到更复杂情况的人激发一些想法。

我下面的约定是:使用多个换行符来“保护”或“取消保护”您正在处理的行的一部分。一个换行符表示一个单词边界。两个换行符表示候选替换的替代方案。我不会立即替换,而是在下一行列出候选替换。三个换行符意味着一个值被“锁定”,就像您尝试使用 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}"