在bash中反转行组

在bash中反转行组,bash,reverse,Bash,Reverse,我正在寻找一种优雅的方法来反转文本文件内容,不仅仅是逐行(像tac一样),而是按行(节)分组。以下示例应详尽无遗: 原始文件内容: -------- ----- time | -------- | 10:00:00 |--- section 1 10:00:10 | 10:00:20 | -------- ----- -------- ----- time | ------

我正在寻找一种优雅的方法来反转文本文件内容,不仅仅是逐行(像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        |
--------    -----
设计输出:

--------    -----
    time        |
--------        | 
10:01:00        |--- section 2 
10:01:10        | 
10:01:20        |
--------    -----
--------    -----
    time        |
--------        | 
10:00:00        |--- section 1 
10:00:10        | 
10:00:20        |
--------    -----
一个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
另一个可能排除无效数据的更安全的变体:

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'/^-----+$/{++j};!j{next};{a[i++]=$0}j==3{t=”“;for(k=0;k
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

您可以使用标准命令,使用临时文件来实现这一点。在我看来,它甚至更短,更具可读性:)

对于
split
命令:

  • -4
    表示4个数字的后缀
  • -d
    表示数字后缀
  • -l 7
    按此行数拆分源文件
  • data.txt
    要读取的文件
  • 临时文件的前缀
split
为每个行块生成一个名为
blocknnnn
的临时文件,其中
nnnn
是一个序列号。
cat
将这些块放在一起,文件列表的顺序与
ls-r
相反

优点:该文件不加载到内存中,因此在这方面它的大小没有限制


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

这是我的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

我希望有一个不链接到特定间隔符的解决方案,输入应该只是属于该节的行数…@Tom我对此进行了更新。@Tom我也添加了一个Bash解决方案。如前所述,请考虑分隔符(-----------)作为文件的普通行。解决方案应取多行并反转。更准确地说,解决方案应将行数作为输入并反转。例如,tac逐行反转,就像传递的输入为1一样。
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
( 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 -e '$stdin.readlines().each_slice(7).entries.reverse.each { |b| puts b; }' < file
#!/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
#!/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
blockSize=7

readarray lines < file.txt
for (( i=${#lines[@]}-blockSize; i>=0; i-=blockSize )); do
    ( IFS=''; echo -n "${lines[*]:i:blockSize}" )
done
split -a 4 -d -l 7 data.txt block
cat $(ls -r block*) > reversed-data.txt
rm block*
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