2009-10-25 25 views
12

Làm cách nào để sử dụng trình bao một lớp và các công cụ GNU phổ biến để nối các dòng trong hai tệp như trong sản phẩm Descartes? Cách gọn gàng, đẹp nhất và "linuxy" là gì?Sản phẩm Descartes của hai tập tin (dưới dạng tập hợp các dòng) trong GNU/Linux

Ví dụ, nếu tôi có hai tập tin:

$ cat file1 
a 
b 
$ cat file2 
c 
d 
e 

Kết quả sẽ được

a, c 
a, d 
a, e 
b, c 
b, d 
b, e 
+0

Ồ không, nó biến thành một cuộc thi ... –

+1

@C. Ross, không. Tôi đã có một tiêu chí rõ ràng và thể hiện không sử dụng perl, python, vv Và phần còn lại chỉ là một trận chiến bình thường để bảo trì. đơn giản và rõ ràng. –

Trả lời

13

Dưới đây là shell script để làm điều đó

while read a; do while read b; do echo "$a, $b"; done < file2; done < file1 

Mặc dù đó sẽ là khá chậm. Tôi không thể nghĩ ra bất kỳ logic biên dịch nào để thực hiện điều này. Bước tiếp theo cho tốc độ sẽ là thực hiện các bước trên trong awk/perl.

awk 'NR==FNR { a[$0]; next } { for (i in a) print i",", $0 }' file1 file2 

Hmm, giải pháp hacky này sử dụng logic biên dịch trước như thế nào?

paste -d, <(sed -n "$(yes 'p;' | head -n $(wc -l < file2))" file1) \ 
      <(cat $(yes 'file2' | head -n $(wc -l < file1))) 
+2

@Pixelbeat: phiên bản đầu tiên của bạn cần phải đảo ngược thứ tự của 'file1' và' file2'. (Đó là, nó nên được thực hiện Telemachus

+3

@Telemachus, thứ tự là không liên quan: nếu tôi nói "sản phẩm Descartes", tôi thực sự * có nghĩa là nó * –

+0

@HiteshPatel Tôi tin rằng điều này có thể hữu ích cho bạn. Thay đổi duy nhất bạn sẽ cần để làm cho câu trả lời 'while read a'-type hoạt động chính xác là thêm đối số' -r', làm cho nó 'trong khi đọc -ra; đọc -rb; do', vì nội dung của bạn có dấu gạch chéo ngược. (@pixelbeat, bạn có thể muốn chỉnh sửa các đối số đã nói vào câu trả lời thích hợp) –

2

Chỉnh sửa: Rất tiếc ... Xin lỗi, tôi nghĩ đây trăn đã được gắn thẻ ...

Nếu bạn có python 2.6:

from itertools import product 
print('\n'.join((', '.join(elt) for elt in (product(*((line.strip() for line in fh) for fh in (open('file1','r'), open('file2','r')))))))) 

a, c 
a, d 
a, e 
b, c 
b, d 
b, e 

Nếu bạn có python trước 2.6:

def product(*args, **kwds): 
    ''' 
    Source: http://docs.python.org/library/itertools.html#itertools.product 
    ''' 
    # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy 
    # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 
    pools = map(tuple, args) * kwds.get('repeat', 1) 
    result = [[]] 
    for pool in pools: 
     result = [x+[y] for x in result for y in pool] 
    for prod in result: 
     yield tuple(prod) 
print('\n'.join((', '.join(elt) for elt in (product(*((line.strip() for line in fh) for fh in (open('file1','r'), open('file2','r')))))))) 
+0

Điều đó sẽ hiệu quả, nhưng python không phải là những gì tôi đã yêu cầu. –

1

Giải pháp 1:

perl -e '{use File::Slurp; @f1 = read_file("file1"); @f2 = read_file("file2"); map { chomp; $v1 = $_; map { print "$v1,$_"; } @f2 } @f1;}'

+0

Tại sao bạn sử dụng 'bản đồ' ở đây? Chúng phải là các vòng lặp 'for'. –

+0

@Kinopiko: Bạn không chỉ phàn nàn về "cảnh sát ngôn ngữ" trên một chủ đề khác? – Telemachus

+0

Điều duy nhất tôi thích sử dụng nhiều hơn bản đồ là Biểu thức chính quy. :) – DVK

6

Cách cơ khí để làm điều đó trong vỏ, không sử dụng Perl hay Python, là:

while read line1 
do 
    while read line2 
    do echo "$line1, $line2" 
    done < file2 
done < file1 

Lệnh join đôi khi có thể được sử dụng cho các hoạt động này - tuy nhiên, tôi không rõ ràng nó có thể làm sản phẩm Descartes như một trường hợp thoái hóa.

Một bước lên từ vòng lặp kép sẽ là:

while read line1 
do 
    sed "s/^/$line1, /" file2 
done < file1 
+0

Tôi sẽ tìm giải pháp đầu tiên vì nó không làm cho các tệp trông giống như chúng khác nhau đáng kể. –

+0

