我有一个大文本文件(有 5m 行),格式如下(4 列,用 分隔;
):
字符串1;字符串2;字符串3;用户身份
这前 3 个字符串(SHA1) 形成单个 ID,称为应用程序ID(所以可以这样简化:)appId; userId
。第二列(string2,或第二部分应用程序ID) 本身可能由用逗号分隔的一些部分组成,
。文件已排序。
我希望前面有每个应用程序的用户列表,如下所示:
输入文件:
app1, user1
app1, user2
app1, user3
app2, user1
输出文件:
app1: user1, user2, user3
app2: user1
“真实”的一部分输入文件:
44a934ca4052b34e70f9cb03f3399c6f065becd0;bf038823f9633d25034220b9f10b68dd8c16d867;309;8ead5b3e0af5b948a6b09916bd271f18fe2678aa
44a934ca4052b34e70f9cb03f3399c6f065becd0;bf038823f9633d25034220b9f10b68dd8c16d867;309;a21245497cd0520818f8b14d6e405040f2fa8bc0
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16;337556fc485cd094684a72ed01536030bdfae5bb
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16;382f3aaa9a0347d3af9b35642d09421f9221ef7d
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16;396529e08c6f8a98a327ee28c38baaf5e7846d14
“真实的”输出文件应如下所示:
44a934ca4052b34e70f9cb03f3399c6f065becd0;bf038823f9633d25034220b9f10b68dd8c16d867;309:8ead5b3e0af5b948a6b09916bd271f18fe2678aa, a21245497cd0520818f8b14d6e405040f2fa8bc0
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16:337556fc485cd094684a72ed01536030bdfae5bb, 382f3aaa9a0347d3af9b35642d09421f9221ef7d, 396529e08c6f8a98a327ee28c38baaf5e7846d14
我怎样才能做到这一点?
编辑:另外,每个应用程序可以有数千个用户,那么排队可以有多长?线路长度有限制吗?
答案1
在 Perl 中
perl -F';' -lane 'push @{$h{join ";",@F[0..2]}},$F[3];
END{
for(sort keys %h){
print "$_: ". join ",",@{$h{$_}};
}
}' your_file
您应该能够awk
使用关联数组做类似的事情,但我并不是很精通,awk
所以我无法贡献实际的代码。
解释
这是上述代码的扩展版本,它使用尽可能少的“魔法”:
open($FH,"<","your_file");
while($line=<$FH>){ # For each line in the file (accomplished by -n)
chomp $line; # Remove the newline at the end (done by -l)
# The ; is set by -F and storing the split in @F done by -a
@F = split /;/,$line # Split the line into fields on ;
$app_id = join ";",@F[0..2]; # AppID is the first 3 fields
push @{$h{$app_id}},$F[3]; # The 4th field is added onto the hash
} # The whole file has been read at this point.
foreach $key (sort keys %h){ # Sort the hash by AppID
print "$key: " . join ",",@{h{$key}}."\n"; # Print the array values
# The newline ("\n") added at the end is also done by -l
}
现在只剩push
下这句话需要更详细地解释:
push
通常用于向数组变量添加元素。例如:push @a,$x
将变量的内容追加
$x
到数组中@a
。逐行读取文件的循环正在填充哈希表 (
%h
)。哈希的键是 AppID,与每个键对应的值是一个数组,其中包含与该 AppID 关联的所有用户 ID。这是一个匿名数组(它没有名称);在 Perl 中,这是作为数组引用实现的(有点类似于 C 指针)。由于%h
与 AppID 相对应的值$app_id
由 表示$h{$app_id}
,因此附加 Perl 数组 sigial (@
) 会将哈希值视为数组(取消引用数组引用)并将当前用户 ID 压入其中。另一种可能会让您感觉不那么“Perlish”的替代方法是将第四个字段连接到当前值:
while(...) { ... $h{$app_id} = $h{$app_id} . ",$F[3]" } foreach $key (sort keys %h) { print "$_: $h{$_}" }
其中
.
Perl 中的 是字符串连接运算符。
请注意,在解释代码中,我省略了perl -e '...'
包装器,因此语法突出显示可以到达代码并使其更具可读性。
答案2
既然您声明文件已排序,那么是否可以使用一个简单的循环来仅存储前面的appId
字符串的内存?有点像 @Qeole 的方法,但通过使用 shell 的分隔函数加上字符串比较来sed
避免正则表达式的开销:read
#!/bin/bash
appId=""
while IFS=\; read -r s1 s2 s3 userId; do
if [ "$s1;$s2;$s3" == "$appId" ]; then
printf ', %s' "$userId"
else
appId="$s1;$s2;$s3"
printf '\n%s:%s' "$appId" "$userId"
fi
done < yourfile
printf '\n'
注意:这会在输出开始时打印一个额外的换行符,但这可以通过最小的额外复杂性来防止。重击应该是相当对于这种事情来说很快,但如果没有,您可以用几乎任何类似的脚本语言重新实现。
答案3
和sed
:
sed 's/;/:\t/3;H;1h;x
s/^\(\([^:]*\):.*\)\n\2/\1/
/\n/P;//g;h;$!d' <input |
tr : \\n
打印:
44a934ca4052b34e70f9cb03f3399c6f065becd0;bf038823f9633d25034220b9f10b68dd8c16d867;309
8ead5b3e0af5b948a6b09916bd271f18fe2678aa
a21245497cd0520818f8b14d6e405040f2fa8bc0
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16
337556fc485cd094684a72ed01536030bdfae5bb
382f3aaa9a0347d3af9b35642d09421f9221ef7d
396529e08c6f8a98a327ee28c38baaf5e7846d14
您可以放下tr
以使各组保持在同一行上。这id:
在这种情况下将用冒号分隔。您可能还需要用文字字符替换\t
第一行中的转义符<tab>
- 或者您可以随意\t
完全删除abs - 它们只会使输出更具可读性(在我看来)并且对于正则表达式的功能来说并不重要。
答案4
一个sed
答案:
sed ': l;N;s/^\([^;]\+;[^;]\+;[^;:]\+\)[;:] *\(.*\)\n\1;\(.*\)/\1: \2, \3/;tl;P;D' input_file.txt
文件只读取一次,因此性能应该不会太差,但我不能告诉你更多。
详细信息:
sed ': l; # Label l
N; # Add next line of input to pattern space
s/^\([^;]\+;[^;]\+;[^;:]\+\)[;:] *\(.*\)\n\1;\(.*\)/\1: \2, \3/;
# If two lines in pattern space start with same AppID, then
# take user ID and append it to first line, then delete second line
tl; # If previous substitution succeeded, i.e. we scanned two lines with
# same AppID, then loop to label l. Else go on…
P; # Print first line from pattern space (here there should be two lines
# in pattern space, starting with a different AppID)
D; # Delete first line of pattern space; start script again with
# remaining text in pattern space, or next input line if pattern
# space is empty
' input_file.txt
(但我不知道行长度的潜在限制,抱歉。)