Декартово произведение двух файлов (в виде наборов строк) в GNU/Linux
Как я могу использовать однострочные оболочки и общие инструменты GNU для конкатенации строк в двух файлах, как в декартовом продукте? Что является самым кратким, красивым и "linuxy" способом?
Например, если у меня есть два файла:
$ cat file1
a
b
$ cat file2
c
d
e
Результат должен быть
a, c
a, d
a, e
b, c
b, d
b, e
Ответы
Ответ 1
Здесь shell script, чтобы сделать это
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
Механический способ сделать это в оболочке, не используя 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
Ответ 3
Я не буду притворяться, что это красиво, но...
join -t, -j 9999 -o 2.1,1.1 /tmp/file1 /tmp/file2
(обновлено благодаря Iwan Aucamp ниже)
- join (GNU coreutils) 8.4
Ответ 4
Edit
попытка DVK вдохновила меня на это с помощью 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:
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
Ответ 5
общая рекурсивная функция 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
С уважением.
Ответ 6
Разделять запятую не будет, но использовать только join
:
$ join -j 2 file1 file2
a c
a d
a e
b c
b d
b e
Ответ 7
Изменить: Oops... Извините, я думал, что это был помечен python...
Если у вас есть 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
Если у вас есть python pre-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'))))))))
Ответ 8
Решение 1:
perl -e '{use File::Slurp; @f1 = read_file("file1"); @f2 = read_file("file2"); map { chomp; $v1 = $_; map { print "$v1,$_"; } @f2 } @f1;}'
Ответ 9
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
Ответ 10
ОК, это вывод решения Денниса Уильямсона выше, поскольку он отметил, что его не читает из файла:
$ echo {`cat a | tr "\012" ","`}\,\ {`cat b | tr "\012" ","`}$'\n'
a, c
a, d
a, e
b, c
b, d
b, e
Ответ 11
Решение с использованием join
, awk
и замещение процесса:
join <(xargs -I_ echo 1 _ < setA) <(xargs -I_ echo 1 _ < setB)
| awk '{ printf("%s, %s\n", $2, $3) }'