【问题标题】:How can I merge two files by column with awk?如何使用 awk 按列合并两个文件?
【发布时间】:2020-07-07 13:03:55
【问题描述】:

我有以下两个文本文件:

文件1

-7.7
-7.4
-7.3
-7.3
-7.3

文件2

4.823
5.472
5.856
4.770
4.425

我想将它们并排合并,用逗号分隔:

文件3

-7.7,4.823
-7.4,5.472
-7.3,5.856
-7.3,4.770
-7.3,4.425

我知道这可以通过paste -d ',' file1 file2 > file3 轻松完成,但我想要一个允许我控制每次迭代的解决方案,因为我的数据集很大,而且我还需要在输出文件中添加其他列。例如:

A,-7.7,4.823,3
A,-7.4,5.472,2
B,-7.3,5.856,3
A,-7.3,4.770,1
B,-7.3,4.425,1

这是我目前得到的:

awk 'NR==FNR {a[$count]=$1; count+=1; next} {print a[$count] "," $1; count+=1;}' file1 file2 > file3

输出:

-7.3,4.823
-7.3,5.472
-7.3,5.856
-7.3,4.770
-7.3,4.425

我是 bash 和 awk 的新手,因此不胜感激:)

编辑:
假设我有一个包含成对文件的目录,以两个扩展名结尾:.ext1 和 .ext2。这些文件的名称中包含参数,例如 file_0_par1_par2.ext1 有其对 file_0_par1_par2.ext2。每个文件包含 5 个值。我有一个函数可以从它的名称中提取它的序列号和它的参数。我的目标是在单个 csv 文件 (file_out.csv) 上写入文件中存在的值以及从它们的名称中提取的参数。
代码:

for file1 in *.ext1 ; do
    for file2 in *.ext2 ; do
        # for each file ending with .ext2, verify if it is file1's corresponding pair
        # I know this is extremely time inefficient, since it's a O(n^2) operation, but I couldn't find another alternative
        if [[ "${file1%.*}" == "${file2%.*}" ]] ; then
            # extract file_number, and par1, par2 based on some conditions, then append to the csv file
            paste -d ',' "$file1" "$file2" | while IFS="," read -r var1 var2;
            do
                echo "$par1,$par2,$var1,$var2,$file_number" >> "file_out.csv" 
            done
        fi
    done
done

【问题讨论】:

标签: bash csv awk


【解决方案1】:

有效执行更新后问题描述的方法:

假设我有一个包含成对文件的目录,以两个结尾 扩展名:.ext1 和 .ext2。这些文件的参数包含在 他们的名字,例如 file_0_par1_par2.ext1 有它的对, 文件_0_par1_par2.ext2。每个文件包含 5 个值。我有一个功能 从名称中提取其序列号和参数。我的目标 是在单个 csv 文件 (file_out.csv) 上写入存在的值 在文件中以及从它们的名称中提取的参数。

for file1 in *.ext1 ; do
    for file2 in *.ext2 ; do
        # for each file ending with .ext2, verify if it is file1's corresponding pair
        # I know this is extremely time inefficient, since it's a O(n^2) operation, but I couldn't find another alternative
        if [[ "${file1%.*}" == "${file2%.*}" ]] ; then
            # extract file_number, and par1, par2 based on some conditions, then append to the csv file
            paste -d ',' "$file1" "$file2" | while IFS="," read -r var1 var2;
            do
                echo "$par1,$par2,$var1,$var2,$file_number" >> "file_out.csv" 
            done
        fi
    done
done

将(未经测试):

for file1 in *.ext1; do
    base="${file1%.*}"
    file2="${base}.ext2"
    paste -d ',' "$file1" "$file2" |
    awk -v base="$base" '
        BEGIN { split(base,b,/_/); FS=OFS="," }
        { print b[3], b[4], $1, $2, b[2] }
    '
done > 'file_out.csv'

执行base="${file1%.*}"; file2="${base}.ext2" 本身将比for file2 in *.ext2 ; do if [[ "${file1%.*}" == "${file2%.*}" ]] ; then 高效N^2 倍(给定N 对文件),执行| awk '...' 本身将比| while IFS="," read -r var1 var2; do echo ...; done 高效一个数量级(参见why-is-using-a-shell-loop-to-process-text-considered-bad-practice ),因此您可以期待看到与现有脚本相比性能有了巨大提升。

