Uni*_*and 6 shell duplicate text-processing
我需要写一个脚本:
这是系统生成文件的例行管理员“清理”所必需的,这些文件应该是唯一的,但由于用户错误而可能不是。
根据您上面的评论,并注意到我的测试数据与您的实际数据非常相似,我能够验证这是有效的:
grep -n '^ID.[^:-]*.[0-9][0-9]*$' |
sed -n 'h;s|\(.*\):6:\(ID.*\)|\2|p;g;s||\2:\1|p'
sort -u |
sed 's|ID..*:||'
Run Code Online (Sandbox Code Playgroud)
我grep的文件夹以ID和 其余的开头,并且因为它找到了多个匹配的文件,所以我要求匹配行-numbersgrep打印:
[filename]:[matching line number]:[IDmatch]
Run Code Online (Sandbox Code Playgroud)
我将它传递给sed在h旧缓冲区中保存该行副本的那个,然后检查该字符串:6:ID,如果找到,则删除该行上的所有内容,直到ID. 然后我p把结果打印出来。
之后,我g收回缓冲区 - 覆盖我在此过程中的最后编辑 - 并交换grep's 匹配行上的位置及其匹配的文件名。因此,对于grep第 6 行匹配的每一行打印,sed将其替换为:
[IDmatch]
[IDmatch]:[filename]
Run Code Online (Sandbox Code Playgroud)
当这个数据被传递给sort它时,它组织整个集合ID,因为我只要求它唯一的-u结果,它删除所有重复的IDmatch行,但保留以下IDmatch:filename行。下sed一条语句只是将其清理干净,呈现如下:
ID00000000
ID00000000:file00
ID00000000:file10
...
ID00000000:file80
ID00000001
ID00000001:file01
ID00000002
ID00000002:file02
...
Run Code Online (Sandbox Code Playgroud)
像这样:
ID00000000
file00
file10
...
file80
ID00000001
file01
ID00000002
file02
...
Run Code Online (Sandbox Code Playgroud)
但是,如果文件名包含ewline 字符,该解决方案将中断\n,尽管以下不会。我想出了如何将以下内容放入 shell 函数中,这样它就不必两次地球化了——我很快就会把它贴在这里。
for f in * ; do
sed '5!d;s|^|: "${'$((i=i+1))'}" |;q' "$f"
done |
sort -t' ' -k3 |
uniq -D -f2 |
sh -cx "$(cat)" -- * 2>&1
Run Code Online (Sandbox Code Playgroud)
应该这样做 - 只要您5将sed语句中的替换为您的 id 所在的任何行。我认为 - 如果我错了,请告诉我 - 这会处理所有情况。
对于目录中的每个文件,它会将数字加一并打印一行以字符串开头的行...
: "${[num]}" ...
Run Code Online (Sandbox Code Playgroud)
... where[num]是一个实际的整数,它刚刚增加了 1 并且...是您唯一的 id 行。
然后它首先将这些行输出到sort将<space>字符视为分隔符并仅对第三个字段中的数据进行排序。的|pipeline继续旁边uniq还限定上<space>和在比较它的输入并且仅打印跳过输入的前两个字段-Duplicate线。下一部分有点奇怪。
因此,我不必再循环一遍并找出哪个文件是哪个文件,而是[num]按照上面提到的方法进行了操作。当最后的shshell 进程|pipeline传递结果时,它只接收这些数字。但是它已经将它的位置参数设置为我们在递增这些数字时迭代的同一个 glob - 因此当它评估这些数字时,它会将它们与其位置数组中已有的文件相关联。这就是它所做的一切。
事实上 - 它甚至几乎没有做到这一点。每个位置参数前面都有:空命令。shell 进程所做的唯一一件事就是评估传递给它的变量——它从不执行一行代码。但是我将它设置为-x调试模式并将其重定向stderr到stdout以便打印所有文件名。
我这样做是因为它比担心奇怪的文件名破坏sort | uniq结果要容易得多。而且效果很好。
我使用以下方式生成的数据集对此进行了测试:
tr -dc '[:graph:]' </dev/urandom |
dd ibs=100 cbs=10 conv=unblock count=91 |
split -b110 --filter='
{ c=${FILE##%%*0} ; c=${c#file}
sed "5cID000000${c:-00}"
} >$FILE' -ed - file ; rm *90*
Run Code Online (Sandbox Code Playgroud)
请注意rm上面的字符串。我有点困了,并不太在意为什么file89生成的只有 102 字节而不是 110 字节作为其余的,所以我四舍五入到 90 年代,然后rmd 它。运行上面的意志RM文件名匹配在当前目录中水珠和覆盖任何文件file00- file89,但在一个委托测试目录使用时,是绝对安全的。
......其中包括......它对所有人都有效。
这会写入 90 个文件,file[0-8][1-9]每个文件以1-4,6-10 个 10 字节的随机数据行和每个文件的第 5 行的唯一 ID命名。它还产生file[0-8]0其中第 5 行总是ID00000000。
在此数据集上运行的顶部小函数的输出如下所示:
+ : file10 ID00000000
+ : file00 ID00000000
+ : file20 ID00000000
+ : file30 ID00000000
+ : file40 ID00000000
+ : file50 ID00000000
+ : file60 ID00000000
+ : file70 ID00000000
+ : file80 ID00000000
Run Code Online (Sandbox Code Playgroud)
如果由于某种原因您不喜欢+输出中的符号,只需更改$PS4最后一个 shell 进程即可。您在最后一行的开头添加它来处理:
PS4= sh ...
Run Code Online (Sandbox Code Playgroud)
但是,您也可以将其设置为任何字符串 - 如果您愿意,甚至可以将其设置为 shell 脚本的可执行位,它会根据需要将文件名分开。基本上,您可以将提示用作自动分隔符。并且最后一个 shell 进程的数组中仍然包含文件名 - 您可以根据自己的喜好添加命令来操作数据。
假设文件名没有空格或换行符,并且uniq支持该-D选项的 GNU可用,这真的很容易(更改后面的数字FNR==以更改标识符的行):
awk 'FNR==2 { print FILENAME,$0 }' * | sort -k 2 | uniq -Df 1 | cut -d ' ' -f 1
Run Code Online (Sandbox Code Playgroud)
如果没有-Dfor 选项uniq,事情很快就会变得更加复杂,一种方法是反转uniq -uusing的输出comm:
awk 'FNR==2 { print FILENAME,$0 }' * | sort >/tmp/sorted_keys
sort -k 2 /tmp/sorted_keys |
uniq -uf 1 | sort | comm -23 /tmp/sorted_keys - | cut -d ' ' -f 1
Run Code Online (Sandbox Code Playgroud)
要对具有任何名称的文件执行此操作,perl可能是最佳选择(更改第$.==1 行后的数字以更改标识符行):
perl -ne 'push(@{$table{$_}}, $ARGV) if $.==2;
$.=0 if eof;
END {
for my $val (values %table) {
print join( "\n", @{$val} ) . "\n" if @{$val} > 1;
}
}' *
Run Code Online (Sandbox Code Playgroud)
这个想法是通过在文件中找到的标识符来索引每个文件名,以便每个标识符都可以用于获取文件名数组。通过这种方式,可以轻松打印出具有多个元素的每个数组。
实际上可以在以下内容中使用与上述相同的方法awk:
awk 'FNR==2 {
i=table_sizes[$0]++;
table[$0,i]=FILENAME
}
END {
for (key in table_sizes) {
if (table_sizes[key] > 1) {
for (long_key in table) {
if ( index(long_key, key SUBSEP) == 1 ) {
print table[long_key]
delete table[long_key] # speed up next search
}
}
}
}
}' *
Run Code Online (Sandbox Code Playgroud)
唯一的问题是 的值是否SUBSEP出现在任何标识符中。通常SUBSEP是非打印字符 ( 0x1c),因此在大多数文本文件中这不会成为问题。它可以根据需要或改变的例子可以适用于实多维阵列(例如array[x][y],而不是array[x,y]在一个)awk支持他们像gawk。