次のデータを含む詳細.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
サイズ/日付/ストレージ名/ストレージパスに値がない場合は、0を印刷する必要があると追加したいと思います。
私はawkスクリプトに以下を追加しようとしましたが、うまくいかずにどのように取得するのかわかりません。
}
/^@repo-name/{
if(++count2==1){ header=header OFS $1"," }
if(-z "${repo-name}") ; then
repoNameArr=0
repoNameArr[count]=$NF
next
}
awkスクリプトでどのように使用するのかわかりません。助けてもらえますか?
サイズ/日付/ストレージ名/ストレージパスの値がない場合、最終出力には0を印刷する必要があります。
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
マッピングを追加したことを除きcreationTime
)、一度に4つのデータ行のみが必要です。これがより便利な場合は、特定のタグ行のクリックをトリガーするように簡単に変更できます。たとえば、numTags == 4
に変更しますtag == "repo-path"
。 sed
追加のパイプやコマンドが必要ないだけでなく、入力に文字列が含まれていると中断されるため、列ヘッダーをパイプしなくなります。date
creatingTime
date
repo-path=/home/date/uploadX
- たとえば、
=
入力にが含まれていると失敗するため、FS値として使用されなくなります。=
repo-path=/home/foo=bar/uploadX
- データからすべてのsを削除するには、出力をパイピングする代わりに
@
使用しますが、実際にはヘッダー名(タグ)に対してのみこれを実行したいようです。それ以外の場合はどちらでも可能です。たとえば、sを含むデータを壊す可能性があるため、タグの先頭にsを含めて削除しました。gsub(/@/,"")
tr -d @
@
repo-path=/home/foo@bar/uploadX
sub(/^@/,"")
@
- 3番目のフィールドを10文字に切り捨てるには、パイプを2番目のawkスクリプトに追加するのではなく、印刷するループの前に
substr(vals[3],1,10)
これを行う方法があるため、これを含めます。vals[]
しかし、2番目の引数はargではなくargでsubstr()
始まります。1
0
答え2
最後のフィールドが空の場合は、次のように0に設定できます。
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
。)
この場合、おそらくより簡単なアプローチを選択します。各レコードが常に4行であると仮定すると、データをタブで区切られた4列に並べ替えるには、次のようにします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
その後、次の方法を使用してCSVに変換できますmlr
(ミラー):
$ 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
CSVの代わりにタブ区切り値(TSV)を使用するには、JSONまたは「きれいな印刷」表形式の出力を取得するために必要なものをすべて--otsv
使用--ocsv
できます。--opprint
--ojson
上記は、入力データが質問のデータと類似していると仮定しています。質問のデータが構造化データ型(XMLやJSONなど)の一部のデータを処理したバリアントである場合は、元のデータを直接使用することをお勧めします。