10

上周五我遇到了一个问题,将文本转换为另一种格式。在那台机器上,只有 gnu sed 可用,没有 awk(奇怪,我知道)。我对perl一无所知。所以我正在寻找一个仅限 sed 的解决方案。

文件内容是:

a  yao.com sina.com
b  kongu.com
c  polm.com unee.net 21cn.com iop.com foo.com bar.com baz.net happy2all.com
d  kinge.net

所需的输出(应该是一个新文件)是:

a  yao.com 
a  sina.com
b  kongu.com
c  polm.com 
c  unee.net 
c  21cn.com 
c  iop.com
c  foo.com
c  bar.com
c  baz.net
c  happy2all.com
d  kinge.net

我尝试了很多,也搜索了著名的 sed oneliner,但我无法做到......有人可以帮助我吗?

4

8 回答 8

6

有趣的问题:

$ sed -r 's/(\w+\.\w+)/>  &/2g;:a s/^([a-z]+)(.*)>/\1\2\n\1/g;ta' file
a  yao.com 
a  sina.com
b  kongu.com
c  polm.com 
c  unee.net 
c  21cn.com 
c  iop.com 
c  foo.com 
c  bar.com 
c  baz.net 
c  happy2all.com
d  kinge.net

编辑:

它通过使用两个替换来工作。

第一个>在需要展平的 URL 之前放置一个作为保持字符的:

$ sed -r 's/(\w+\.\w+)/>  &/2g' file
a  yao.com >  sina.com
b  kongu.com
c  polm.com >  unee.net >  21cn.com >  iop.com >  foo.com >  bar.com ...
d  kinge.net

第二个基本上用>换行符替换了持有(使用条件分支)

$ sed -r ':a s/^([a-z]+)(.*)>/\1\2\n\1/g;ta'
于 2013-03-16T22:09:40.293 回答
5

对于 sed 来说,这不是一件容易的事,尤其是单行。但是你提到了“gnu sed”。我看见了光!

gnu sed 支持s/.../.../ge在这种情况下很有用:

kent$  sed -r 's@(^[a-z]+) (.*)@echo "\2"\|sed "s# #\\n\1  #g"\|sed "/^$/d"@ge' file  
a  yao.com
a  sina.com
b  kongu.com
c  polm.com
c  unee.net
c  21cn.com
c  iop.com
c  foo.com
c  bar.com
c  baz.net
c  happy2all.com
d  kinge.net

简短说明:

  1. 外部 sed允许我们将匹配sed -r 's@..x..@..y..@ge' file 的部分传递给外部命令ge
  2. ..y..部分由ge. 我传递\2给另一个sed(通过echo):sed "s# #\\n\1 #g"这个 sed 将所有空间替换为\n + \1 + space
  3. 在原始文件中,\n每一行都有(结尾),所以第 2 步(上一步)的结果中有空行,我们需要删除那些空行"/^$/d"
  4. 最后,可以完成步骤 1 中的替换(外部 sed),我们得到结果。

检查info sed_s/../../ge

编辑,添加了 OP 评论的双空格。

于 2013-03-16T22:20:50.410 回答
1

正如其他人所指出的,sed 解决方案很棘手,所以我想我发布了一个 bash-dito:

#!/bin/bash

while read -a array
do
    for i in ${array[@]:1}
    do
        echo ${array[0]} $i
    done
done < input

输出:

a yao.com
a sina.com
b kongu.com
c polm.com
c unee.net
c 21cn.com
c iop.com
c foo.com
c bar.com
c baz.net
c happy2all.com
d kinge.net
于 2013-03-16T22:32:12.347 回答
1

这是一个真正有效的 sed-only 脚本。我在下面将其编写为 sed 在命令行上调用的文件,但它可以全部在命令行上键入,也可以全部输入到单独的脚本中:

将以下内容另存为 sedscript(或任何您想调用的名称)。解释跟在输出后面。

:start
    h
    s/\(.\ \ [^ ]*\).*/\1/
    t continue
    d
:continue
    p
    x
    s/\(.\ \)\ [^ ]*\(\ .*\)/\1\2/
    t start
    d

现在运行sed -f sedscript myfile.txt

将上面的示例保存为 myfile.txt,输出如下:

a  yao.com
a  sina.com
b  kongu.com
c  polm.com
c  unee.net
c  21cn.com
c  iop.com
c  foo.com
c  bar.com
c  baz.net
c  happy2all.com
d  kinge.net

