按表值连接文件

mrb*_*ash 7 bash shell-script text-processing

我有许多文件,每个文件的名称中都包含特定模式,例如ABC1234001携带有关某些数据组(多列表)的信息。我也有一个info.tsv这样的表:

group1    ABC1234001    ABC1234010
group2    ABC1234011    ABC1234018
group3    ABC1234019    ABC1234028
...       ...           ...
Run Code Online (Sandbox Code Playgroud)

它包含了:

  • “group”列,指定组,
  • “第一个文件”列,指定包含相应组信息的第一个文件的模式(按字母顺序),
  • “最后一个文件”列,指定包含相应组信息的最后一个文件(按字母顺序)的模式。

所以我需要做的是将每个组的文件合并到一个文件中 - 就像

cat ABC123401{1..8}* >> group2.tsv
Run Code Online (Sandbox Code Playgroud)

以 group2 为例 - 在读取此info.tsv文件时。在此给定示例中,所有文件 ( ABC1234011.tsv, ABC1234012.tsv, ABC1234013.tsv, ABC1234014.tsv, ABC1234015.tsv, ABC1234016.tsv, ABC1234017.tsv, ABC1234018.tsv) 都连接成一个group2.tsv文件

我要做的事情如下:

group1    ABC1234001    ABC1234010
group2    ABC1234011    ABC1234018
group3    ABC1234019    ABC1234028
...       ...           ...
Run Code Online (Sandbox Code Playgroud)

但我不太确定如何迭代地更改这种方法的变量。也许使用awk更有用,但我不知道。该脚本应该生成一堆名为group1.tsv,的文件group2.tsv,其中包含表中从“第一个文件”到“最后一个文件”的相应文件的内容。请帮我编写脚本来执行此操作。

Kus*_*nda 4

下面的脚本假设您可能想要连接的所有文件都与模式匹配*.tsv。如果您知道它们全部匹配ABC*.tsv,那么您可能希望在脚本开头使用该模式来代替*.tsv.

*.tsv该脚本还假设进入特定组的所有文件名称都生成为扩展至的列表的连续子列表。

#!/bin/sh

set -- *.tsv

while read -r group first last; do
        collect=false

        for name do
                if ! "$collect"; then
                        [ "$name" = "$first.tsv" ] || continue
                        collect=true
                fi

                if "$collect"; then
                        cat -- "$name"
                        [ "$name" = "$last.tsv" ] && break
                fi
        done >"$group.tsv"

done <info.tsv
Run Code Online (Sandbox Code Playgroud)

该脚本将位置参数列表设置为匹配的名称列表*.tsv。然后,它将每行的三个字段读取info.tsv到变量groupfirst和中last

对于以这种方式读取的每一行info.tsv,将扫描位置参数列表以查找与组中第一个名称匹配的名称。一旦找到这个名字,我们就设置一个标志 ,collect它告诉脚本的逻辑从列表中的当前位置开始从位置参数列表中指定的文件收集数据。一旦我们遇到与组的姓氏相对应的名称,这就结束。

请注意,此处truefalse用作命令而不是简单的字符串。存储在变量中的值$collect正在执行,if ! "$collect"这意味着脚本将运行两个 shell 内置命令之一truefalse. shell 不像其他语言(例如 Python)那样有任何特殊的 true 或 false 关键字。

测试:

$ ls
script
Run Code Online (Sandbox Code Playgroud)
$ touch ABC{1234001..1234030}.tsv
$ for name in ABC*.tsv; do printf 'Name: %s\n' "$name" >"$name"; done
$ cat ABC1234015.tsv
Name: ABC1234015.tsv
Run Code Online (Sandbox Code Playgroud)
$ cat >info.tsv <<END_DATA
group1 ABC1234001 ABC1234010
group2 ABC1234025 ABC1234030
END_DATA
Run Code Online (Sandbox Code Playgroud)
$ ./script
$ cat group1.tsv
Name: ABC1234001.tsv
Name: ABC1234002.tsv
Name: ABC1234003.tsv
Name: ABC1234004.tsv
Name: ABC1234005.tsv
Name: ABC1234006.tsv
Name: ABC1234007.tsv
Name: ABC1234008.tsv
Name: ABC1234009.tsv
Name: ABC1234010.tsv
$ cat group2.tsv
Name: ABC1234025.tsv
Name: ABC1234026.tsv
Name: ABC1234027.tsv
Name: ABC1234028.tsv
Name: ABC1234029.tsv
Name: ABC1234030.tsv
Run Code Online (Sandbox Code Playgroud)

正如对此答案的评论中提到的,我开发此脚本供我个人使用的方式是让脚本看起来像这样:

#!/bin/sh

while read -r group first last; do
        collect=false

        for name do
                filename=$( basename "$name" )

                if ! "$collect"; then
                        [ "$filename" = "$first.tsv" ] || continue
                        collect=true
                fi

                if "$collect"; then
                        cat -- "$name"
                        [ "$filename" = "$last.tsv" ] && break
                fi
        done >"$group.tsv"

done
Run Code Online (Sandbox Code Playgroud)

set请注意顶部命令的删除(这将被命令行参数替换),以及重定向的删除info.tsv(这将被命令行上的重定向替换)。我还引入了一个filename变量,它将保存命令行上给出的路径名的文件名部分。

然后我会像这样运行脚本:

$ ./script ABC*.tsv <info.tsv
Run Code Online (Sandbox Code Playgroud)

我用这个实现的是一个脚本,它不知道输入组列表的存储位置或其名称,并且不关心文件的名称ABC(只要它们具有.tsv文件名后缀)或它们的存储位置。