到目前为止我已经能够四处走动sed 的更高级的功能,例如跨多行的向前/向后查看,但我想了解如何实现以下任务sed因为我感觉我这样做的方法例如在Python不是必需的,也可以在过滤器管道内完成指令。
传入数据的剥离示例如下所示:
1b41cf70 0
1cb8dd19 1
620f0b67 2
620f0b67 3
f35d35fe 4
3a6fb62a 5
620f0b67 6
620f0b67 7
620f0b67 8
b958a7ea 9
f35d35fe 10
f35d35fe 11
620f0b67 12
第一列的宽度始终相同(包含缩短的哈希值),第二列的内容是完全排序的、数字的且没有间隙(因此除了在较长的列表上提供方向之外可能没有必要)。
所需的输出将是这样的(将最后一次连续出现的索引放入附加列中):
1b41cf70 0
1cb8dd19 1
620f0b67 2 3
f35d35fe 4
3a6fb62a 5
620f0b67 6 8
b958a7ea 9
f35d35fe 10 11
620f0b67 12
或者甚至更好地使用重复值的聚合数量(数学表达式(添加)似乎更容易完成awk但我的技能更差,所以这只是为了说明其他理想的结果):
1b41cf70 0
1cb8dd19 1
620f0b67 2 +1
f35d35fe 4
3a6fb62a 5
620f0b67 6 +2
b958a7ea 9
f35d35fe 10 +1
620f0b67 12
我试图追踪在 SO 空间中发现的几个相似但不同的问题,但无法将我的头脑集中在可能更简单的部分上,可能会导致一个解决方案,比如为什么sed '$!N;/^\([^\ ]\+\)\ [0-9]\+\n\1\ /{P;d}' sampledata
会用索引 3,7,11 而不是 8 来切割行。
我的系统安装了 GNU sed 版本 4.8 和 awk 版本 5.1.0,我很想了解如何使用其中之一来完成此任务。不,这不是家庭作业,而是冗长的哈希列表,其中有大量冗余,需要压缩和比较。 ;)
答案1
完全忽略原来的第二列,我们可以用来uniq -c
计算字符串在连续行中重复出现的次数。
从 中获取两个字段的输出uniq -c
,只要字符串重复出现多次(在表格中+x
,其中x
是该字段出现的次数减一),我们就可以创建第三个字段。然后我们重新排列前两个字段并打印。
cut -d ' ' -f 1 file |
uniq -c |
awk '$1 > 1 { $3 = "+" $1 - 1 } { nr += $1; $1 = $2; $2 = nr - 1 - $3; print }'
该nr
变量表示原始文件中的行号。
给出问题中数据的输出:
1b41cf70 0
1cb8dd19 1
620f0b67 2 +1
f35d35fe 4
3a6fb62a 5
620f0b67 6 +2
b958a7ea 9
f35d35fe 10 +1
620f0b67 12
答案2
使用awk
:
awk 'function prnt() { print buf, preV; preK=$1; preV=""; buf=$0 }
preK!=$1 { prnt(); next } { preV=$2 }
END { prnt() }' infile
输出:
1b41cf70 0
1cb8dd19 1
620f0b67 2 3
f35d35fe 4
3a6fb62a 5
620f0b67 6 8
b958a7ea 9
f35d35fe 10 11
620f0b67 12
awk 'function prnt() { print buf, (c?"+"c:""); preK=$1; c=0; buf=$0 }
preK!=$1 { prnt(); next } { c++ }
END { prnt() }' infile
输出:
1b41cf70 0
1cb8dd19 1
620f0b67 2 +1
f35d35fe 4
3a6fb62a 5
620f0b67 6 +2
b958a7ea 9
f35d35fe 10 +1
620f0b67 12
答案3
你要求的sed
。这里有 2 个版本接近您自己的尝试,但使用 POSIX埃雷
扩展正则表达式。两者都在模式空间中最多保留 2 行。
sed -E '
:Q
$!N
/^([^ ]+) ([0-9]+)( [0-9]+)?\n\1 ([0-9]+)$/{
s//\1 \2 \4/
bQ
}
P
D
' -- file
在哪里:
- 除非在最后一行 (
$!
) 附加换行符并将下一行附加到当前行 (N
) - 匹配表达式
/…/
将字段 1 和 2 捕获为\1
and\2
,可能的最后一个索引为\3
,最后下一行的索引为\4
- 如果字段 1 在下一行重复,则整个模式空间将替换为字段 1(哈希)、字段 2(第一个索引)和最后一个索引,并分支到脚本开头 - 命令中的空正则表达式
s
将重新应用最后使用的正则表达式(在/…/
) - 否则打印并删除第一行 (
P;D;
) 并恢复循环
输出:
1b41cf70 0
1cb8dd19 1
620f0b67 2 3
f35d35fe 4
3a6fb62a 5
620f0b67 6 8
b958a7ea 9
f35d35fe 10 11
620f0b67 12
如果相反:
/^([^ ]+) ([0-9]+)( ([+]+))?\n\1 [0-9]+$/{
s//\1 \2 \4+/
输出变为:
1b41cf70 0
1cb8dd19 1
620f0b67 2 +
f35d35fe 4
3a6fb62a 5
620f0b67 6 ++
b958a7ea 9
f35d35fe 10 +
620f0b67 12
sed
不热衷于计数(但可以做到)。
sed
最后,对使用 POSIX 的脚本进行一些评论布雷s
- 不要
[]
对 s 内的字符进行转义,转义字符除外。,]
, 以及可能的转义字符-
- BRE 中的
+
不是量词而是普通的加号 - 不需要转义空格字符
}
为了便于移植,在结束编辑命令列表之前使用分号- 该
d
命令删除整个模式空间,而不仅仅是第一个换行符
答案4
只是一个快速混淆的多行查找替换解决方案(这次是在 Perl 中)
perl -0pe 's/(\w+) (\d+)(\n\1 (\d+))+/$1 $2 $4/g' file
相应的 (gnu)sed 版本可能是...
sed -rz 's/(\w+) ([0-9]+)(\n\1 ([0-9]+))+/\1 \2 \4/g' file
对于“+”输出,我们必须做一些额外的计算:
perl -0pe 's/(\w+) (\d+)(\v\1 (\d+))+/"$1 $2 +" . ($4-$2)/ge' file