我正在寻找一种优雅的方法来反转文本文件内容,而不仅仅是逐行(如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 |
-------- -----
答案 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