我有1个文本文件,即test1.txt。
text1.txt包含以下内容:
输入:
##[A1] [B1] [T1] [V1] [T2] [V2] [T3] [V3] [T4] [V4]## --> headers
1 1000 0 100 10 200 20 300 30 400
40 500 50 600 60 700 70 800
1010 0 101 10 201 20 301 30 401
40 501 50 601
2 1000 0 110 15 210 25 310 35 410
45 510 55 610 65 710
1010 0 150 10 250 20 350 30 450
40 550
条件:
每个A1 +(B1 + [Tn + Vn])的A1和B1->
A1应该在1列中。
B1应该在1列中。
T1,T2,T3和T4应该在1列中。
V1,V2,V3和V4应该在1列中。
我如何将其变成如下所示?
需求输出:
## A1 B1 Tn Vn ## --> headers
1 1000 0 100
10 200
20 300
30 400
40 500
50 600
60 700
70 800
1010 0 101
10 201
20 301
30 401
40 501
50 601
2 1000 0 110
15 210
25 310
35 410
45 510
55 610
65 710
1010 0 150
10 250
20 350
30 450
40 550
这是我当前的代码:
第一次尝试:
输入
cat test1.txt | awk ' { a=$1 b=$2 } { for(i=1; i<=5; i=i+1) { t=substr($0,11+i*10,5) v=substr($0,16+i*10,5) if( t ~ /^\ +[0-9]+$/ || t ~ /^[0-9]+$/ || t ~ /^\ +[0-9]+\ +$/ ){ printf "%7s %7d %8d %8d \n",a,b,t,v } }}' | less
输出:
1 1000 400 0
40 500 800 0
1010 0 401 0
2 1000 410 0
1010 0 450 0
我正在尝试使用简单的awk命令,但仍然无法获得结果。
有人可以帮我吗?
谢谢,
Am
答案 0 :(得分:4)
与其他地方所说的不同,这根本没有什么棘手的问题,您只是在输入中使用固定宽度的字段,而不是用字符/字符串分隔的字段。
使用GNU awk来让FIELDWIDTHS处理固定宽度的字段,实际上再简单不过了:
$ cat tst.awk
BEGIN {
# define the width of the input and output fields
FIELDWIDTHS = "2 4 5 5 6 5 6 5 6 5 6 99"
ofmt = "%2s%5s%6s%5s%6s%s\n"
}
{
# strip leading/trailing blanks and square brackets from every field
for (i=1; i<=NF; i++) {
gsub(/^[[\s]+|[]\s]+$/,"",$i)
}
}
NR==1 {
# print the header line
printf ofmt, $1, $2, $3, "Tn", "Vn", " "$NF
next
}
{
# print every other line
for (i=4; i<NF; i+=2) {
printf ofmt, $1, $2, $3, $i, $(i+1), ""
$1 = $2 = $3 = ""
}
}
。
$ awk -f tst.awk file
## A1 B1 Tn Vn ## --> headers
1 1000 0 100
10 200
20 300
30 400
40 500
50 600
60 700
70 800
1010 0 101
10 201
20 301
30 401
40 501
50 601
2 1000 0 110
15 210
25 310
35 410
45 510
55 610
65 710
1010 0 150
10 250
20 350
30 450
40 550
对于其他问题,您将使用while() { substr() }
循环而不是FIELDWIDTHS
,这样虽然要多写几行代码,但仍然很琐碎。
以上内容将比等效的shell脚本快几个数量级。参见https://unix.stackexchange.com/questions/169716/why-is-using-a-shell-loop-to-process-text-considered-bad-practice。
答案 1 :(得分:3)
这并不容易,因为很难确定何时具有不同的行样式-第1列和第2列中都有值的行,第1列中没有值和第2列中的值的行以及第1列或第2列中没有值。第一步是使此操作更容易— sed
进行救援:
$ sed 's/[[:space:]]\{1,\}$//
s/^....../&|/
s/|....../&|/
:a
s/|\( *[0-9][0-9]* \)\( *[^|]\)/|\1|\2/
t a' data
1 | 1000 | 0 | 100 | 10 | 200 | 20 | 300 | 30 | 400
| | 40 | 500 | 50 | 600 | 60 | 700 | 70 | 800
| 1010 | 0 | 101 | 10 | 201 | 20 | 301 | 30 | 401
| | 40 | 501 | 50 | 601
2 | 1000 | 0 | 110 | 15 | 210 | 25 | 310 | 35 | 410
| | 45 | 510 | 55 | 610 | 65 | 710
| 1010 | 0 | 150 | 10 | 250 | 20 | 350 | 30 | 450
| | 40 | 550
$
第一行删除任何结尾的空格,以避免混淆。接下来的两个表达式处理固定宽度的列1和2(每个列6个字符)。下一行创建标签a
;替代者找到一个管道|
,一些空格,一些数字,一个空格以及一些不包含管道的尾随材料;并在中间插入管道。如果完成替换,t a
会跳回到标签。
有了这一点,使用awk
字段分隔符就可以轻松管理|
。
这很冗长,但似乎可以解决问题:
awk -F '|' '
$1 > 0 { printf "%5d %4d %3d %3d\n", $1, $2, $3, $4
for (i = 5; i <= NF; i += 2) { printf "%5s %4s %3d %3d\n", "", "", $i, $(i+1) }
next
}
$2 > 0 { printf "%5s %4d %3d %3d\n", "", $2, $3, $4
for (i = 5; i <= NF; i += 2) { printf "%5s %4s %3d %3d\n", "", "", $i, $(i+1) }
next
}
{ for (i = 3; i <= NF; i += 2) { printf "%5s %4s %3d %3d\n", "", "", $i, $(i+1) }
next
}'
输出:
1 1000 0 100
10 200
20 300
30 400
40 500
50 600
60 700
70 800
1010 0 101
10 201
20 301
30 401
40 501
50 601
2 1000 0 110
15 210
25 310
35 410
45 510
55 610
65 710
1010 0 150
10 250
20 350
30 450
40 550
如果需要删除标题,请将1d;
添加到sed
脚本的开头。
答案 2 :(得分:1)
这可能对您有用(GNU sed):
sed -r '1d;s/^(.{11}).{11}/&\n\1/;s/^((.{5}).*\n)\2/\1 /;s/^(.{5}(.{6}).*\n.{5})\2/\1 /;/\S/P;D' file
删除第一行(如果需要标题,请参见下文)。键域占据前11个字符(第一个键为5个字符,后6个为字符),数据域占据下11个字符。在每对数据域之前插入换行符和键域。比较相邻行上的键,如果重复则用空格替换。不要打印任何空白行。
如果需要标题,请使用以下内容:
sed -r '1{s/\[[^]]+\]\s*//5g;y/[]/ /;s/1/n/3g;s/B/ B/;G;b};s/^(.{11}).{11}/&\n\1/;s/^((.{5}).*\n)\2/\1 /;s/^(.{5}(.{6}).*\n.{5})\2/\1 /;/\S/P;D' file
这会在第一行进行额外的格式化,以删除多余的标题[]
,将1
替换为n
,添加额外的对齐空间和随后的空行
更多。通过将输入文件的第二行用作数据模板,可以创建不具有任何hard coded
值的sed脚本:
sed -r '2!d;s/\s*\S*//3g;s/.\>/&\n/;h;s/[^\n]/./g;G;s/[^\n.]/ /g;s#(.*)\n(.*)\n(.*)\n(.*)#1d;s/^(\1\2)\1\2/\&\\n\\1/;s/^((\1).*\\n)\\2/\\1\3/;s/^(\1(\2).*\\n\1)\\2/\\1\4/;/\\S/P;D#' file |
sed -r -f - file
将从模板创建的脚本作为文件通过管道传递给sed的第二次调用,并针对原始文件运行以产生所需的输出。
同样,如果需要,标题可以被格式化:
sed -r '2!d;s/\s*\S*//3g;s/.\>/&\n/;h;s/[^\n]/./g;G;s/[^\n.]/ /g;s#(.*)\n(.*)\n(.*)\n(.*)#s/^(\1\2)\1\2/\&\\n\\1/;s/^((\1).*\\n)\\2/\\1\3/;s/^(\1(\2).*\\n\1)\\2/\\1\4/;/\\S/P;D#' file |
sed -r -e '1{s/\[[^]]+\]\s*//5g;y/[]/ /;s/1/n/3g;s/B/ B/;G;b}' -f - file
通过从输入文件的第二行提取前四个字段,可以创建四个变量。两个正则表达式和两个值。这些变量可用于构建sed脚本。
sed脚本是根据从模板中提取的字符串创建的,并且所产生的变量也是字符串,因此可以将它们连接起来以产生其他新的正则表达式和新值等
答案 3 :(得分:0)
这是一个相当棘手的问题,可以通过多种方法来解决。无论是bash
,perl
还是awk
,您都需要以某种半通用的方式处理字段数,而不仅仅是对示例中的值进行硬编码。
使用bash,只要您可以依赖所有行中偶数个字段(除了具有唯一初始值的行(例如1010
),就可以容纳一个字段对于具有1, 2
等的行,您知道初始输出将包含4-fields
;对于具有1010
等的行,您知道输出将包含初始3-fields
。对于其余的值,您只需输出对。
棘手的部分是处理 alignment 。这是printf
的地方,它允许您使用"%*s"
形式的参数设置 field-width ,其中转换说明符期望下一个参数为{{1} }值,指定 field-width ,后跟字符串转换本身的参数。这需要一些体操,但是您可以在bash本身中执行以下操作:
(注意:进行编辑以匹配您的输出标题格式)
integer
使用重定向将输入文件重定向到脚本(而不是读取文件)(如果您只想提供文件名,则重定向文件以提供输出#!/bin/bash
declare -i nfields wd=6 ## total no. fields, printf field-width modifier
while read -r line; do ## read each line (preserve for header line)
arr=($line) ## separate into array
first=${arr[0]} ## check for '#' in first line for header
if [ "${first:0:1}" = '#' ]; then
nfields=$((${#arr[@]} - 2)) ## no. fields in header
printf "## A1 B1 Tn Vn ## --> headers\n" ## new header
continue
fi
fields=${#arr[@]} ## fields in line
case "$fields" in
$nfields ) ## fields -eq nfiles?
cnt=4 ## handle 1st 4 values in line
printf " "
for ((i=0; i < cnt; i++)); do
if [ "$i" -eq '2' ]; then
printf "%*s" "5" "${arr[i]}"
else
printf "%*s" "$wd" "${arr[i]}"
fi
done
echo
for ((i = cnt; i < $fields; i += 2)); do ## handle rest
printf "%*s%*s%*s\n" "$((2*wd))" " " "$wd" "${arr[i]}" "$wd" "${arr[$((i+1))]}"
done
;;
$((nfields - 1)) ) ## one less than nfields
cnt=3 ## handle 1st 3 values
printf " %*s%*s" "$wd" " "
for ((i=0; i < cnt; i++)); do
if [ "$i" -eq '1' ]; then
printf "%*s" "5" "${arr[i]}"
else
printf "%*s" "$wd" "${arr[i]}"
fi
done
echo
for ((i = cnt; i < $fields; i += 2)); do ## handle rest
if [ "$i" -eq '0' ]; then
printf "%*s%*s%*s\n" "$((wd+1))" " " "$wd" "${arr[i]}" "$wd" "${arr[$((i+1))]}"
else
printf "%*s%*s%*s\n" "$((2*wd))" " " "$wd" "${arr[i]}" "$wd" "${arr[$((i+1))]}"
fi
done
;;
* ) ## all other lines format as pairs
for ((i = 0; i < $fields; i += 2)); do
printf "%*s%*s%*s\n" "$((2*wd))" " " "$wd" "${arr[i]}" "$wd" "${arr[$((i+1))]}"
done
;;
esac
done
循环)
使用/输出示例
while read...
在$ bash text1format.sh <dat/text1.txt
## A1 B1 Tn Vn ## --> headers
1 1000 0 100
10 200
20 300
30 400
40 500
50 600
60 700
70 800
1010 0 101
10 201
20 301
30 401
40 501
50 601
2 1000 0 110
15 210
25 310
35 410
45 510
55 610
65 710
1010 0 150
10 250
20 350
30 450
40 550
和awk
之间,bash
通常会更快,但是在这里使用格式化输出时,它可能比平时更近。仔细研究一下,如果您有任何疑问,请告诉我。