我有许多文件,每个文件的名称中都包含特定模式,例如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
变量group
、first
和中last
。
对于以info.tsv
这种方式读取的每一行,将扫描位置参数列表以查找与组中第一个名称匹配的名称。一旦找到这个名字,我们就设置一个标志 ,collect
它告诉脚本的逻辑从列表中的当前位置开始从位置参数列表中指定的文件收集数据。一旦我们遇到与组的姓氏相对应的名称,这就结束。
请注意,此处true
和false
用作命令而不是简单的字符串。存储在变量中的值$collect
正在执行,if ! "$collect"
这意味着脚本将运行两个 shell 内置命令之一true
或false
. 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
这将首先ABC
从info.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')
连接该行中的文件列表。