我希望您通过删除具有相同值的列来修剪文件。
# the file I have (tab-delimited, millions of columns)
jack 1 5 9
john 3 5 0
lisa 4 5 7
# the file I want (remove the columns with the same value in all lines)
jack 1 9
john 3 0
lisa 4 7
你能否就这个问题给我任何指示?我更喜欢sed或awk解决方案,或者可能是perl解决方案。
提前致谢。 最好,
答案 0 :(得分:5)
这是一个快速的perl脚本,用于确定哪些列可以剪切。
open FH, "file" or die $!;
my @baseline = split /\t/,<FH>; #snag the first row
my @linemap = 0..$#baseline; #list all equivalent columns (all of them)
while(<FH>) { #loop over the file
my @line = split /\t/;
@linemap = grep {$baseline[$_] eq $line[$_]} @linemap; #filter out any that aren't equal
}
print join " ", @linemap;
print "\n";
您可以使用上述许多建议来实际删除列。我最喜欢的可能是cut实现,部分原因是上面的perl脚本可以修改为你提供精确的命令(甚至可以为你运行)。
@linemap = map {$_+1} @linemap; #Cut is 1-index based
print "cut --complement -f ".join(",",@linemap)." file\n";
答案 1 :(得分:3)
#!/usr/bin/perl
$/="\t";
open(R,"<","/tmp/filename") || die;
while (<R>)
{
next if (($. % 4) == 3);
print;
}
嗯,这是假设它是第三列。如果是按值:
#!/usr/bin/perl
$/="\t";
open(R,"<","/tmp/filename") || die;
while (<R>)
{
next if (($_ == 5);
print;
}
通过问题编辑,OP的愿望变得清晰。怎么样:
#!/usr/bin/perl
open(R,"<","/tmp/filename") || die;
my $first = 1;
my (@cols);
while (<R>)
{
my (@this) = split(/\t/);
if ($. == 1)
{
@cols = @this;
}
else
{
for(my $x=0;$x<=$#cols;$x++)
{
if (defined($cols[$x]) && !($cols[$x] ~~ $this[$x]))
{
$cols[$x] = undef;
}
}
}
next if (($_ == 5));
# print;
}
close(R);
my(@del);
print "Deleting columns: ";
for(my $x=0;$x<=$#cols;$x++)
{
if (defined($cols[$x]))
{
print "$x ($cols[$x]), ";
push(@del,$x-int(@del));
}
}
print "\n";
open(R,"<","/tmp/filename") || die;
while (<R>)
{
chomp;
my (@this) = split(/\t/);
foreach my $col (@del)
{
splice(@this,$col,1);
}
print join("\t",@this)."\n";
}
close(R);
答案 2 :(得分:3)
如果您知道要提前剥离哪一列,那么cut
会有所帮助:
cut --complement -d' ' -f 3 filename
答案 3 :(得分:2)
据我所知,您希望浏览每一行并检查某些列中的值是否没有差异,然后在这种情况下您可以删除该列。
如果是这种情况,我有一个建议,但没有现成的脚本,但我认为你将能够弄明白。你应该看看cut
。它提取部分线条。您可以使用它来提取第一列,然后对输出的数据运行uniq
,然后如果在唯一后只有一个值,则表示该列中的所有值都相同。这样,您可以收集没有差异的列数。您将需要shell脚本来查看您提交的列数(我猜是使用head -n 1
并计算分隔符数)并在每个列上运行此类过程,将列号存储在数组中,然后在最后制作切割线到删除不感兴趣的列。虽然它不是awk或perl但应该工作,并且只使用传统的Unix工具。那么你可以在perl脚本中使用它们,如果你想:)
好吧,如果我误解了这个问题可能会被削减仍然有用:)它似乎是鲜为人知的工具之一。
答案 4 :(得分:1)
据我所知,你需要让它成为一个多程序程序,以满足你的需求而不会耗费内存。对于初学者,将文件的一行加载到数组中。
open FH,'datafile.txt' or die "$!";
my @mask;
my @first_line= split(/\s+/,<FH>);
然后你要按顺序读入其他行
while(my @next_line= split(/\s+/,<FH>)) {
/* compare each member of @first_line to @next_line
* any match, make a mark in mask to true
*/
当你到达文件的底部时,回到顶部并使用蒙版来确定要打印的列。
答案 5 :(得分:1)
您可以选择要剪切的列
# using bash/awk
# I had used 1000000 here, as you had written millions of columns but you should adjust it
for cols in `seq 2 1000000` ; do
cut -d DELIMITER -f $cols FILE | awk -v c=$cols '{s+=$0} END {if (s/NR==$0) {printf("%i,",c)}}'
done | sed 's/,$//' > tmplist
cut --complement -d DELIMITER -f `cat tmplist` FILE
但它可能真的很慢,因为它没有优化,并且多次读取文件...所以要小心大文件。
或者您可以使用awk读取整个文件一次并选择可转储列,然后使用cut。
cut --complement -d DELIMITER -f `awk '{for (i=1;i<=NF;i++) {sums[i]+=$i}} END {for (i=1;i<=NF; i++) {if (sums[i]/NR==$i) {printf("%i,",c)}}}' FILE | sed 's/,$//'` FILE
HTH
答案 6 :(得分:1)
尚未完全测试,但这似乎适用于所提供的测试集,请注意它会破坏原始文件......
#!/bin/bash
#change 4 below to match number of columns
for i in {2..4}; do
cut -f $i input | sort | uniq -c > tmp
while read a b; do
if [ $a -ge 2 ]; then
awk -vfield=$i '{$field="_";print}' input > tmp2
$(mv tmp2 input)
fi
done < tmp
done
$ cat input
jack 1 5 9
john 3 5 0
lisa 4 5 7
$ ./cnt.sh
$ cat input
jack 1 _ 9
john 3 _ 0
lisa 4 _ 7
使用_
使输出更清晰......
答案 7 :(得分:1)
这里的主要问题是你说“数百万列”,并没有指定多少行。为了检查每一行中的每个值与其他列中的对应值...您正在查看大量检查。
当然,您可以随时减少列数,但仍然需要检查每一列到最后一行。所以......很多处理。
我们可以从两个第一行开始制作“种子”哈希:
use strict;
use warnings;
open my $fh, '<', "inputfile.txt" or die;
my %matches;
my $line = <$fh>;
my $nextline = <$fh>;
my $i=0;
while ($line =~ s/\t(\d+)//) {
my $num1 = $1;
if ($nextline =~ s/\t(\d+)//) {
if ($1 == $num1) { $matches{$i} = $num1 }
} else {
die "Mismatched line at line $.";
}
$i++;
}
然后使用此“种子”哈希,您可以读取其余行,并从哈希中删除不匹配的值,例如:
while($line = <$fh>) {
my $i = 0;
while ($line =~ s/\t(\d+)//) {
if (defined $matches{$i}) {
$matches{$i} = undef if ($matches{$i} != $1);
}
$i++;
}
}
可以想象一个解决方案,其中一个已经被证明是唯一的所有行都被剥离了,但为了做到这一点,你需要创建一个行的数组,或者制作一个正则表达式,我不确定仅仅通过字符串就不会花费同样的时间。
然后,在处理完所有行之后,你会得到一个带有重复数字值的哈希值,这样你就可以重新打开文件,然后进行打印:
open my $fh, '<', "inputfile.txt" or die;
open my $outfile, '>', "outfile.txt" or die;
while ($line = <$fh>) {
my $i = 0;
if ($line =~ s/^([^\t]+)(?=\t)//) {
print $outfile $1;
} else { warn "Missing header at line $.\n"; }
while ($line =~ s/(\t\d+)//) {
if (defined $matches{$i}) { print $1 }
$i++;
}
print "\n";
}
这是一项相当繁重的操作,此代码未经测试。这将为您提供解决方案的提示,处理整个文件可能需要一段时间。我建议运行一些测试,看看它是否适用于您的数据,然后进行调整。
如果你只有几个匹配的列,那么简单地从行中提取它们要容易得多,但是我在这么长的行上犹豫了split
。类似的东西:
while ($line = <$fh>) {
my @line = split /\t/, $line;
for my $key (sort { $b <=> $a } keys %matches) {
splice @line, $key + 1, 1;
}
$line = join ("\t", @line);
$line =~ s/\n*$/\n/; # awkward way to make sure to get a single newline
print $outfile $line;
}
请注意,我们必须按照数字降序对键进行排序,以便我们从末尾修剪值。否则,我们搞砸了后续数组编号的唯一性。
无论如何,这可能是一种方法。不过,这是一个相当大的操作。我会保留备份。 ;)