在bash中反转行组

时间:2013-09-08 13:43:09

标签: bash reverse

我正在寻找一种优雅的方法来反转文本文件内容,而不仅仅是逐行(如tac),而是按行(部分)组。以下示例应该是详尽无遗的:

原始文件内容:

--------    -----
    time        |
--------        | 
10:00:00        |--- section 1 
10:00:10        | 
10:00:20        |
--------    -----
--------    -----
    time        |
--------        | 
10:01:00        |--- section 2 
10:01:10        | 
10:01:20        |
--------    -----

Desidered output:

--------    -----
    time        |
--------        | 
10:01:00        |--- section 2 
10:01:10        | 
10:01:20        |
--------    -----
--------    -----
    time        |
--------        | 
10:00:00        |--- section 1 
10:00:10        | 
10:00:20        |
--------    -----

4 个答案:

答案 0 :(得分:2)

一个awk可以做到:

awk '{a[i++]=$0};/-----/{++j};j==3{t="";for(k=0;k<i;++k)t=t a[k]"\n";b[++l]=t;i=j=0}END{for(i=l;i;--i)printf "%s", b[i]}' file

输出:

----------------------------------------------------------------
date        time        hostname    cpu     ram     /       /opt
----------------------------------------------------------------
2013/09/08  15:40:00    server1     20%     30%     50%     70%
2013/09/08  15:40:00    server2     15%     21%     49%     72%
2013/09/08  15:40:00    server3     20%     40%     40%     75%
----------------------------------------------------------------
----------------------------------------------------------------
date        time        hostname    cpu     ram     /       /opt
----------------------------------------------------------------
2013/09/08  15:35:00    server1     15%     30%     50%     70%
2013/09/08  15:35:00    server2     18%     21%     49%     72%
2013/09/08  15:35:00    server3     15%     40%     40%     75%
----------------------------------------------------------------
----------------------------------------------------------------
date        time        hostname    cpu     ram     /       /opt
----------------------------------------------------------------
2013/09/08  15:30:00    server1     20%     30%     50%     70%
2013/09/08  15:30:00    server2     10%     21%     49%     72%
2013/09/08  15:30:00    server3     15%     40%     40%     75%
----------------------------------------------------------------

另一种可能排除无效数据的更安全的变体:

awk '/^-----+$/{++j};!j{next};{a[i++]=$0}j==3{t="";for(k=0;k<i;++k)t=t a[k]"\n";b[++l]=t;i=j=0}END{for(i=l;i;--i)printf "%s", b[i]}' file

另一个基于行数的人:

awk '{a[i++]=$0}i==7{t="";for(k=0;k<i;++k)t=t a[k]"\n";b[++l]=t;i=0}END{for(i=l;i;--i)printf "%s", b[i]}' file

更简单一点:

awk '{a[i++]=$0}i==7{t="";for(i=0;i<7;++i)t=t a[i]"\n";b[++j]=t;i=0}END{for(;j;--j)printf "%s", b[j]}' file

另一个简单的方法是在Bash中使用相同的概念:

( IFS=$'\n'; while read -r A[I++]; do [[ I -eq 7 ]] && { B[++J]="${A[*]}"; I=0; }; done; for ((;J;--J)); do echo "${B[J]}"; done; ) < file

Ruby的另一个 true 单行代码:

ruby -e '$stdin.readlines().each_slice(7).entries.reverse.each { |b| puts b; }' < file

答案 1 :(得分:1)

您可以使用标准命令,使用临时文件来执行此操作。它甚至更短,在我看来更具可读性:)

split -a 4 -d -l 7 data.txt block
cat $(ls -r block*) > reversed-data.txt
rm block*

对于split命令:

  • -a 4的后缀为4个数字
  • -d用于数字后缀
  • -l 7按行数分割源文件
  • data.txt文件阅读
  • block临时文件前缀

split为每个行块生成一个临时文件,名为blocknnnn,其中nnnn是序列号。 cat将块放在一起,文件列表的顺序相反,ls -r给出了它。

优点:文件未加载到内存中,因此在此前端

上的大小没有限制

缺点:将完整的数据副本复制到磁盘,因此需要两倍的空间

答案 2 :(得分:0)

awk绝对是正确的方法,但这里有一个bash替代方案:

#!/bin/bash

separator='----------------------------------------------------------------' # each block must end with a separator
blockSeparators=3 # number of separators in each block. Could be 1 as well

dataArr=()
current=0
subCounter=0
while read -r curLine; do
    dataArr[current]+=$curLine$'\n'
    if [[ $curLine == "$separator" ]]; then
        (( ++subCounter == blockSeparators )) && (( current++ , subCounter=0 ))
    fi
done < file.txt

for (( i=${#dataArr[@]}; i>=0; i-- )); do
    echo -n "${dataArr[i]}"
done

如果您知道所有块都有7行:

#!/bin/bash

blockLines=7

dataArr=()
current=0
lineCounter=0
while read -r curLine; do
    dataArr[current]+=$curLine$'\n'
    (( ++lineCounter == blockLines )) && (( current++ , lineCounter=0 ))
done < file.txt

for (( i=${#dataArr[@]}; i>=0; i-- )); do
    echo -n "${dataArr[i]}"
done

但如前所述,请使用awk解决方案。 Bash不适合做这样的操作:)

编辑:

在bash中更短!

blockSize=7

readarray lines < file.txt
for (( i=${#lines[@]}-blockSize; i>=0; i-=blockSize )); do
    ( IFS=''; echo -n "${lines[*]:i:blockSize}" )
done

答案 3 :(得分:0)

这是我的bash解决方案

tmparr=()
blockSize=7
i=$blockSize
tac file | while read line; do  
tmparr[$i]="$line" && ((i--))  
[ $i -eq 0 ] && i=$blockSize && for j in "${tmparr[@]}"; do echo "$j"; done
done