5

@各列を分割するために使用する2つのCSVファイルがあります。最初のファイル (file1.csv) には 2 つの列があります。

cat @ eats fish
spider @ eats insects

2 番目のファイル (file2.csv) には 4 つの列があります。

info @ cat @ info @ info
info @ spider @ info @ info
info @ rabbit @ info @ info

最初のファイルの最初の列と 2 番目のファイルの 2 番目の列の詳細が一致する場合、最初のファイルの 2 列目の情報を 2 番目のファイルの新しい列に追加する必要があります。上記の場合、次のようになります。

info @ cat @ info @ info @ eats fish
info @ spider @ info @ info @ eats insects
info @ rabbit @ info @ info @

上記のように、最初のファイルにはウサギに関する情報が含まれていなかったため、2 番目のファイルの最後の行に新しい空の列が追加されています。

これまでのところ、私が知っている方法は次のとおりです。

while read line2 番目のファイルの行を循環するために使用できます。

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

私のアプローチは非効率的で、 の 2 列目のデータをfile1.csv1の新しい列に追加する方法がわかりませんfile2.csv

  • file1.csv1の列 1と列 2 の項目は、file2.csvそれらのファイルに固有です。これらのファイル内に重複するエントリはありません。
  • 結果のファイルには、一部の列が空の場合でも、各行に正確に 5 列が含まれている必要があります。
  • このファイルには、さまざまな言語の文字が UTF-8 で多数含まれています。
  • の周りに空白があります@が、これがスクリプトに問題を引き起こす場合は、これを削除できます。

最初のファイルのデータを 2 番目のファイルのデータに追加するにはどうすればよいですか?

4

8 に答える 8

5

そして、すてきできれいな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]
}

as を呼び出すawk -f join.awk file2.csv file1.csvか、実行可能にして./join.awk file2.csv file1.csv.

于 2012-04-06T04:04:24.137 に答える
5

jowdder の答えはほとんどそこにありますが、そこのコメントで言及した問題のために不完全です。フィールドに不要な空白があり、ファイルがソートされていない必要があります。

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}
于 2012-04-06T03:22:57.213 に答える
3

これが POSIX のjoinユーティリティの目的です。並べ替え後(後者file1.csvfile2.csv2 番目のフィールドで並べ替え)、次の行に沿って何かを実行します。

join -2 2 -a 2 -t @ -e '' -o 2.1,0,2.3,2.4,1.2 file1.csv file2.csv
于 2012-04-06T01:26:49.127 に答える
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 @

ただし、大量の場合はそれほど高速ではない可能性があります!

于 2012-04-06T08:15:36.467 に答える
1

編集:(Text::CSV基礎となるパーサー/ライターエンジンである)のドキュメントを掘り下げた後quote_space、空白の存在がフィールドの引用をトリガーするのを防ぐオプションを見つけました。あなたの質問では、文字の周りの空白の削除を許可できると言っていますが@、このメソッドはプロセスでそれを行いますが、それが受け入れられる場合、この回答はすべての基準を満たすはずです.

Perl と my を使用した簡単な例を次に示しTie::Array:CSVます。このモジュールを使用すると、CSV ファイルをネイティブの Perl 2D 配列と同じように扱うことができます。

#!/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} || '';
}
于 2012-04-06T01:37:13.777 に答える