bash add/append 来自其他文件的新列
bash add/append new columns from other files
我有一个一列的 name.txt 文件,例如
A
B
C
D
E
F
然后我有很多文件,例如x.txt、y.txt 和 z.txt
x.txt 有
A 1
C 3
D 2
y.txt 有
A 1
B 4
E 3
z.txt 有
B 2
D 2
F 1
理想的输出是(没有映射就填0)
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
用bash可以吗? (也许是 awk?)
非常感谢!!!
第一次编辑-我的尝试
因为我对 bash 很陌生,所以我真的很难用 awk 找到一个可能的解决方案。我对 R 比较熟悉,可以通过
namematrix[namematrix[,1]==xmatrix[,1],]
总而言之,我非常感谢下面帮助我了解更多关于 awk
和 join
的帮助!
第二次编辑 - 想出了一个超级有效的方法!
幸运的是,受到下面一些非常出色的答案的启发,我整理出了一种计算效率非常高的方法,如下所示。这可能对遇到类似问题的其他人有所帮助,尤其是当他们处理大量文件且大小非常大时。
先摸一个join_awk.bash
#!/bin/bash
join -oauto -e0 -a1 | awk '{print }'
例如,为 name.txt 和 x.txt
执行此 bash 脚本
join_awk.bash name.txt x.txt
会生成
1
0
3
2
0
0
请注意,这里我只保留第二列以节省磁盘 space,因为在我的数据集中,第一列是非常长的名称,会占用大量磁盘 space。
然后简单地实现
parallel join_awk.bash name.txt {} \> outdir/output.{} ::: {a,b,c}.txt
这是受到下面使用 GNU parallel and join 的精彩答案的启发。不同之处在于,由于其串行附加逻辑,下面的答案必须为 parallel
指定 j1
,这使得它不是真正的“并行”。而且,随着串行追加的进行,速度会越来越慢。相比之下,这里我们分别并行操作每个文件。当我们用多个 CPU 处理大量大文件时,它可以非常快。
最后通过
将所有单列输出文件合并在一起
cd outdir
paste output* > merged.txt
这也将非常快,因为 paste
本质上是并行的。
是的,你可以做到,是的,awk
是工具。使用数组和您的普通文件行号(FNR
文件记录数)和总行数(NR
记录)您可以将 names.txt
中的所有字母读入 a[]
数组,然后跟踪变量 fno
中的文件编号,您可以添加 x.txt
中的所有添加项,然后在处理下一个文件的第一行 (y.txt
) 之前,遍历在上一个文件中看到的所有字母,对于那些 没有看到的 放置一个 0
,然后继续正常处理。对每个附加文件重复此操作。
进一步逐行解释见评论:
awk '
FNR==NR { # first file
a[] = "" # fill array with letters as index
fno = 1 # set file number counter
next # get next record (line)
}
FNR == 1 { fno++ } # first line in file, increment file count
fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt)
for (i in a) # loop over letters
if (!(i in seen)) # if not in seen array
a[i] = a[i]" "0 # append 0
delete seen # delete seen array
}
in a { # if line begins with letter in array
a[] = a[]" " # append second field
seen[]++ # add letter to seen array
}
END {
for (i in a) # place zeros for last column
if (!(i in seen))
a[i] = a[i]" "0
for (i in a) # print results
print i a[i]
}' name.txt x.txt y.txt z.txt
例子Use/Output
只需复制以上内容,然后鼠标中键粘贴到包含您文件的当前目录的 xterm 中,您将收到:
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
创建自包含脚本
如果您想为 运行 创建一个脚本而不是在命令行中粘贴,您只需包含内容(不要用单引号引起来)然后使文件可执行。例如,您将解释器作为第一行,内容如下:
#!/usr/bin/awk -f
FNR==NR { # first file
a[] = "" # fill array with letters as index
fno = 1 # set file number counter
next # get next record (line)
}
FNR == 1 { fno++ } # first line in file, increment file count
fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt)
for (i in a) # loop over letters
if (!(i in seen)) # if not in seen array
a[i] = a[i]" "0 # append 0
delete seen # delete seen array
}
in a { # if line begins with letter in array
a[] = a[]" " # append second field
seen[]++ # add letter to seen array
}
END {
for (i in a) # place zeros for last column
if (!(i in seen))
a[i] = a[i]" "0
for (i in a) # print results
print i a[i]
}
awk
将按照给定的顺序处理作为参数给出的文件名。
例子Use/Output
使用脚本文件(我把它放在 names.awk
然后用 chmod +x names.awk
使它可执行),然后你会做:
$ ./names.awk name.txt x.txt y.txt z.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
如果您还有其他问题,请告诉我。
您可以使用这个 awk
:
awk 'NF == 2 {
map[FILENAME,] =
next
}
{
printf "%s",
for (f=1; f<ARGC-1; ++f)
printf "%s", OFS map[ARGV[f],]+0
print ""
}' {x,y,z}.txt name.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
和bash
怎么样:
#!/bin/bash
declare -A hash # use an associative array
for f in "x.txt" "y.txt" "z.txt"; do # loop over these files
while read -r key val; do # read key and val pairs
hash[$f,$key]=$val # assign the hash to val
done < "$f"
done
while read -r key; do
echo -n "$key" # print the 1st column
for f in "x.txt" "y.txt" "z.txt"; do # loop over the filenames
echo -n " ${hash[$f,$key]:-0}" # print the associated value or "0" if undefined
done
echo # put a newline
done < "name.txt"
再添加一种方法。您能否尝试使用显示的示例进行跟踪、编写和测试。恕我直言,应该可以在任何 awk
中工作,尽管我只有 3.1 版的 GNU awk
。这是非常简单和常用的方法,在第一个(主要)Input_file 的阅读中创建一个数组,然后在每个文件中添加 0
在特定 [=23] 中找不到该数组的任何元素=], 只用给定的小样本进行测试。
awk '
function checkArray(array){
for(i in array){
if(!(i in found)){ array[i]=array[i] OFS "0" }
}
}
FNR==NR{
arr[[=10=]]
next
}
foundCheck && FNR==1{
checkArray(arr)
delete found
foundCheck=""
}
{
if( in arr){
arr[]=(arr[] OFS )
found[]
foundCheck=1
next
}
}
END{
checkArray(arr)
for(key in arr){
print key,arr[key]
}
}
' name.txt x.txt y.txt z.txt
说明:为以上添加详细说明。
awk ' ##Starting awk program from here.
function checkArray(array){ ##Creating a function named checkArray from here.
for(i in array){ ##CTraversing through array here.
if(!(i in found)){ array[i]=array[i] OFS "0" } ##Checking condition if key is NOT in found then append a 0 in that specific value.
}
}
FNR==NR{ ##Checking condition if FNR==NR which will be TRUE when names.txt is being read.
arr[[=11=]] ##Creating array with name arr with index of current line.
next ##next will skip all further statements from here.
}
foundCheck && FNR==1{ ##Checking condition if foundCheck is SET and this is first line of Input_file.
checkArray(arr) ##Calling function checkArray by passing arr array name in it.
delete found ##Deleting found array to get rid of previous values.
foundCheck="" ##Nullifying foundCheck here.
}
{
if( in arr){ ##Checking condition if 1st field is present in arr.
arr[]=(arr[] OFS ) ##Appening 2nd field value to arr with index of .
found[] ##Adding 1st field to found as an index here.
foundCheck=1 ##Setting foundCheck here.
next ##next will skip all further statements from here.
}
}
END{ ##Starting END block of this program from here.
checkArray(arr) ##Calling function checkArray by passing arr array name in it.
for(key in arr){ ##Traversing thorugh arr here.
print key,arr[key] ##Printing index and its value here.
}
}
' name.txt x.txt y.txt z.txt ##Mentioning Input_file names here.
GNU awk
的另一种方法
$ cat script.awk
NF == 1 {
name[] =
for (i = 1; i < ARGC - 1; i++) {
name[] = name[] " 0"
}
next
}
{
name[] = gensub(/ ./, " " , ARGIND - 1, name[])
}
END {
for (k in name) {
print name[k]
}
}
调用脚本:
$ awk -f script.awk name.txt {x,y,z}.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
输出显示与 name.txt
相同的顺序,但我不认为所有类型的输入都是如此。
您可以使用join
join -a1 -e0 -o '0,2.2' name.txt x.txt | join -a1 -e0 -o '0,1.2,2.2' - y.txt | join -a1 -e0 -o '0,1.2,1.3,2.2' - z.txt
这可能适合您(GNU 并行和连接):
cp name.txt out && t=$(mktemp) &&
parallel -j1 join -oauto -e0 -a1 out {} \> $t \&\& mv $t out ::: {x,y,z}.txt
输出将在文件 out
.
中
我有一个一列的 name.txt 文件,例如
A
B
C
D
E
F
然后我有很多文件,例如x.txt、y.txt 和 z.txt
x.txt 有
A 1
C 3
D 2
y.txt 有
A 1
B 4
E 3
z.txt 有
B 2
D 2
F 1
理想的输出是(没有映射就填0)
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
用bash可以吗? (也许是 awk?)
非常感谢!!!
第一次编辑-我的尝试
因为我对 bash 很陌生,所以我真的很难用 awk 找到一个可能的解决方案。我对 R 比较熟悉,可以通过
namematrix[namematrix[,1]==xmatrix[,1],]
总而言之,我非常感谢下面帮助我了解更多关于 awk
和 join
的帮助!
第二次编辑 - 想出了一个超级有效的方法!
幸运的是,受到下面一些非常出色的答案的启发,我整理出了一种计算效率非常高的方法,如下所示。这可能对遇到类似问题的其他人有所帮助,尤其是当他们处理大量文件且大小非常大时。
先摸一个join_awk.bash
#!/bin/bash
join -oauto -e0 -a1 | awk '{print }'
例如,为 name.txt 和 x.txt
执行此 bash 脚本join_awk.bash name.txt x.txt
会生成
1
0
3
2
0
0
请注意,这里我只保留第二列以节省磁盘 space,因为在我的数据集中,第一列是非常长的名称,会占用大量磁盘 space。
然后简单地实现
parallel join_awk.bash name.txt {} \> outdir/output.{} ::: {a,b,c}.txt
这是受到下面使用 GNU parallel and join 的精彩答案的启发。不同之处在于,由于其串行附加逻辑,下面的答案必须为 parallel
指定 j1
,这使得它不是真正的“并行”。而且,随着串行追加的进行,速度会越来越慢。相比之下,这里我们分别并行操作每个文件。当我们用多个 CPU 处理大量大文件时,它可以非常快。
最后通过
将所有单列输出文件合并在一起cd outdir
paste output* > merged.txt
这也将非常快,因为 paste
本质上是并行的。
是的,你可以做到,是的,awk
是工具。使用数组和您的普通文件行号(FNR
文件记录数)和总行数(NR
记录)您可以将 names.txt
中的所有字母读入 a[]
数组,然后跟踪变量 fno
中的文件编号,您可以添加 x.txt
中的所有添加项,然后在处理下一个文件的第一行 (y.txt
) 之前,遍历在上一个文件中看到的所有字母,对于那些 没有看到的 放置一个 0
,然后继续正常处理。对每个附加文件重复此操作。
进一步逐行解释见评论:
awk '
FNR==NR { # first file
a[] = "" # fill array with letters as index
fno = 1 # set file number counter
next # get next record (line)
}
FNR == 1 { fno++ } # first line in file, increment file count
fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt)
for (i in a) # loop over letters
if (!(i in seen)) # if not in seen array
a[i] = a[i]" "0 # append 0
delete seen # delete seen array
}
in a { # if line begins with letter in array
a[] = a[]" " # append second field
seen[]++ # add letter to seen array
}
END {
for (i in a) # place zeros for last column
if (!(i in seen))
a[i] = a[i]" "0
for (i in a) # print results
print i a[i]
}' name.txt x.txt y.txt z.txt
例子Use/Output
只需复制以上内容,然后鼠标中键粘贴到包含您文件的当前目录的 xterm 中,您将收到:
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
创建自包含脚本
如果您想为 运行 创建一个脚本而不是在命令行中粘贴,您只需包含内容(不要用单引号引起来)然后使文件可执行。例如,您将解释器作为第一行,内容如下:
#!/usr/bin/awk -f
FNR==NR { # first file
a[] = "" # fill array with letters as index
fno = 1 # set file number counter
next # get next record (line)
}
FNR == 1 { fno++ } # first line in file, increment file count
fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt)
for (i in a) # loop over letters
if (!(i in seen)) # if not in seen array
a[i] = a[i]" "0 # append 0
delete seen # delete seen array
}
in a { # if line begins with letter in array
a[] = a[]" " # append second field
seen[]++ # add letter to seen array
}
END {
for (i in a) # place zeros for last column
if (!(i in seen))
a[i] = a[i]" "0
for (i in a) # print results
print i a[i]
}
awk
将按照给定的顺序处理作为参数给出的文件名。
例子Use/Output
使用脚本文件(我把它放在 names.awk
然后用 chmod +x names.awk
使它可执行),然后你会做:
$ ./names.awk name.txt x.txt y.txt z.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
如果您还有其他问题,请告诉我。
您可以使用这个 awk
:
awk 'NF == 2 {
map[FILENAME,] =
next
}
{
printf "%s",
for (f=1; f<ARGC-1; ++f)
printf "%s", OFS map[ARGV[f],]+0
print ""
}' {x,y,z}.txt name.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
和bash
怎么样:
#!/bin/bash
declare -A hash # use an associative array
for f in "x.txt" "y.txt" "z.txt"; do # loop over these files
while read -r key val; do # read key and val pairs
hash[$f,$key]=$val # assign the hash to val
done < "$f"
done
while read -r key; do
echo -n "$key" # print the 1st column
for f in "x.txt" "y.txt" "z.txt"; do # loop over the filenames
echo -n " ${hash[$f,$key]:-0}" # print the associated value or "0" if undefined
done
echo # put a newline
done < "name.txt"
再添加一种方法。您能否尝试使用显示的示例进行跟踪、编写和测试。恕我直言,应该可以在任何 awk
中工作,尽管我只有 3.1 版的 GNU awk
。这是非常简单和常用的方法,在第一个(主要)Input_file 的阅读中创建一个数组,然后在每个文件中添加 0
在特定 [=23] 中找不到该数组的任何元素=], 只用给定的小样本进行测试。
awk '
function checkArray(array){
for(i in array){
if(!(i in found)){ array[i]=array[i] OFS "0" }
}
}
FNR==NR{
arr[[=10=]]
next
}
foundCheck && FNR==1{
checkArray(arr)
delete found
foundCheck=""
}
{
if( in arr){
arr[]=(arr[] OFS )
found[]
foundCheck=1
next
}
}
END{
checkArray(arr)
for(key in arr){
print key,arr[key]
}
}
' name.txt x.txt y.txt z.txt
说明:为以上添加详细说明。
awk ' ##Starting awk program from here.
function checkArray(array){ ##Creating a function named checkArray from here.
for(i in array){ ##CTraversing through array here.
if(!(i in found)){ array[i]=array[i] OFS "0" } ##Checking condition if key is NOT in found then append a 0 in that specific value.
}
}
FNR==NR{ ##Checking condition if FNR==NR which will be TRUE when names.txt is being read.
arr[[=11=]] ##Creating array with name arr with index of current line.
next ##next will skip all further statements from here.
}
foundCheck && FNR==1{ ##Checking condition if foundCheck is SET and this is first line of Input_file.
checkArray(arr) ##Calling function checkArray by passing arr array name in it.
delete found ##Deleting found array to get rid of previous values.
foundCheck="" ##Nullifying foundCheck here.
}
{
if( in arr){ ##Checking condition if 1st field is present in arr.
arr[]=(arr[] OFS ) ##Appening 2nd field value to arr with index of .
found[] ##Adding 1st field to found as an index here.
foundCheck=1 ##Setting foundCheck here.
next ##next will skip all further statements from here.
}
}
END{ ##Starting END block of this program from here.
checkArray(arr) ##Calling function checkArray by passing arr array name in it.
for(key in arr){ ##Traversing thorugh arr here.
print key,arr[key] ##Printing index and its value here.
}
}
' name.txt x.txt y.txt z.txt ##Mentioning Input_file names here.
GNU awk
$ cat script.awk
NF == 1 {
name[] =
for (i = 1; i < ARGC - 1; i++) {
name[] = name[] " 0"
}
next
}
{
name[] = gensub(/ ./, " " , ARGIND - 1, name[])
}
END {
for (k in name) {
print name[k]
}
}
调用脚本:
$ awk -f script.awk name.txt {x,y,z}.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
输出显示与 name.txt
相同的顺序,但我不认为所有类型的输入都是如此。
您可以使用join
join -a1 -e0 -o '0,2.2' name.txt x.txt | join -a1 -e0 -o '0,1.2,2.2' - y.txt | join -a1 -e0 -o '0,1.2,1.3,2.2' - z.txt
这可能适合您(GNU 并行和连接):
cp name.txt out && t=$(mktemp) &&
parallel -j1 join -oauto -e0 -a1 out {} \> $t \&\& mv $t out ::: {x,y,z}.txt
输出将在文件 out
.