我有一个details.txt 文件,其中包含以下数据
size=190000
date=1603278566981
repo-name=testupload
repo-path=/home/test/testupload
size=140000
date=1603278566981
repo-name=testupload2
repo-path=/home/test/testupload2
size=170000
date=1603278566981
repo-name=testupload3
repo-path=/home/test/testupload3
等等
下面的 awk 脚本将其处理为
#!/bin/bash
awk -vOFS='\t' '
BEGIN{ FS="=" }
/^size/{
if(++count1==1){ header=$1"," }
sizeArr[++count]=$NF
next
}
/^@repo-name/{
if(++count2==1){ header=header OFS $1"," }
repoNameArr[count]=$NF
next
}
/^date/{
if(++count3==1){ header=header OFS $1"," }
dateArr[count]=$NF
next
}
/^@blob-name/{
if(++count4==1){ header=header OFS $1"," }
repopathArr[count]=$NF
next
}
END{
print header
for(i=1;i<=count;i++){
printf("%s,%s,%s,%s,%s\n",sizeArr[i],repoNameArr[i],dateArr[i],repopathArr[i])
}
}
' details.txt | tr -d @ |awk -F, '{$3=substr($3,0,10)}1' OFS=,|sed 's/date/creationTime/g'
打印格式正确的值
size date repo-name repo-path
190000 1603278566981 testupload /home/test/testupload
140000 1603278566981 testupload2 /home/test/testupload2
170000 1603278566981 testupload3 /home/test/testupload3
我想添加,如果任何大小/日期/存储库名称/存储库路径没有值,则应该打印零
我尝试在 awk 脚本中添加以下内容,但它不起作用,我不知道如何获取它
}
/^@repo-name/{
if(++count2==1){ header=header OFS $1"," }
if(-z "${repo-name}") ; then
repoNameArr=0
repoNameArr[count]=$NF
next
}
我不确定如何在 awk 脚本中使用 if,你能帮我一下吗
如果没有针对大小/日期/存储库名称/存储库路径的值,则最终输出应打印零
size date repo-name repo-path
190000 1603278566981 testupload /home/test/testupload
140000 1603278566981 testupload2 /home/test/testupload2
170000 1603278566981 testupload3 /home/test/testupload3
170000 1603278566981 0 /home/test/testupload4
170000 1603278566981 0 /home/test/testupload5
170000 1603278566981 testupload6 /home/test/testupload6
请指导
答案1
以下假设当你说any of the size/date/repo-name/repo-path has no value
你的意思时。例如,存在,而不是某些块中根本repo-name=
没有线。repo-name=
以下是如何使用任何 awk 真正完成您想要做的事情,然后column
设置最终的列间距:
$ cat tst.sh
#!/usr/bin/env bash
awk '
BEGIN { OFS="\t" }
{
sub(/^@/,"") # instead of `| tr -d @`
++numTags
tag = val = $0
sub(/ *=.*/,"",tag)
sub(/[^=]+= */,"",val)
tags[numTags] = tag
vals[numTags] = val
}
numTags == 4 {
if ( !doneHdr++ ) {
for ( i=1; i<=numTags; i++ ) {
tag = ( tags[i] == "date" ? "creationTime" : tags[i] ) # instead of `| sed s/date/creationTime/`
printf "%s%s", tag, (i<numTags ? OFS : ORS)
}
}
vals[3] = substr(vals[3],1,10) # instead of `| awk {$3=substr($3,0,10}1`
for ( i=1; i<=numTags; i++ ) {
val = ( vals[i] == "" ? 0 : vals[i] )
printf "%s%s", val, (i<numTags ? OFS : ORS)
}
numTags = 0
}
' "${@:--}" |
column -s$'\t' -t
$ cat file
size=190000
date=1603278566981
repo-name=testupload
repo-path=
size=140000
date=1603278566981
repo-name=
repo-path=/home/test/testupload2
size=
date=1603278566981
repo-name=testupload3
repo-path=/home/test/testupload3
$ ./tst.sh file
size creationTime repo-name repo-path
190000 1603278566981 testupload 0
140000 1603278566981 0 /home/test/testupload2
0 1603278566981 testupload3 /home/test/testupload3
对现有代码的更改:
awk
不再需要立即将整个文件读入内存。我怀疑column
必须这样做才能找出间距。如果您没有,column
那么 awk 必须将所有输入读取到内存中,因为我们在输出之前使用 2 遍方法来计算出每列中字段的最大长度printf
以及这些最大字段宽度。- 它不再依赖于数据中的值(除了我在当前使用 sed 管道执行的标题行中添加了
date
to 的映射creationTime
),它只依赖于一次有 4 行数据。如果更有用的话,可以轻松更改为触发点击特定的标签行,例如只需更改numTags == 4
为tag == "repo-path"
. - 它不再通过管道将列标题
sed
更改为,因为除了不需要额外的管道和命令之外,如果您的输入在任何地方包含字符串,那么这会中断,例如date
creatingTime
date
repo-path=/home/date/uploadX
- 它不再用作
=
FS 值,因为如果您的任何输入包含=
, ,例如repo-path=/home/foo=bar/uploadX
,这样做将会失败 - 如果您想
@
从数据中删除所有 s,则执行此操作的方法是使用gsub(/@/,"")
,而不是将输出通过管道传输到tr -d @
,但我认为您实际上只想对标头名称(标签)执行此操作,否则如果有任何一个,它就会中断您的数据包含@
s,例如repo-path=/home/foo@bar/uploadX
,所以我只包含从标签开头sub(/^@/,"")
删除s 。@
- 如果您想将第三个字段修剪为 10 个字符,则方法是在
substr(vals[3],1,10)
正在打印的循环之前vals[]
,而不是向第二个 awk 脚本添加管道,所以我将其包括在内。顺便说一句,第二个参数从argsubstr()
开始1
,而不是0
.
答案2
如果最后一个字段为空,您可以使用以下命令将其设置为零
if ($NF == "") $NF = 0
所以你会得到类似的东西
/^@repo-name/ {
if (++count2 == 1) header = header OFS $1 ","
if ($NF == "") $NF = 0
repoNameArr[count] = $NF
next
}
或者,为了避免重复代码,
$NF == "" { $NF = 0 }
# ...
/^@repo-name/ {
if (++count2 == 1) header = header OFS $1 ","
repoNameArr[count] = $NF
next
}
(请注意,您的数据中没有任何行匹配^@repo-name
。)
在这种情况下,我可能会采用更简单的方法。假设每条记录始终为四行,我们可以使用以下方法将数据重新排列为四个制表符分隔的列paste
:
$ cat file
size=
date=1603278566981
repo-name=testupload
repo-path=/home/test/testupload
size=140000
date=
repo-name=testupload2
repo-path=/home/test/testupload2
size=170000
date=1603278566981
repo-name=
repo-path=/home/test/testupload3
size=170000
date=1603278566981
repo-name=testupload3
repo-path=/home/test/testupload3
$ paste - - - - <file
size= date=1603278566981 repo-name=testupload repo-path=/home/test/testupload
size=140000 date= repo-name=testupload2 repo-path=/home/test/testupload2
size=170000 date=1603278566981 repo-name= repo-path=/home/test/testupload3
size=170000 date=1603278566981 repo-name=testupload3 repo-path=/home/test/testupload3
然后可以使用以下方法将其转换为 CSVmlr
(磨坊主):
$ paste - - - - <file | mlr --ifs tab --ocsv cat
size,date,repo-name,repo-path
,1603278566981,testupload,/home/test/testupload
140000,,testupload2,/home/test/testupload2
170000,1603278566981,,/home/test/testupload3
170000,1603278566981,testupload3,/home/test/testupload3
我们还可以用mlr
零替换任何缺失值:
$ paste - - - - <file | mlr --ifs tab --ocsv put 'for (k,v in $*) { is_null(v) { $[k] = 0 } }'
size,date,repo-name,repo-path
0,1603278566981,testupload,/home/test/testupload
140000,0,testupload2,/home/test/testupload2
170000,1603278566981,0,/home/test/testupload3
170000,1603278566981,testupload3,/home/test/testupload3
您是否希望使用制表符分隔值 (TSV) 代替 CSV,然后--otsv
使用--ocsv
.您可以使用--opprint
、 或 JSON--ojson
或任何您需要的方式获得“打印精美”的表格输出。
请注意,上面假设输入数据与问题中的数据类似。如果问题中的数据是结构化数据格式(例如 XML 或 JSON)中某些数据的处理变体,那么直接使用原始数据会更好。