每个第i个字符打印子字符串

时间:2018-06-26 19:21:49

标签: bash awk substr

我有一些文件想以“滑动窗口”的方式分为子字符串,以1个字符为增量。每个文件只有一行,我可以这样打印子字符串:

input="file.txt"
awk '{print substr($1,1,21)}' $input


awk '{print substr($1,2,21)}' $input

分别给我以下输出。

AATAAGGTGCCTGATTAAA-G   
ATAAGGTGCCTGATTAAA-GG

输入文件包含大约17,000个字符,我设法尝试执行for循环以计算字符并在for循环内尝试上述命令,如下所示:

count=`wc -c ${input} |cut -d' ' -f1`
for num in `seq ${count}`
   do
awk '{print substr($1,$num,21)}' $input
   done

但是这将返回空输出。我还想将其作为bash脚本运行,并在命令行中指定输入和子字符串以及输出文件的大小,例如:

script.sh input_file.txt 21 output.txt

我尝试了一下,但是也没有用。

  input=$1
  kmer=$2
  output=$3
  count=`wc -c ${input} |cut -d' ' -f1`
  for num in `seq ${count}`
    do
 awk '{print substr($1,$num,$kmer)}' $input > $output
  done

关于我在做什么的任何提示?我对awk很陌生...

5 个答案:

答案 0 :(得分:3)

#!/usr/bin/env bash 

input=$1
kmer=$2
output=$3

data=$(<"$input")

for ((i=0;i<${#data};i++)); do
    echo "${data:i:kmer}"
done > "$output"

它仅使用substring expansion,引自手册:

  

${parameter:offset:length}

     

这称为子字符串扩展。   最多可扩展为length个值的parameter个字符   从offset指定的字符开始。


使用gawk

awk -v num="$kmer" '{for(i=1;i<=length($0);i++) print substr($0,i,num)}' "$input" > "$output"

这是一个快得多的解决方案。速度差异非常明显:在17,000个字符和30个字符的窗口上测试:第一个解决方案〜10s ,第二个解决方案〜0.01s

答案 1 :(得分:1)

您也可以使用GNU sed来执行此操作,如下所示:

echo -n "123456789" | sed -r ':loop h;s/.//3g;p;x; s/.//; t loop'
12
23 
34
45
56
67
78
89 
9

3g是“滑动窗口”的大小+ 1。

要处理文件中的数据而不是STDIN,只需在sed命令后指定它即可:

sed -r ':loop h;s/.//3g;p;x; s/.//; t loop' myfile

答案 2 :(得分:1)

关于您的特定问题,摘要:

awk '{print substr($1,$num,21)}' $input

存在一个问题,即单引号内的内容不是受外壳变量扩展的影响。这可以通过以下方式看到:

pax$ num=42 && echo '$num'
$num
pax$ num=42 && echo "$num"
42

因此$num不会替换为shell变量的值。

从上面也可以看到,您可以使用双引号将 允许扩展,但是您需要转义$1来防止其扩展。我通常会发现,将shell变量转换为awk变量比较容易,如下:

awk -vnum=$num '{print substr($1,num,21)}' $input

以下代码片段显示了此操作:

pax$ num=42 && awk 'END{print $num}' </dev/null

pax$ num=42 && awk -v num=$num 'END{print num}' </dev/null
42

但是,外部程序的17,000次调用效率非常低,最好编译一些东西,或者,如果必须使用脚本编制,则可以完全在bash本身中完成。下面的代码显示了如何执行此操作,其中的重要位在time ( )块中,其他所有内容仅是设置测试数据,计时和清理。

# Create test data.

(
    for i in {1..1000} ; do
        echo -n "abcdefghijklmnop-"
    done
) >inputdata.txt

# Time the execution.

time (
    char17k="$(cat inputdata.txt)"
    echo ${#char17k}
    for ((i = 0; i < ${#char17k}; i++)) ; do
        echo ${char17k:i:21}
    done

)

# Clean up.

rm -rf inputdata.txt

在我的系统上,此过程大约需要十秒钟。即使没有做任何有用的工作,调用17,000 awk所花费的时间也大约是此时间的三倍:

pax$ time (for in in {1..17000} ; do awk '{}' </dev/null ; done )
real    0m30.649s
user    0m5.196s
sys     0m4.848s

通过让awk完成所有工作,您当然可以甚至更快地获得更多的速度。用以下代码替换上面代码中的time ( )块的内容:

awk '{for (i = 1; i < length($0); i++) {print substr($0, i, 21)}}' inputdata.txt

给人留下深刻的印象(大约十分之一秒):

real    0m0.121s
user    0m0.008s
sys     0m0.016s

答案 3 :(得分:0)

$ echo {1..9} | tr -d ' ' |   # create test data
  awk -v len=3 '{n=length($0); for(i=1;i<=n-len+1;i++) print substr($0,i,len)}'

123
234
345
456
567
678
789

答案 4 :(得分:0)

需要输入perl吗?

#! /bin/env perl

use strict;
use warnings;

my $data;
my $offset = 0;
my $window = shift or die "Use: $0 {windowSize} [ < ] infile [ > outfile ]\n";

{ local $/;
  $data = <>;
}

print "$_\n" while $_ = substr $data, $offset++, $window;

exit;

可以挤成一排,甚至可以使用严格的警告&c ...

$: wc -c src
17000 src

$: time ./slide 21 src
!"#$%&'()*+,-./012345
"#$%&'()*+,-./0123456
#$%&'()*+,-./01234567
$%&'()*+,-./012345678
  

。 。 。

WXYZ[\
XYZ[\
YZ[\
Z[\
[\
\

real    0m0.029s
user    0m0.004s
sys     0m0.021s