2009-10-25 2 views
12

Как я могу использовать однострочные оболочки и общие инструменты GNU для конкатенации строк в двух файлах, как в декартовом продукте? Что является самым кратким, красивым и «linuxy» способом?Декартовое произведение двух файлов (в виде наборов строк) в GNU/Linux

Например, если у меня есть два файла:

$ cat file1 
a 
b 
$ cat file2 
c 
d 
e 

Результат должен быть

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

О нет, это превратилось в соревнование ... –

+1

@c. Росс, это не так. У меня был определенный и выраженный критерий не использования perl, python и т. Д. И остальное было всего лишь обычной битвой за ремонтопригодность. простота и ясность. –

ответ

13

Вот скрипт, чтобы сделать это

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

Хотя это будет довольно медленно. Я не могу думать о какой-либо прекомпилированной логике для достижения этой цели. Следующим шагом для скорости было бы сделать выше в awk/perl.

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

Хм, как насчет этого хакерского решения для использования прекомпилированной логики?

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

@Pixelbeat: вашей первой версии необходимо изменить порядок 'file1' и' file2'. (То есть, это должно быть сделано «done Telemachus

+3

@Telemachus, порядок не имеет значения: если я говорю «декартово произведение», я действительно * его *. –

+0

@HiteshPatel , Я считаю, что это может быть полезно для вас. Единственное изменение, которое вам нужно, чтобы корректный ответ 'while read a'-type работал правильно, заключается в добавлении аргумента' -r', что делает его 'while read -ra; read -rb; do', так как ваш контент имеет буквальную обратную косую черту (@pixelbeat, вы можете отредактировать указанные аргументы в правильном ответе). –

2

Edit: Упс ... Извините, я думал, что это была помечена питон ...

Если у вас есть 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 

Если у вас есть питон предварительно 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

Это сработает, но питон не то, о чем я просил. –

1

Решение 1:

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

+0

Почему вы используете 'map' здесь? Это должны быть «за» петли. – 2009-10-25 14:05:37

+0

@ Kinopiko: Разве вы не просто жаловались на «языковую полицию» на другую тему? – Telemachus

+0

Единственное, что мне нравится использовать больше, чем карты, это регулярные выражения. :) – DVK

6

Механический способ сделать это в оболочке, а не с помощью Perl или Python, является:

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

Команда join иногда могут быть использованы для этих операций - однако, я не ясно, что он может делать декартовую продукцию как вырожденный случай.

Один шаг от двойного цикла будет:

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

Я бы выбрал первое решение, потому что оно не делает файлы похожими они существенно отличаются. –

+0

Это (первое решение), вероятно, будет значительно медленнее - но оно также будет невосприимчивым к нечетным символам (например, косерам) в данных. Исправление вещей, так что это не проблема, это немного странно, и в этот момент вы начинаете думать об использовании Perl или Python в конце концов. –

+0

@Pavel - спасибо за редакционную помощь. –

4

Edit:

ДВК попытка «s вдохновила меня сделать это с 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/^ //' 

Или более простой сценарий sed:

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

который вы бы использовали без переключателя -n.

, который дает:

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

Оригинальный ответ:

В Bash, вы можете сделать это. Он не читает из файлов, но это ловкий трюк:

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

Более просто:

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

приятно. но я уверен, что не захочет поддерживать этот скрипт. :) – ghostdog74

+0

Поистине восхитительный, но неподъемный. :) –

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

ОК, это вывод решения Денниса Уильямсона выше, так как он отметил, что его делает не считывается из файла:

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

Это то, что дает мне: '{a, b,}, {c, d, e,}' как литеральную строку. –

1

раствор с помощью join, awk и процесс ов ubstitution:

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

Какое содержимое файла "a"? Должен ли один из них быть другим файлом? Вероятно, AWK можно было заменить на 'cut -f2- -d '''. –

+0

Файл «a» содержит набор. Они могут быть разными, если они захотят. Я исправлю это! – yassin

+0

@ Dennis, 'cut', вероятно, лучше, поскольку он работает, даже если' setB' содержит строки с пробелами. –

6

Я не буду делать вид, что это красиво, но ...

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

(обновлено благодаря Iwan Aucamp ниже)

- присоединиться (GNU Coreutils) 8,4

+0

вы можете исключить использование разреза, добавив -o '2.1.1.1' (или в зависимости от того, как вам понравится) –

3

родовая рекурсивная функция BASH может быть что-то вроде этого:

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 

С уважением.

+2

Это решение является уникальным среди этих решений тем, что оно решает более общий случай произвольного набора декартовых операций продукта. –

+0

Однако использование '$ *', а не '' $ @ "' является неудачным; это означает, что любой '' * "' как аргумент будет заменен, например, списком имен файлов. –

4

Там не будет запятой для разделения, но с использованием только join:

$ 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, может быть, стоит отметить, что если вы понизите его до одного разделителя, то есть' -t, ', он также будет работайте с рядом реализаций присоединения к GNU. Оговорка OP в стороне, более широкое сообщество ценит переносимость ответов. Мы все не запускаем Linux. :) – ghoti

Смежные вопросы