bash:在n行之后剪切,然后水平连接



我有一个大的ascii文件,我想重新格式化它。输入格式为:

a ; 1 ; 2 
b ; 2 ; 3 
c ; 4 ; 5 
d ; 6 ; 7 
e ; 8 ; 9 
f ; 10 ; 11

它有N=4行。输出格式应为

a ; 1 ; 2; c ; 4 ; 5; e ; 8 ; 9
b ; 2 ; 3; d ; 6 ; 7; f ; 10 ; 11

所以我想剪切n=2个连续的行,并将它们水平粘贴到一个新的结果文件中。

如何使用bash完成此操作?

您可以使用awk。

Awk逐行处理其输入文件,并允许您每次执行各种操作。它支持数组,因此对于手头的问题,我们可以使用一个数组,在该数组中,我们以正确的格式为最终输出准备数据。

最初,数组为空。对于第一个n行,我们所要做的就是将该行存储在二维数组的新行中。例如,这给了我们:

| a ; 1 ; 2 |
| b ; 2 ; 3 |

我们是如何在awk中构建的?为了方便起见,awk提供了一个特殊的变量NR,它始终保持输入文件中当前行的行号。所以我们可以使用该变量来索引数组的第一个维度,除了NR是基于1的,所以我们需要减去1来进行基于0的索引:

a[NR-1] = $0

这里,$0包含awk中当前行的内容。

在第一行n之后,我们希望将每一行新行连接到数组中已经存储的内容,始终从顶部开始。所以我们需要注意两件事:

  1. 计算数组a的正确索引
  2. 执行串联操作

以下行同时执行这两项操作:

a[(NR-1)%n] = a[(NR-1)%n] "; " $0

请注意,数组索引的计算现在不再是NR-1,而是使用mod运算符%(NR-1)%n。连接是微不足道的:我们只写三个部分来按顺序连接:1(上一个数组条目2(分隔符字符串;,3(再次连接当前行。

然而,我们观察到了一些有趣的事情:由于awk处理未初始化变量的方式,我们几乎可以对第一行n使用上面的表达式,因为mod不会更改这些值,而a[(NR-1)%n]在第一次使用时只是空字符串。唯一的问题是分隔符字符串:我们不希望它出现在行的开头。

但有一个简单的方法:我们可以简单地选择最后不打印。那么剩下要做的就是:

  • 每行:a[(NR-1)%n] = a[(NR-1)%n] "; " $0
  • 最后:打印a的内容,但抑制前两个字符

这基本上就是下面的脚本所做的,除了还修剪了每行开头和结尾的空白(使用gsub(,并通过使用命令行参数增加了一点便利:

#!/bin/sh
if [ $# -lt 2 ]; then
echo "USAGE: $(basename "${0}") <n> <file>+"
exit 1
fi
n=${1}
shift
awk -v "n=${n}" '
{ gsub(/^[ t]+|[ t]+$/, "", $0); a[(NR-1)%n] = a[(NR-1)%n] "; " $0 }
END { 
for(i=0; i<n; i++) {
print substr(a[i],3)
}
}' ${@}

这是我的"非优雅解决方案";使用@Thomas的部分答案:

#!/bin/bash
in_file=data.txt
# start at line 2
n=2
begin=$n
num_iterations=2
# first paste 2 starting lines to result file
head -n $n  $in_file > result.txt 
# then paste remaining 
for i in $(seq $num_iterations); do
# increment starting line by 2
let "begin+=$n"  
# cut 2 lines and paste to temporary file
head -n $begin  $in_file | tail -n $n | sed 's/ *# */ ; /' > tmp.txt  

# concat results horizontally
paste -d '; ' result.txt tmp.txt > result_tmp.txt;  

# update result file
mv result_tmp.txt result.txt  
done

更好地尝试理解@oguz-ismail 未解释的解决方案

awk -v n=2 '++i>n{i=1} {r[i]=r[i]s[i]$0;s[i]="; "} END{for(i=1;i<=n;i++)print r[i]}' file

相关内容

最新更新