如何使用awk从两个文件中找出公共列及其记录

how to find out common columns and its records from two files using awk

我有两个文件:

文件 1:

id|name|address|country
1|abc|efg|xyz
2|asd|dfg|uio

文件 2(仅 headers):

id|name|country

现在,我想要这样的输出:

输出:

id|name|country
1|abc|xyz
2|asd|uio

基本上,我有一个用户记录文件(file1)和一个header文件(file2)。现在,我只想从(file1)中提取那些列匹配的记录在 header 文件中。

我想使用 awk 或 bash。

我尝试使用:

awk 'BEGIN { OFS="..."} FNR==NR { a[(FNR"")] = [=16=]; next } { print a[(FNR"")], [=16=] > "test.txt"}' header.txt file.txt

不知道下一步该做什么。

谢谢

以下 awk 可能会对您有所帮助。

awk -F"|" 'FNR==NR{for(i=1;i<=NF;i++){a[$i]};next} FNR==1 && FNR!=NR{for(j=1;j<=NF;j++){if($j in a){b[++p]=j}}} {for(o=1;o<=p;o++){printf("%s%s",$b[o],o==p?ORS:OFS)}}' OFS="|" File2  File1

现在也添加了一种非线性形式的解决方案。

awk -F"|" '
FNR==NR{
   for(i=1;i<=NF;i++){
     a[$i]};
   next}
FNR==1 && FNR!=NR{
   for(j=1;j<=NF;j++){
     if($j in a){ b[++p]=j }}
}
{
   for(o=1;o<=p;o++){
     printf("%s%s",$b[o],o==p?ORS:OFS)}
}
' OFS="|" File2  File1

Ed Morton 编辑:FWIW 这是用普通 indenting/spacing 和几个更​​有意义的变量名编写的相同脚本:

BEGIN { FS=OFS="|" }
NR==FNR {
    for (i=1; i<=NF; i++) {
        names[$i]
    }
    next
}
FNR==1 {
    for (i=1; i<=NF; i++) {
        if ($i in names) {
            f[++numFlds] = i
        }
    }
}
{
    for (i=1; i<=numFlds; i++) {
        printf "%s%s", $(f[i]), (i<numFlds ? OFS : ORS)
    }
}

使用 bash>4 的解决方案:

IFS='|' headers1=($(head -n1 $file1))
IFS='|' headers2=($(head -n1 $file2))
IFS=$'\n'


# find idxes we want to output, ie. mapping of headers1 to headers2
idx=()
for i in $(seq 0 $((${#headers2[@]}-1))); do
        for j in $(seq 0 $((${#headers1[@]}-1))); do
                if [ "${headers2[$i]}" == "${headers1[$j]}" ]; then
                        idx+=($j)
                        break
                fi
        done
done
# idx=(0 1 3) for example

# simple join output function from 
join_by() { local IFS=""; shift; echo "$*"; }

# first line - output headers
join_by '|' "${headers2[@]}"

isfirst=true
while IFS='|' read -a vals; do
        # ignore first (header line)
        if $isfirst; then
                isfirst=false
                continue;
        fi;
        # filter from line only columns with idx indices
        tmp=()
        for i in ${idx[@]}; do 
             tmp+=("${vals[$i]}")
        done
        # join ouptut with '|'
        join_by '|' "${tmp[@]}"
done < $file1

这类似于RavinderSingh13's solution,它首先从较短的文件中读取headers,然后根据headers决定从较长的文件中保留哪些列在它的第一行。

然而,它的输出不同。如果不想包含特定字段,它不会构造字符串,而是将列向左移动。

BEGIN       { FS = OFS = "|" }

# read headers from first file
NR == FNR   { for (i = 1; i <= NF; ++i) header[$i]; next }

# mark fields in second file as "selected" if the header corresponds
# to a header in the first file
FNR == 1    {
    for (i = 1; i <= NF; ++i)
        select[i] = ($i in header)
}

{
    skip = 0
    pos  = 1
    for (i = 1; i <= NF; ++i)
        if (!select[i]) {          # we don't want this field
            ++skip
            $pos = $(pos + skip)   # shift fields left
        } else
            ++pos

    NF -= skip  # adjust number of fields
    print
}

运行这个:

$ mawk -f script.awk file2 file1
id|name|country
1|abc|xyz
2|asd|uio

这个遵循 file1 中的列顺序,更改了顺序:

$ cat file1
id|country|name

awk:

$ awk '
BEGIN { FS=OFS="|" }
NR==1 {                                             # file1
    n=split([=11=],a)
    next
}
NR==2 {                                             # file2 header
    for(i=1;i<=NF;i++)
        b[$i]=i
} 
{                                                   # output part
    for(i=1;i<=n;i++)
        printf "%s%s", $b[a[i]], (i==n?ORS:OFS)
}' file1 file2
id|country|name
1|xyz|abc
2|uio|asd

(另一个版本使用cut输出in revisions)

使用(很多)unix 管道作为 Doug McIlroy 的意图...

$ function p() { sed 1q "" | tr '|' '\n' | cat -n | sort -k2; }
$ cut -d'|' -f"$(join -j2 <(p header) <(p file) | sort -k2n | cut -d' ' -f3 | paste -sd,)" file

id|name|country
1|abc|xyz
2|asd|uio