![交换显示公共字符串的两个连续行的位置](https://linux22.com/image/192808/%E4%BA%A4%E6%8D%A2%E6%98%BE%E7%A4%BA%E5%85%AC%E5%85%B1%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E4%B8%A4%E4%B8%AA%E8%BF%9E%E7%BB%AD%E8%A1%8C%E7%9A%84%E4%BD%8D%E7%BD%AE.png)
我有一个大约包含的文本文件。 1200万行,每行由4个字段组成(第1、2、3和4列)
大多数行在第 2 列中都有一个唯一的 STRING。我不想修改这些行中的任何一行。
在文本文件中,我经常在第 2 列中有 2 个连续行具有相同的 STRING。这种情况在整个文本文件中大约出现 10,000 次。下面显示了一个示例:
column1 column2 column3 column4
WT 1 ILS G
WT 2 DSG E
WT 3 WYT S
. . . .
WT 106 AAA X
WT 106 BBB Y
. . . .
WT 2704 CCC X
WT 2704 DDD Y
. . . .
我想要实现的目标:
column1 column2 column3 column4
WT 1 ILS G
WT 2 DSG E
WT 3 WYT S
. . . .
WT 106 BBB Y
WT 106 AAA X
. . . .
WT 2704 DDD Y
WT 2704 CCC X
. . . .
我有什么资源?
我有一个包含 10,000 个字符串的文本文件,这些字符串出现了两次(在我的示例中为 106 和 2704),我需要交换这两行。我还知道 X 和 Y 在第 2 列相同的行中始终相同。
到现在为止我都做了什么?
我知道如何识别具有公共字符串(我指定的,例如 106)的两行,并使用 sed 交换它们。我不知道如何使其动态化(使用带有 10000 个字符串的文本文件来识别),所以我不必单独启动 10000 个命令。
预先感谢您的时间和帮助。最好的,
洛朗
答案1
awk 中的强力方法:始终保存一行,并在字段 1 中提升与其匹配的任何后续行。
我对一百万行进行了测试(使用一次交换),它在 5.5 秒内运行,因此您的运行时间应该只是一分钟多一点。它不需要您的参考文件。
该脚本,包括带有测试行的 HereDoc。
#! /bin/bash
awkPairs () {
local Awk='
FNR == 1 { k = $2; x = $0; next; }
$2 != k { print x; k = $2; x = $0; next; }
{ print $0; }
END { print x; }
'
awk -f <( printf '%s' "${Awk}" ) -
}
[ x ] && time awkPairs <<'[][]'
WT 1 One x1
WT 2 Two x2
WT 3 Three_1 x3
WT 3 Three_2 y3
WT 4 Four x4
WT 5 Five_1 x5
WT 5 Five_2 y5
[][]
(短期)测试运行。
$ ./awkPairs
WT 1 One x1
WT 2 Two x2
WT 3 Three_2 y3
WT 3 Three_1 x3
WT 4 Four x4
WT 5 Five_2 y5
WT 5 Five_1 x5
real 0m0.009s
user 0m0.004s
sys 0m0.006s
删除了所有测试材料的脚本。可以使用单个参数(输入文件的名称)或使用重定向或管道输入的标准输入来调用它。输出始终转到 stdout。
#! /bin/bash
awkPairs () {
local Awk='
FNR == 1 { k = $2; x = $0; next; }
$2 != k { print x; k = $2; x = $0; next; }
{ print $0; }
END { print x; }
'
awk -f <( printf '%s' "${Awk}" ) "${1:--}"
}
awkPairs "${1}"
因此可以通过以下任何一种方式调用它:
./awkPairs myData.txt
./awkPairs < myData.txt
cat myData.txt | ./awkPairs
答案2
GNU sed 处于扩展正则表达式模式-E
(这使得正则表达式的噪音更少)。我们在模式空间中保存两条线并比较两条线的第一个字段。如果它们匹配,我们将在模式空间中打印交换的行并读取下一行。
sed -Ee '
$!N
s/^(\S+\s+(\S+)\s.*)\n(\S+\s+\2\s.*)/\3\n\1/
t;P;D
' file
注意:这假设“文件”中没有前导空格。
答案3
假设只有对需要交换的行数(即不是具有相同第二个字段的三个或更多连续行),并且该文件至少包含两行:
function possibly_swap() {
if (stringa == stringb) {
# The two previous lines needs swapping.
t = linea
linea = lineb
lineb = t
}
}
FNR >= 3 {
possibly_swap()
# Output the 2nd previous line (possibly swapped now).
print lineb
}
{
# Push new data.
stringb = stringa
lineb = linea
stringa = $2
linea = $0
}
END {
# We may need to output the last two
# lines swapped...
possibly_swap()
print lineb
print linea
}
该awk
程序使用两组变量,stringa
和stringb
、linea
和lineb
。变量string
包含字符串,即输入行中最近两行的第二个字段。变量line
包含相应的满的线。
整个代码中使用的后缀a
和b
对应于前一行和之前的行(“前第二行”)。
您可以将上面的代码放入其自己的文件中(此处我使用script.awk
)并在其他文件(此处file
)上运行它,如下所示
awk -f script.awk file
与“one-liner”相同的代码:
awk 'FNR>=3{if(sa==sb){t=la;la=lb;lb=t}print lb}{sb=sa;lb=la;sa=$2;la=$0}END{if(sa==sb){print la;print lb}else{print lb;print la}}' file
答案4
awk '
f {
swap = $2 == p2
print (swap ? $0 : prev)
}
!swap {
prev = $0
p2 = $2
f = 1
}
END { if (f) print prev }' file
延迟打印 1 条记录。根据当前第二个字段是否与前一个字段相同,选择要打印的记录(当前或上一个)。如果打印当前记录,prev
则不会更新。具有相同第二个字段的连续记录将有效地向上移动 1 个位置,其中第一个记录将移至最后一个位置。最后,打印剩余的记录(如果输入中存在任何记录)。
如果您想将此“转移”限制为最大连续记录数(例如仅交换对),只需更改swap = $2 == p2
为swap = $2 == p2 && f++ < 2
.