我有两个CSV文件,使用@
来划分每列。第一个文件(file1.csv)有两列:
cat @ eats fish
spider @ eats insects
第二个文件(file2.csv)有四列:
info @ cat @ info @ info
info @ spider @ info @ info
info @ rabbit @ info @ info
如果第一个文件的第一列和第二个文件的第二列的详细信息匹配,我需要将第一个文件的第二列中的信息添加到第二个文件中的新列。 ,上述结果将成为:
info @ cat @ info @ info @ eats fish
info @ spider @ info @ info @ eats insects
info @ rabbit @ info @ info @
如上所示,由于第一个文件不包含有关兔子的信息,因此会在第二个文件的最后一行添加一个新的空列。
到目前为止,我知道该怎么做:
while read line
可用于遍历第二个文件中的行,例如:
while read line
do
(commands)
done < file2.csv
可以使用awk -F "@*" '{print $n}'
访问特定列的数据,其中n
是列号。
while read line
do
columntwo=$(echo $line | awk -F "@*" '{print $2})
while read line
do
columnone=$(echo $line | awk -F "@*" '{print $1})
if [ “$columnone” == “$columntwo” ]
then
(commands)
fi
done < file1.csv
done < file2.csv
我的方法似乎效率低下,我不确定如何使用file1.csv1
的第二列中的数据添加到file2.csv
中的新列。
file1.csv1
第1列中的项目和file2.csv
的第2列对这些文件是唯一的。这些文件中没有重复的条目。@
周围有空格,但如果这会导致脚本出现问题,我可以将其删除。如何将第一个文件中的数据添加到第二个文件中的数据?
答案 0 :(得分:5)
jowdder's answer几乎就在那里,但由于我在评论中提到的问题不完整:字段中会有不需要的空格,文件没有排序,这是他们需要的。
join -t@ -11 -22 -o2.1,0,2.3,2.4,1.2 <(sed 's/ *@ */@/g' file1.csv | sort -t@) <(sed 's/ *@ */@/g' file2.csv | sort -t@ -k2) | sed 's/@/ @ /g' > output-file
这也可以写成bash脚本,我将解释其中的每一步:
#!/bin/bash -e
# Remove whitespace around the `@`s, then sort using `@` to separate fields (-t@).
# -k2 tells sort to use the second field.
sed 's/ *@ */@/g' file1.csv | sort -t@ >temp-left
sed 's/ *@ */@/g' file2.csv | sort -t@ -k2 >temp-right
# Join the files. -t@ means break fields at @,
# -11 says use the first field in the first file, -22 is the second field in the second file.
# -o... controls the output format, 2.1=second file, first field; 0 is the join field.
join -t@ -11 -22 -o2.1,0,2.3,2.4,1.2 temp-left temp-right > temp-joined
# Add whitespace back in around the @s so it looks better.
sed 's/@/ @ /g' temp-joined >output-file
# Clean up temporary files
rm temp-{left,right,joined}
答案 1 :(得分:4)
一个不错的,干净的awk
解决方案:
awk -F" *@ *" 'NR==FNR{lines[$2]=$0} NR!=FNR{if(lines[$1])lines[$1]=lines[$1] " @ " $2} END{for(line in lines)print lines[line]}' file2.csv file1.csv
漂亮的单行。不是短暂的,但不是我见过的最长的。请注意,file2和file1已切换。同样,作为带有解释的脚本:
#!/usr/bin/awk -f
# Split fields on @ and the whitespace on either side.
BEGIN { FS = " *@ *" }
# First file
NR == FNR {
#Store the line
lines[$2] = $0
}
# Second file
NR != FNR {
# If the appropriate animal was in the first file, append its eating habits.
# If not, it's discarded; if you want something else, let me know.
if(lines[$1]) lines[$1] = lines[$1] " @ " $2
}
# After both files have been processed
END {
# Loop over all lines in the first file and print them, possibly updated with eating habits.
# No guarantees on order.
for(line in lines) print lines[line]
}
呼叫awk -f join.awk file2.csv file1.csv
,或制作可执行文件./join.awk file2.csv file1.csv
。
答案 2 :(得分:3)
这是POSIX的join
实用程序的用途。排序file1.csv
和file2.csv
(在第二个字段上对后者进行排序)后,运行以下内容:
join -2 2 -a 2 -t @ -e '' -o 2.1,0,2.3,2.4,1.2 file1.csv file2.csv
答案 3 :(得分:2)
这可能对您有用:
sed -e '1i\s/$/ @/' -e 's|^\([^@]*\)@\(.*\)|/^[^@]*@ \1/s/$/\2/|' file1.csv |
sed -f - file2.csv
info @ cat @ info @ info @ eats fish
info @ spider @ info @ info @ eats insects
info @ rabbit @ info @ info @
然而,在大卷上可能不是很快!
答案 4 :(得分:1)
编辑:在深入研究Text::CSV
(这是底层解析器/编写器引擎)的文档后,我找到了quote_space
选项,可防止空白存在触发引用该领域。在你的问题中,你说你可以允许删除@
字符周围的空格,这个方法会在这个过程中为你做这个,但如果这是可以接受的,那么这个答案现在应该满足所有标准。 / p>
以下是使用Perl和我的Tie::Array:CSV
的快速示例。此模块允许您像处理本机Perl 2D阵列一样处理CSV文件。
#!/usr/bin/env perl
use strict;
use warnings;
use Tie::Array::CSV;
use List::Util 'first';
my %opts = (
text_csv => {
sep_char => '@',
allow_whitespace => 1,
quote_space => 0,
},
);
tie my @file1, 'Tie::Array::CSV', 'file1.csv', %opts;
tie my @file2, 'Tie::Array::CSV', 'file2.csv', %opts;
foreach my $line (@file2) {
my $animal = $line->[1];
my $eats = first { $_->[0] eq $animal } @file1;
if ( $eats ) {
push @$line, $eats->[1];
} else {
push @$line, '';
}
}
根据file1.csv的大小,最好将整个文件解析到内存中以便更有效地进行搜索。
无论如何,这里是首先在file1.csv中解析的选项
#!/usr/bin/env perl
use strict;
use warnings;
use Tie::Array::CSV;
my %opts = (
text_csv => {
sep_char => '@',
allow_whitespace => 1,
quote_space => 0,
},
);
tie my @file1, 'Tie::Array::CSV', 'file1.csv', %opts;
tie my @file2, 'Tie::Array::CSV', 'file2.csv', %opts;
# parse in file1 so that it doesn't need to be searched each time
my %eats;
foreach my $line (@file1) {
$eats{$line->[0]} = $line->[1];
}
foreach my $line (@file2) {
my $animal = $line->[1];
push @$line, $eats{$animal} || '';
}
答案 5 :(得分:1)
另请参阅DBD :: CSV perl模块。它会将每个文件视为一个表,并允许您在其上编写SQL连接。 http://metacpan.org/pod/DBD::CSV
答案 6 :(得分:1)
你还没有说过为什么你必须在bash中这样做。使用像ruby,python或perl这样的全功能语言会更容易。这是一个简短的红宝石计划:
#!/usr/bin/env ruby
f1_map = Hash[ * IO.readlines('file1.csv').map {|l| l.chomp.split(/\s+@\s+/,2) }.flatten ]
STDIN.each_line do |l|
cols = l.chomp.split /\s+@\s+/
puts ( cols << f1_map[cols[1]] ).join(' @ ')
end
答案 7 :(得分:1)
我有一个solution based in a Ruby script可以从控制台执行。
我确定你可以对你的细节进行必要的调整,例如&#34; @&#34;作为字段分隔符。