按表值连接文件

按表值连接文件

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

group1    ABC1234001    ABC1234010
group2    ABC1234011    ABC1234018
group3    ABC1234019    ABC1234028
...       ...           ...

它包含了:

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

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

cat ABC123401{1..8}* >> group2.tsv

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

我要做的事情如下:

while read $file; do
  #assign columns to variables like $1="group", $2="firstfile", $3="lastfile"
  cat *{$2..$3}* > $1.tsv;
done < info.tsv

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

答案1

下面的脚本假设您可能想要连接的所有文件都与模式匹配*.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

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

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

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

测试:

$ ls
script
$ touch ABC{1234001..1234030}.tsv
$ for name in ABC*.tsv; do printf 'Name: %s\n' "$name" >"$name"; done
$ cat ABC1234015.tsv
Name: ABC1234015.tsv
$ cat >info.tsv <<END_DATA
group1 ABC1234001 ABC1234010
group2 ABC1234025 ABC1234030
END_DATA
$ ./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

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

#!/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

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

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

$ ./script ABC*.tsv <info.tsv

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

答案2

您的方法是一个好主意,但不幸的是它不起作用,因为变量不会在大括号扩展内扩展:

$ echo {1..5}
1 2 3 4 5
$ a=1
$ b=5
$ echo {$a..$b}
{1..5}

你可以通过使用以下方法来解决这个问题eval

sed 's/ABC//g' info.tsv | 
    while read -r group start end; do 
        files=( $(eval echo ABC{$start..$end}.tsv) )
        cat "${files[@]}" > "$group.tsv"; 
    done 

这将首先ABCinfo.tsv文件中删除 的所有实例,以便我们可以单独获取数字。请注意,这假设了您向我们展示的确切数据结构。如果ABC也可以出现在组名中,那么这就会被打破。

删除 后ABC,结果将通过管道传输到while读取三个变量的循环中:$group$start$end。然后将它们传递给eval将在调用大括号扩展之前扩展变量的变量,从而允许您获取文件名列表:

$ eval echo ABC{1..5}
ABC1 ABC2 ABC3 ABC4 ABC5

的结果eval存储在$files数组中,该数组作为输入传递给cat

cat "${files[@]}" > "$group.tsv";

答案3

如果我理解正确的话,这是一个选项

$ while IFS= read -r i; do
    f=$(echo "$i" | cut -d' ' -f1)
    cat $(echo "$i" | cut -d' ' -f2- | sed -E 's/([0-9])\s+/\1.tsv /;s/([0-9])$/\1.tsv /') > "$f.txt"
  done < info.tsv

  • f=$(echo "$i" | cut -d' ' -f1)检索组的名称。
  • cat $(cut -d' ' -f2- | sed -E 's/([0-9])\s+|([0-9])$/\1.tsv /g')连接该行中的文件列表。

相关内容