【讨论】:

  • 当您已经在使用awk 时,您还在使用paste 吗?也许从IFS=_ read -r _ _ par1 par2 <<< ${file1%.ext1}; 开始,但这也可以在awk 中完成。
  • @Walter 因为paste | awk 可能比首先将所有 file1s 值存储在内存中的 awk 数组中更有效,如果文件很大,则 paste 将根据需要通过分页来处理它,而 awk 会只是失败了,编写的代码更少,编写的代码也稍微简单一些。我已经在 awk 中使用 split() 更简单有效地完成最后一部分。
【解决方案2】:

您可以将您的解决方案与“粘贴”一起使用。只需添加 while 循环,例如 控制每次迭代。

paste -d ',' file1 file2 | while IFS="," read -r lineA lineB;
do
    # you can build new file here like you need
    echo "$lineA,$lineB"
done

【讨论】:

    【解决方案3】:

    您的命令失败:

    awk 'NR==FNR {a[$count]=$1; count+=1; next} {print a[$count] "," $1; count+=1;}' file1 file2 > file3
    

    不要使用$count,而是count,从计数1开始,在file2开始时将计数重置为1。最后两个条件可以通过FNR==1 {count=1}{count=FNR}添加。
    count 总是与FNR 相同时,为什么要使用count

    awk 'NR==FNR {a[FNR]=$1; next} {print a[FNR] "," $1; }' file1 file2
    

    【讨论】:

      【解决方案4】:
      awk 'BEGIN {FS=","} {getline file2_line < "file2.txt"; print $1","file2_line }' file1.txt
      

      begin 块将字段分隔符设置为逗号,但这仅适用于 file1.txt 中的数据

      脚本主体中的第一条语句将 file2.txt 中这一行的值存储到名为 file2_line 的变量中。此变量包含 file2.txt 中的 整个 行,并且该行中的数据不会以通常的方式拆分为字段。这意味着如果 file2.txt 也是逗号分隔的,您可能希望使用 awk 的 split 函数将字符串拆分为一个数组,以便处理各个字段。

      在 awk 中,通过简单地一个接一个地写入字符串值来连接,因此print $1","file2_line 写入第一个文件中的第一个字段,一个文字逗号,以及我们之前存储的 file2.txt 的这一行的字符串值。

      【讨论】:

      • 如果您正在考虑使用 getline,请参阅 awk.freeshell.org/AllAboutGetline 了解注意事项和调用方法。此外,您发布的解决方案应使用OFS=","print $0, file2_line 而不是FS=","print $1","file2_line。声明The begin block sets the field separator to comma, but this only applies to data in file1.txt 是错误的。
      • OFS="," 是一个很好的建议,但字段分隔符仅适用于 not 使用 getline 打开的文件并没有错。它直接来自手册。 “在这个版本的 getline 中,预定义的变量都没有改变,记录也没有被分割成字段。” gnu.org/software/gawk/manual/html_node/…
      • 你的说法是错误的(但手册是正确的),字段分隔符适用于所有文件,而不仅仅是file1.txt中的数据。您看不到它的效果的原因是您没有进行字段拆分,因为您的 getline 正在填充变量而不是填充 $0,因此您没有使用 FSs 值。我现在想我明白你想说什么(比如“从 file2.txt 读取时我们没有使用 FS”),但我不确定需要说什么,而且当前的陈述具有误导性,因为它听起来你说 FS 只适用于 cmd 行文件。
      【解决方案5】:

      两条可能有帮助的建议:

      首先,我怀疑执行您想要的操作的 Awk 脚本对于单行脚本来说太长了。我会编写一个以file1file2 作为参数的多行脚本,并将其存储在一个名为mymerge.awk 的文件中。这是一个骨架:

      #!/usr/bin/awk -f
      
      BEGIN {
          file1=ARGV[1]; file2=ARGV[2]
      }
      
      # The guts of your script go here.
      

      然后您可以简单地使您的脚本可执行 (chmod +x mymerge.awk) 并从 shell 调用它:mymerge.awk file1 file2。这种方法的优点是使您的脚本易于阅读、重用和维护。

      第二条建议:使用 awk 的getline &lt; file1file1 读取数据,而不是stdinfile2 也是如此。要将刚刚读取的行存储在变量中,可以说

      getline var1 < file1; getline var2 < file2
      

      Gnu Awk 用户指南有一个全面易读的description of getline 以及如何使用它。

      今晚我无法为您编写和测试工作脚本,但我希望这会帮助您取得一些进展。

      【讨论】:

        猜你喜欢
        • 2020-06-05
        • 1970-01-01
        • 1970-01-01
        • 2011-07-24
        • 2015-11-07
        • 2016-07-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多