Sed 有一个模式缓冲区(您通常在其中使用s/a/b/各种命令)和一个保持缓冲区。在此脚本中,信息在保留缓冲区中来回交换,以在处理另一部分时保留一行中未编辑的部分。

:start= 启用跳跃的标签

h= 将模式缓冲区(当前行)交换到保持缓冲区

s/\(.\ \ [^ ]*\).*/\1/= 虽然整行在保持缓冲区中是安全的,但删除第一个域之后的所有内容,留下第一个所需的行(例如“a yao.com”)。

t continue= 如果上一个命令导致替换,则跳转到“继续”标签

d=如果我们没有跳,那意味着我们已经完成了。删除模式缓冲区并继续到文件的下一行。

:continue= 上一个跳转的标签

p= 打印出模式缓冲区(例如“a yao.com”)

x= 将模式缓冲区与保持缓冲区交换(也可g用于简单地将保持缓冲区复制到模式缓冲区上)

s/\(.\ \)\ [^ ]*\(\ .*\)/\1\2/= 完整的原始字符串现在已被交换到模式缓冲区 - 剥离我们刚刚处理的域(例如“yao.com”)

t start= 如果这不是最后一个域,则使用新的缩短字符串重新开始脚本。

d= 如果这是最后一个域,则删除模式缓冲区并继续到文件中的下一行。

于 2013-03-16T23:56:54.313 回答
1

这可能对您有用(GNU sed):

sed -r 's/^((\S+\s+)\S+)\s+/\1\n\2/;P;D' file
于 2013-03-17T14:01:00.753 回答
1

这是一个可以做到这一点的单线(对于“一个”的一些定义)。它应该适用于任何 sed,但我只用 gnu sed 测试过它。

sed ':l;s/\(^\|\n\)\([^ \n]\)  \([^ \n][^ \n]*\) /\1\2  \3\ 
\2  /;t l'

这是 . 之后的文字换行符\3\

解释:

  1. 通过用反斜杠转义,可以在替换中包含文字换行符。
  2. :l制作一个名为 的标签l
  3. 如果进行了替换,则循环t l到标签。l
  4. s命令对最初包含输入行的模式空间缓冲区进行操作。在s命令之后,模式空间缓冲区包含替换的结果,包括换行符。通过循环的第二次和后续时间,该s命令获取整个模式空间缓冲区,包括在早期替换中添加的任何换行符。
于 2014-03-16T23:38:36.143 回答
0
cat inputFile.txt | sed -e 's/\([^\ ]*\)\(\ *\)\([^\ ]*\)\(\ *\)\([^\ ]*\)\(\ *\)\([^\ ]*\)\(\ *\)\([^\ ]*\)\(\ *\)/\1 \3\n\1 \5\n\1 \7\n\1 \9/' | grep -vE "^..$"

适用于我的 Ubuntu 12.10。

解释:

  • 将其分为 2 组:文本组和空字符组
  • 重复第 1 组(带第一个字符)甚至组(带文本)
  • 目前适用于由空字符分隔的 4 个文本

最后,删除包含空“第二”组的行。

另一个尝试使用 BASH(执行为“script.sh inputFile.txt”):

#!/bin/bash

firstParams=`cat $1 | sed -e 's/\([^\ ]*\)\(.*\)/\1/'`
count=1
for MY1 in $firstParams
do
    # print line number ${count} and filter params from the second one forth
    restParams=`cat $1 | sed -n "${count}p" | sed -e 's/\([^\ ]*\)\(.*\)/\2/'`
    for MY2 in $restParams
    do
        echo "$MY1 $MY2"
    done
    count=$(($count+1))
done
于 2013-03-16T21:38:50.240 回答
-1

您可以使用

sed -r -n 's/^([a-z])\ \ ([0-9a-z.]*)\ ([0-9a-z .]*)/\1  \2\n\1  \3/p'

它将转换表格的每一行

c  polm.com unee.net 21cn.com iop.com foo.com bar.com baz.net happy2all.com

进入

c  polm.com
c  unee.net 21cn.com iop.com foo.com bar.com baz.net happy2all.com

每次运行时。

所以下次它在前一个 sed 的输出上运行时,这将变成

c  polm.com
c  unee.net
c  21cn.com iop.com foo.com bar.com baz.net happy2all.com

等等。

因此,将前一个 sed 的输出推入新的 sed 最终应该会为您提供所需的格式。

我知道这可能不是最佳答案,如果可能,我会尝试改进它。

于 2013-03-16T22:10:37.280 回答