我的文件夹中有 5000 个文件。这些文件被命名为 XX0000001 到 XX0005000
我正在尝试从每个文件中获取单词,然后 grep 它们以及另一个文件中的下一行(target.txt)
我的一些 XX* 文件中有大约 30,000 个单词
有什么办法可以做到这一点吗?
我努力了 :
start_number=0000001
end_number=0005000
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
for ((i=start_number; i <=end_number; i++)); do
filename="XX$(printf "%07d" "$i")"
output_file="$output_folder/output_${filename}.txt"
while read -r word; do
awk -v word="$word" '{for (i=1; i<=NF; i++) if($1 ~ word) {print; next}}' "$filename" >> "$output_file"
done < "$words_file"
done
有没有更快的方法来做到这一点?我的目标文件有数百万行需要搜索;精确目标文件大小为 20 GB,106441678 行
例如:XX0000001 文件如下所示:
Big1 Big5 Big7 Big10 Big11
(还有很多很多的字;一些 XX 文件甚至可能有多达 30k 字)
Target.txt 文件如下所示:
#Big1
This_is_a_file_containing_xxxxx
#Big2
This_is_a_file_containing_xxxxx
#Big3
This_is_a_file_containing_xxxxx
#Big4
This_is_a_file_containing_xxxxx
#Big5
This_is_a_file_containing_xxxxx
#Big6
This_is_a_file_containing_xxxxx
#Big7
This_is_a_file_containing_xxxxx
#Big8
This_is_a_file_containing_xxxxx
#Big9
This_is_a_file_containing_xxxxx
#Big10
This_is_a_file_containing_xxxxx
#Big11
This_is_a_file_containing_xxxxx
#Big12
This_is_a_file_containing_xxxxx
答案1
我的文件夹中有 5000 个文件。这些文件被命名为 XX0000001 到 XX0005000
要循环一组文件,请使用 shell glob,例如for f in XX0*
此处。如果您确实需要循环一组数字,则需要注意前导零,就像在 Bash 中一样,它们将数字标记为八进制。例如尝试你的循环,但只是打印数字并查看最后一个:
start_number=0000001
end_number=0005000
for ((i=start_number; i <=end_number; i++)); do
echo $i
done |tail -1
我们看到输出是2560
,不是5000
。不过,zsh 中的情况并非如此,并且您没有提及您正在运行哪个 shell,但是这个问题可能值得指出。
这里,
while read -r word; do
awk -v word="$word" '{for (i=1; i<=NF; i++) if($1 ~ word) {print; next}}' "$filename" >> "$output_file"
done < "$words_file"
我不太确定这是做什么的,但我只是注意到,在 AWK 脚本中,您循环遍历输入行的所有字段,但随后您只引用$1
循环内的字段 1 ( )。
现在,我假设您的文件如下所示:
% cat XX0000001
Big1 Big7
% cat XX0000002
Big5 Big10
% cat target.txt
#Big1
This_is_a_file_containing_xxxxx
#Big2
This_is_a_file_containing_xxxxx
[...]
即文件中的一行中有多个不同的模式(例如Big1
和) (而不是例如每行一个)。另外,我想您想找到与任何模式匹配的行,然后将它们与下一行一起打印。Big7
XX0*
target.txt
现在,标准 grep 可以在匹配后打印“多一行”,并且可以同时查找多个模式。该-f
选项采用文件的名称,该文件的行形成模式,因此您必须预处理文件XX0*
以使每个模式显示为单行。您可以通过将所有空格更改为换行符来做到这一点tr
。最简单的方法是使用进程替换将tr
to的输出grep
作为文件使用,但您也可以使用临时文件(或者甚至可以将输出通过管道传输到tr
to grep -f -
)
例如:
% grep -A1 -f <(tr ' ' '\n' < XX0000001 ) target.txt
#Big1
This_is_a_file_containing_xxxxx
--
#Big7
This_is_a_file_containing_xxxxx
--
#Big10
This_is_a_file_containing_xxxxx
当然,该模式Big1
也出现在该行上#Big10
,因此它是匹配的。 (但是您可以尝试-w
使用 grep 选项来请求全字匹配。)如果您想删除--
分隔符,您可以通过管道传输结果grep -ve --
。
其效率如何可能取决于您的 grep 实现,但作为一个为此目的而设计的工具,它可能比在 shell 脚本中执行相同的操作有更好的机会进行优化。 Shell 脚本速度很慢。如果所有模式均为 格式Big*
,明智的做法是仅查找公共部分一次。也许可以将模式列表更改为单个模式,Big(1|5|7|10)
希望它能更好地为正则表达式引擎工作。
答案2
您正在一个非常大的文件中搜索大量搜索词,您不太可能在 shell/标准工具中找到“快速”解决方案。也就是说,我认为你的方法效率特别低。
也许是这样的(未经测试):
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
for filename in XX* ; do
output_file="$output_folder/output_${filename}.txt"
grep -f "$filename" -Fx -A1 "$words_file" > "$output_file"
done
但坦率地说,我也不认为这会很快。
这可能是多线程的,这可能会提高吞吐量。
这是一个多线程替代方案。您需要将该threads
值调整为适合您的操作环境的值。
threads=4
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
find . -maxdepth 1 -type f -name 'XX*' -print0 | \
xargs -I% -0 -P$threads bash -c '
file="%"
output_file="$output_folder/output_${file#./}.txt"
grep -f "%" -Fx -A1 "$words_file" > "$output_file"
'
从中获得的任何好处都将取决于硬件因素,例如可用内存、CPU 核心数量、存储速度以及服务器上的其他活动。
答案3
您的问题不清楚,并且您没有提供任何预期的输出,这无助于澄清您的需求,并且无法测试潜在的解决方案以查看它是否有效,但这可能是您想要的,使用任何 awk :
awk '
FILENAME != ARGV[ARGC-1] {
for ( i=1; i<=NF; i++ ) {
words[$i]
}
next
}
f {
print
}
{
f = 0
for ( word in words ) {
if ( $0 ~ word ) {
print
f = 1
next
}
}
}
' some_folder/XX* target.txt
如果您的XX*
文件过多,ARG_MAX
则将其更改为:
printf '%s\n' some_folder/XX* |
awk '
FILENAME == "-" {
ARGV[ARGC++] = $0
next
}
FILENAME != ARGV[ARGC-1] {
for ( i=1; i<=NF; i++ ) {
words[$i]
}
next
}
f {
print
}
{
f = 0
for ( word in words ) {
if ( $0 ~ word ) {
print
f = 1
next
}
}
}
' - target.txt