Nó (giải pháp đầu tiên) có thể sẽ chậm hơn đáng kể - nhưng nó cũng sẽ miễn dịch với các ký tự lẻ (chẳng hạn như dấu gạch chéo) trong dữ liệu. Sửa chữa những thứ để không phải là một vấn đề là một chút fiddlier, và vào thời điểm đó bạn bắt đầu suy nghĩ về việc sử dụng Perl hoặc Python sau khi tất cả. –

+0

@Pavel - cảm ơn sự hỗ trợ biên tập. –

4

Sửa:

DVK nỗ lực 's cảm hứng cho tôi để làm điều này với eval:

script='1{x;d};${H;x;s/\n/\,/g;p;q};H' 
eval "echo {$(sed -n $script file1)}\,\ {$(sed -n $script file2)}$'\n'"|sed 's/^ //' 

Hoặc tập lệnh sed đơn giản hơn:

script=':a;N;${s/\n/,/g;b};ba' 

mà bạn sẽ sử dụng mà không cần chuyển đổi -n.

mang đến cho:

a, c 
a, d 
a, e 
b, c 
b, d 
b, e 

Original câu trả lời:

Trong Bash, bạn có thể làm điều này. Nó không đọc từ các tập tin, nhưng đó là một lừa gọn gàng:

$ echo {a,b}\,\ {c,d,e}$'\n' 
a, c 
a, d 
a, e 
b, c 
b, d 
b, e 

More đơn giản:

$ echo {a,b}{c,d,e} 
ac ad ae bc bd be 
+0

đẹp. nhưng tôi chắc chắn sẽ không muốn duy trì kịch bản này. :) – ghostdog74

+0

Thực sự thú vị nhưng không thể duy trì được. :) –

1
awk 'FNR==NR{ a[++d]=$1; next} 
{ 
    for (i=1;i<=d;i++){ 
    print $1","a[i] 
    } 
}' file2 file1 

# ./shell.sh 
a,c 
a,d 
a,e 
b,c 
b,d 
b,e 
1

OK, đây là nguồn gốc của giải pháp Dennis Williamson trên kể từ khi ông lưu ý rằng ông không không đọc từ file:

$ echo {`cat a | tr "\012" ","`}\,\ {`cat b | tr "\012" ","`}$'\n' 
a, c 
a, d 
a, e 
b, c 
b, d 
b, e 
+1

Đây là những gì mang lại cho tôi: '{a, b,}, {c, d, e,}' như một chuỗi chữ. –

1

Một giải pháp sử dụng join, awk và quá trình s ubstitution:

join <(xargs -I_ echo 1 _ < setA) <(xargs -I_ echo 1 _ < setB) 
    | awk '{ printf("%s, %s\n", $2, $3) }' 
+0

Nội dung của tệp "a" là gì? Một trong số đó có phải là một tệp khác không? AWK có thể được thay thế bằng 'cut -f2- -d '''. –

+0

Tệp "a" chứa tập hợp. Họ có thể khác nếu muốn. Tôi sẽ sửa nó! – yassin

+0

@ Dennis, 'cut' có lẽ tốt hơn, vì nó hoạt động ngay cả khi' setB' chứa các dòng có khoảng trắng. –

6

Tôi sẽ không giả vờ này là khá, nhưng ...

join -t, -j 9999 -o 2.1,1.1 /tmp/file1 /tmp/file2 

(cập nhật nhờ Iwan Aucamp dưới đây)

- tham gia (GNU coreutils) 8.4

+0

bạn có thể loại bỏ việc sử dụng cắt bằng cách thêm -o '2.1.1.1' (hoặc bất cứ cách nào bạn thích) –

3

hàm BASH đệ quy chung có thể giống như sau:

foreachline() { 

    _foreachline() { 

     if [ $# -lt 2 ]; then 
      printf "$1\n" 
      return 
     fi 

     local prefix=$1 
     local file=$2 
     shift 2 

     while read line; do 
      _foreachline "$prefix$line, " $* 
     done <$file 
    } 

    _foreachline "" $* 
} 

foreachline file1 file2 file3 

Trân trọng.

+2

Giải pháp này là duy nhất trong số các giải pháp ở chỗ nó giải quyết trường hợp tổng quát hơn của một tập hợp tùy ý các hoạt động sản phẩm Descartes. –

+0

Việc sử dụng '$ *' thay vì '" $ @ "' là không may, tuy nhiên; nó có nghĩa là bất kỳ '" * "' nào làm đối số sẽ được thay thế bằng danh sách tên tệp, ví dụ. –

4

Sẽ không có một dấu phẩy để tách nhưng chỉ join sử dụng:

$ join -j 2 file1 file2 
a c 
a d 
a e 
b c 
b d 
b e 
+0

'join -j 2 -o '1.1 2.1' -t ',' file1 file2' – Marcus

+0

@Marcus, có thể là đáng để chỉ ra rằng nếu bạn hạ cấp điều này thành một dấu tách đơn, tức là' -t, ', nó cũng sẽ làm việc với một số triển khai kết nối không phải GNU. Các điều khoản của OP sang một bên, cộng đồng rộng lớn hơn đánh giá cao tính di động trong câu trả lời. Chúng tôi không chạy Linux. :) – ghoti

Các vấn đề liên quan