쉘 스크립트로 텍스트 조작: "누락된" 줄을 채우는 방법은 무엇입니까?

쉘 스크립트로 텍스트 조작: "누락된" 줄을 채우는 방법은 무엇입니까?

CSV와 같은 데이터 목록이 있지만 일부 행에 값이 누락되었습니다. Linux 쉘 스크립트를 사용하여 행 전후를 기반으로 누락된 행의 값을 생성하고 싶습니다.
이 표를 예로 들어 보겠습니다.

철사 사람들 나이
1 아담 45
2 단발 50
신디 47
4 * #
5 에드 49

내가 하고 싶은 것은 4행 48(47과 49는 C열(각 방향에서 가장 가까운 유효한 데이터 포인트)을 의미함)의 "*"를 채우는 것입니다.

산출:

철사 사람들 나이
1 아담 45
2 단발 50
신디 47
4 신디: 에드 48
5 에드 49

내 데이터는 임의의 줄 수를 포함하는 공백으로 구분된 텍스트 파일 형식입니다. 모든 행은 세 개의 열입니다.

For 루프와 grep 등을 이해하지만 일반 Linux 쉘 스크립트에서 이를 처리하는 방법을 모르겠습니다.

내 생각에는 별표와 해시가 있는 행을 찾기 위해 초기 패스를 수행하는 것입니다. 그런 다음 두 번째 단계를 수행하고 이전 및 다음 줄의 별표를 (awk '{print $2}'):(awk '{print $2}') 로 바꿉니다.

누락된 데이터가 첫 번째 또는 마지막 행에 있는 경우 그대로 두시면 됩니다. 연속 행에서 데이터가 누락된 경우 누락된 모든 행을 동일한 "Cindy:Ed" 및 동일한 평균으로 설정할 수 있습니다. "Cindy:Ed:1" 및 Cindy:Ed:2" 등을 설정할 수 있다면 더욱 멋질 것입니다.

최악의 원시 입력의 정확한 예: (손실된 지연 시간을 나타내기 위해 "#"이 추가된 추적 경로입니다.)


1 192.168.200.2 1
2 192.168.200.1 1
3 10.10.10.1 1
4 11.22.33.44 2
5 11.22.33.55 5
6 * #
7 11.22.44.66 9
8 * #
9 * #
10 8.8.8.0 25
11 * #
12 * #
13 * #

내가 원하는 것:

1 192.168.200.2 1
2 192.168.200.1 1
3 10.10.10.1 1
4 11.22.33.44 2
5 11.22.33.55 5
6 11.22.33.55:11.22.44.66 7
7 11.22.44.66 9
8 11.22.44.66:8.8.8.0 17
9 11.22.44.66:8.8.8.0 17
10 8.8.8.0 25
11 * #
12 * #
13 * #

답변1

그리고 awk:

#if a previous line with proper IP has been read
oldip != "" {
#i is counter for consecutive invalid lines
    i=0
#if IP is set, just print and go to next record
    if ($2!="*") {
        print ; oldip=$2 ; oldlat=$3 ; next
    }
#otherwise get following line and increase counter
    else {
#getline exit status => fails for the last line
        while (getline > 0) {i++
#check if new line has IP, if so
#set IPold:IPnew and average latency value
            if ($2!="*") {
                ipfill=oldip":"$2 ; latfill=(oldlat+$3)/2
#print filler lines for all consecutive records without value
                for (j=1 ; j<=i ; j++) {
                    print NR-i+j-1,ipfill,latfill
#alternative printing with oldIP:newIP:counter
#                   print NR-i+j-1,ipfill":"j,latfill
                }
#save current IP+lat and print "good" line
                oldp=$2; oldlat=$3
                print ; next
            }
        }
    }
#in case getline failed => all previous lines had no value
#just fill them with N/A data as in input
    for (j=0 ; j<=i ; j++) {
        print NR-i+j,"*","#"
    }
}

#If leading lines have no IP value, print them until IP is found
oldip == "" { if ($2=="*") {print ; next} ; oldip=$2 ; oldlat=$3 ; print }

입력하다:

1 * #
2 * #
3 10.10.10.1 1
4 11.22.33.44 2
5 11.22.33.55 5
6 * #
7 11.22.44.66 10
8 * #
9 * #
10 8.8.8.0 25
11 * #
12 * #
13 * #

산출:

1 * #
2 * #
3 10.10.10.1 1
4 11.22.33.44 2
5 11.22.33.55 5
6 11.22.33.55:11.22.44.66 7.5
7 11.22.44.66 10
8 11.22.33.55:8.8.8.0 17.5
9 11.22.33.55:8.8.8.0 17.5
10 8.8.8.0 25
11 * #
12 * #
13 * #

