【问题标题】:How to loop a variable range in cut command如何在 cut 命令中循环变量范围
【发布时间】:2017-11-07 17:49:14
【问题描述】:

我有一个包含 2 列的文件,我想使用第二列中的值来设置 cut 命令中的范围以从另一个文件中选择一个字符范围。我想要的范围是第二列中值位置的字符加上接下来的 10 个字符。一会儿我会举个例子。

我的文件是这样的:

文件有 2 列,行间没有空行 (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

【问题讨论】:

  • 第一个是个好问题。很清楚,也显示了努力。
  • 记得接受答案。

标签: bash environment-variables cut


【解决方案1】:

这里不需要cutdd 可以完成对文件的索引工作,并且只读取您想要的字节数。 (请注意,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 副本)。

【讨论】:

  • 好建议!非常感谢。效果很好。
【解决方案2】:

使用 awk

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

【讨论】:

  • 嗯。这会将所有file2 存储在内存中,对吗?因此,如果 file1 很长(因为 awk 循环比 bash while read 循环快得多),它看起来会是一个很好的解决方案,但如果 file2 很长(超出 RAM 所能容纳的范围),则不会那么快。
  • @CharlesDuffy 当 file2 很长时 data=(&lt;file2.txt) 在另一个解决方案中也很困难。
  • 是的,我同意——这就是为什么我评论说我喜欢这个解决方案“如果 file2.txt 很小/很短”(并且它的作者在周围的散文中明确了限制),以及为什么我认为我自己的解决方案有一个利基,它是最佳选择(如果 data2 可能太大而无法存储在 RAM 中)。
【解决方案3】:

如果file2.txt不是太大,那么可以在内存中读取, 并使用 Bash 子字符串来提取所需的范围:

data=$(<file2.txt)
while read -r name index _; do
  echo "${data:$index:10}"
done <file1.txt >result.txt

这将比为每个范围定义运行 cut 或其他进程更有效。

(感谢@CharlesDuffy 提示阅读data 没有无用的catwhile 循环。)

【讨论】:

  • data=$(&lt;file2.txt),以避免运行外部cat 的成本。如果file2.txt 很小/很短,我同意这是最好的答案。
  • file2.txt 是一个完整的真核基因组,因此它是一个大文件,但您的解决方案非常适合小型基因组,例如原核生物基因组。谢谢你的建议。
【解决方案4】:

一种解决方法:

#!/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 黑客会使用的解决方案,但对于 bash 新手来说非常有用。它使用非常通用的工具,但如果您需要高性能,虽然有点糟糕。 Shell 脚本通常由很少使用 shell 脚本但知道一些命令并且只想完成工作的人使用。这就是我包含此解决方案的原因,即使其他答案对于更有经验的人来说更好。

第一行很简单。它只是从file1.txt 中提取数字。第二行使用了非常好的工具headtail。通常,它们与行而不是字符一起使用。尽管如此,我还是用head 打印了第一个pos + 10 字符。结果通过管道传送到tail,它会打印最后一个10 字符。

感谢@CharlesDuffy 的改进。

【讨论】:

  • (我还建议避免在内循环中使用子shell,尤其是,因为它们很容易避免;每个$( ... ) 都是fork()wait() )。
  • 由于我是 bash 新手,我非常感谢您的解决方案。对于我,这说得通。谢谢!
猜你喜欢
  • 2013-08-30
  • 1970-01-01
  • 1970-01-01
  • 2018-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-12
相关资源
最近更新 更多