我该如何使用shell一行命令和常用的GNU工具来像笛卡尔积一样连接两个文件中的行?最简洁、美观和“Linux风格”的方法是什么?
例如,如果我有两个文件:
$ cat file1
a
b
$ cat file2
c
d
e
结果应该是:a, c
a, d
a, e
b, c
b, d
b, e
我该如何使用shell一行命令和常用的GNU工具来像笛卡尔积一样连接两个文件中的行?最简洁、美观和“Linux风格”的方法是什么?
例如,如果我有两个文件:
$ cat file1
a
b
$ cat file2
c
d
e
结果应该是:a, c
a, d
a, e
b, c
b, d
b, e
以下是用Shell脚本实现的方法
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
嗯,使用预编译的逻辑,这个hacky(不太优美但实用的)解决方案怎么样?
paste -d, <(sed -n "$(yes 'p;' | head -n $(wc -l < file2))" file1) \
<(cat $(yes 'file2' | head -n $(wc -l < file1)))
file1
和file2
的顺序。(也就是说,应该使用done < file2; done < file1
才能得到所需的结果。) - Telemachuswhile read a
类型的答案正确运行所需添加-r
参数,使其变为while read -r a; do while read -r b; do
,因为你的内容包含了字面上的反斜杠。(@pixelbeat,您可能需要将这些参数编辑到答案中)。 - Charles Duffy使用join
方法,不需要逗号分隔:
$ join -j 2 file1 file2
a c
a d
a e
b c
b d
b e
join -j 2 -o '1.1 2.1' -t ', ' file1 file2
- Marcusjoin -t, -j 9999 -o 2.1,1.1 /tmp/file1 /tmp/file2
(感谢下面的Iwan Aucamp更新)
--join(GNU coreutils)8.4
使用shell机械的方式来做,不使用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
编辑:
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=':a;N;${s/\n/,/g;b};ba'
如果不使用-n
开关,则会使用它。
这将产生:
a, c
a, d
a, e
b, c
b, d
b, e
Translated answer:
在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
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
祝好。
$*
而不是"$@"
是不幸的,因为这意味着任何作为参数的"*"
都将被替换为文件名列表。 - Charles Duffy编辑:哎呀...对不起,我以为这是标记了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版本在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'))))))))
使用join
、awk
和进程替换的解决方案:
join <(xargs -I_ echo 1 _ < setA) <(xargs -I_ echo 1 _ < setB)
| awk '{ printf("%s, %s\n", $2, $3) }'
cut -f2- -d' '
替换。 - Dennis Williamsoncut
可能更好,因为它可以处理setB
中包含空格的行。 - P Shved解决方案1:
perl -e '{use File::Slurp; @f1 = read_file("file1"); @f2 = read_file("file2"); map { chomp; $v1 = $_; map { print "$v1,$_"; } @f2 } @f1;}'
map
?那些应该是 for
循环。 - user181548awk '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