계산된 행 카운터를 사용한 대체 출력:

1 * #
2 * #
3 10.10.10.1 1
4 11.22.33.44 2
5 11.22.33.55 5
6 11.22.33.55:11.22.44.66:1 7.5
7 11.22.44.66 10
8 11.22.33.55:8.8.8.0:1 17.5
9 11.22.33.55:8.8.8.0:2 17.5
10 8.8.8.0 25
11 * #
12 * #
13 * #

답변2

$ cat tst.awk
$2 == "*" {
    buf[++bufSz] = $0
    next
}
bufSz > 0 {
    split(prev,p)
    rng = p[2] ":" $2
    val = ($3 + p[3]) / 2
    for (i=1; i<=bufSz; i++) {
        split(buf[i],flds)
        print (prev == "" ? buf[i] : flds[1] OFS rng OFS val)
    }
    bufSz = 0
}
{
    print
    prev = $0
}
END {
    for (i=1; i<=bufSz; i++) {
        print buf[i]
    }
}

$ awk -f tst.awk file
1 192.168.200.2 1
2 192.168.200.1 1
3 10.10.10.1 1
4 11.22.33.44 2
5 11.22.33.55 5
6 11.22.33.55:11.22.44.66 7
7 11.22.44.66 9
8 11.22.44.66:8.8.8.0 17
9 11.22.44.66:8.8.8.0 17
10 8.8.8.0 25
11 * #
12 * #
13 * #

답변3

GNU sed는 확장 정규식 모드(-E)를 사용합니다.

S='(\S+)'; _re="$S $S"
re="^$_re\\n$_re\$"
_avg='1k\4 \2+2/f'
avg='"$(echo '"'$_avg'"'|dc)"'

sed -E '
  s/^(\S+ )[*] #(\n.*\n(.*))/\1\3\2/
  ta
  s/\n.*//

  /[*] #$/b
  $!N;//!ba

  :loop
  ${//q;bb}
  N;//bloop

  :b;h
  s/\n.*\n/\n/
  s/^\S+ //Mg'"
  s#$re#echo '\1:\3' $avg#e
  x;G

  :a
  P;D
" file

Perl에서 범위 연산자 사용

perl -lane 'print,next
  unless my $e = /\d$/ ... /\d$/;
  push @A,[@F]; next
  unless $e =~ /E0/ || eof;
  if (@A>2&&$A[-1][-1] =~ /\d/) {
    my($str,$avg);
    for (0,-1) {
      $avg += $A[$_][2] / 2.0;
      $str .= $A[$_][1] . ":";
    }
    $str =~ s/.$//;
    @{$A[$_]}[1,2] = ($str,$avg)
      for 1..$#A-1;
  }
  print "@$_" for splice @A,0,@A-(eof?0:1);
  @A=(); redo if ! eof;
' file

python3 -c 'import sys, itertools as it
prev = ""
p = lambda x: print(*x,sep="",end="")
q = lambda x: x.split()
g = lambda x: x.endswith("* #\n")
with open(sys.argv[1]) as f:
  for t in it.groupby(f,g):
    G = list(t[1])
    if prev == "":
      p(G)
      if not t[0]: prev = G[-1]
    else:
      if t[0]: M = G
      else:
        a,b = map(q,[prev,G[0]])
        x = f"{a[1]}:{b[1]}"
        y = sum(map(int,[a[2],b[2]]))/2.0
        for l in M:
          for e in q(l)[0]:
            print(e,x,y)
        p(G); prev = G[-1]
  p(M)
' file

$ cat file
1 * #
2 * #
3 10.10.10.1 1
4 11.22.33.44 2
5 11.22.33.55 5
6 * #
7 11.22.44.66 10
8 * #
9 * #
10 8.8.8.0 25
11 * #
12 * #
13 * #

산출;

1 * #
2 * #
3 10.10.10.1 1
4 11.22.33.44 2
5 11.22.33.55 5
6 11.22.33.55:11.22.44.66 7.5
7 11.22.44.66 10
8 11.22.44.66:8.8.8.0 17.5
9 11.22.44.66:8.8.8.0 17.5
10 8.8.8.0 25
11 * #
12 * #
13 * #

답변4

var1=$(awk '{a[++i]=$0}/#/{for(x=NR-1;x<NR;x++)print a[x]}' file.txt | awk '{print $NF}')
var2=$(awk '/#/{x=NR+1}(NR==x){print $NF}' file.txt)

sed -i "s/#/$var3/g" file.txt
sed -i "s/\*/Cindy:Ed/g" file.txt


output

cat file.txt
line    person  age
1       Adam    45
2       Bob     50
3       Cindy   47
4       Cindy:Ed       48
5       Ed      49

관련 정보