如何在cut命令中循环变量范围

5
我有一个包含2列的文件,我想使用第二列的值来设置cut命令中的范围,以选择另一个文件中的一系列字符。我所需的范围是第二列中的值所在位置的字符及其后面的10个字符。稍后我会举个例子。
我的文件大致如下:
两列之间没有空行(file1.txt):
NAME1 10
NAME2 25
NAME3 48
NAME4 66

要提取变量范围的字符文件(只有一行非常长的无空格和无粗体字)(file2.txt):

GATCGAGCGGGATTCTTTTTTTTTAGGCGAGTCAGCTAGCATCAGCTACGAGAGGCGAGGGCGGGCTATCACGACTACGACTACGACTACAGCATCAGCATCAGCGCACTAGAGCGAGGCTAGCTAGCTACGACTACGATCAGCATCGCACATCGACTACGATCAGCATCAGCTACGCATCGAAGAGAGAGC

......或更直接地(复制/粘贴测试):

GATCGAGCGGGATTCTTTTTTTTTAGGCGAGTCAGCTAGCATCAGCTACGAGAGGCGAGGGCGGGCTATCACGACTACGACTACGACTACAGCATCAGCATCAGCGCACTAGAGCGAGGCTAGCTAGCTACGACTACGATCAGCATCGCACATCGACTACGATCAGCATCAGCTACGCATCGAAGAGAGAGC

期望的结果文件,每行一个序列 (result.txt):

GATTCTTTTT
GGCGAGTCAG
CGAGAGGCGA
TATCACGACT

生成的文件将包含来自第一个文件的第二列中的值设置的起始点,每个范围在新行中的字符从10-20、25-35、48-58和66-76。因此,它将始终保持10的范围,但在不同的起始点上。我尝试了以下命令:
for i in $(awk '{print $2}' file1.txt);
do
        p1=$i;
        p2=`expr "$1" + 10`
        cut -c$p1-$2 file2.txt > result.txt;
done

我没有得到任何输出或错误信息。

我也尝试过:

while read line; do
    set $line
    p2=`expr "$2" + 10`
    cut -c$2-$p2 file2.txt > result.txt;
done <file1.txt

最后这个命令给了我一个错误信息:

cut: invalid range with no endpoint: -
Try 'cut --help' for more information.
expr: non-integer argument

这是一个非常好的问题,尤其是作为第一个问题。它很清晰,并且展现了你的努力。 - klutt
请记住要接受一个答案。 - klutt
4个回答

4

这里不需要使用 cut; dd 可以完成索引文件并读取您想要的字节数。 (请注意,status = none 是 GNUism;如果您想抑制信息记录,则可能需要在其他平台上省略它并重定向 stderr)。

while read -r name index _; do
  dd if=file2.txt bs=1 skip="$index" count=10 status=none
  printf '\n'
done <file1.txt >result.txt

这种方法避免了读取整个 file2(假设它很大)时的过多内存需求,并且具有有限的性能要求(开销等于每个序列启动一个 dd 副本来提取)。


非常好的建议!非常感谢。它完美地运行。 - Fernanda Costa

3

使用 awk

$ awk 'FNR==NR{a=$0; next} {print substr(a,$2+1,10)}' file2 file1
GATTCTTTTT
GGCGAGTCAG
CGAGAGGCGA
TATCACGACT

1
嗯。这会将file2全部存储在内存中,对吧?所以如果file1很长的话(因为awk循环比bash的while read循环快得多),看起来这是一个不错的解决方案,但如果file2太长(超出了RAM的容量),那就不太适用了。 - Charles Duffy
1
@CharlesDuffy 当file2很长时,在另一个解决方案中data=(<file2.txt)也很困难。 - Walter A
是的,我同意--这就是为什么我评论说我喜欢那个解决方案“如果file2.txt很小/短”(并且它的作者在周围的散文中明确说明了限制),以及为什么我认为我的解决方案有一个独特性,在处理可能存储不下的data2时,是最好的选择。 - Charles Duffy

2
如果file2.txt文件不太大,那么可以将其读入内存中,并使用Bash子字符串来提取所需范围:
data=$(<file2.txt)
while read -r name index _; do
  echo "${data:$index:10}"
done <file1.txt >result.txt

相比于针对每个范围定义运行cut或另一个进程,这将更加高效。

(感谢@CharlesDuffy的提示,在没有无用的catwhile循环的情况下读取data。)


2
data=$(<file2.txt),避免运行外部的 cat 命令以节省成本。如果 file2.txt 很小/短,则我同意这是最佳答案。 - Charles Duffy
2
file2.txt 是一个完整的真核基因组,因此它是一个很大的文件,但是您的解决方案非常适用于小型基因组,比如原核生物。感谢您的建议。 - Fernanda Costa

1
一种解决方法是:
#!/bin/bash                                                                                                        

while read line; do
    pos=$(echo "$line" | cut -f2 -d' ')
    x=$(head -c $(( $pos + 10 )) file2.txt | tail -c 10)
    echo "$x"
done < file1.txt > result.txt

这并不是一位经验丰富的Bash黑客会使用的解决方案,但对于新手来说非常好。它使用的工具非常灵活,尽管在需要高性能时有些不足。Shell脚本通常由很少编写Shell脚本但知道一些命令并想要完成任务的人使用。这就是为什么我包含了这个解决方案,即使其他答案对于更有经验的人来说更好。
第一行非常简单。它只是从file1.txt中提取数字。第二行使用非常好的工具head和tail。通常,它们与行而不是字符一起使用。尽管如此,我使用head打印前pos + 10个字符。结果被传送到tail,它打印最后的10个字符。
感谢@CharlesDuffy进行改进。

我建议在内部循环中避免使用子shell,尤其是当它们可以轻松避免时;每个$( ... )都会导致一次fork()wait() - Charles Duffy
由于我是一个Bash新手,我非常感激你的解决方案。它对我来说很有意义。谢谢! - Fernanda Costa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接