Oto moje zdanie na ten temat. Testowany w FreeBSD, więc oczekiwać, że będę pracować dosłownie wszędzie ...
#!/usr/bin/awk -f
BEGIN {
depth=1;
}
$1 ~ /^#(\.#)*\)$/ {
thisdepth=split($1, _, ".");
if (thisdepth < depth) {
# end of subsection, back out to current depth by deleting array values
for (; depth>thisdepth; depth--) {
delete value[depth];
}
}
depth=thisdepth;
# Increment value of last member
value[depth]++;
# And substitute it into the current line.
for (i=1; i<=depth; i++) {
sub(/#/, value[i], $0);
}
}
1
Podstawowym założeniem jest to, że utrzymuje tablicę (value[]
) naszego zagnieżdżonych wartości rozdziałów. Po aktualizacji tablicy zgodnie z wymaganiami, przechodzimy przez wartości, zastępując pierwsze wystąpienie oktothorpe (#
) za każdym razem z bieżącą wartością dla tej pozycji tablicy.
To zajmie każdy poziom zagnieżdżenia i jak już wspomniałem, powinien działać zarówno w wersjach awk GNU (Linux), jak i innych niż GNU (FreeBSD, OSX, itp.).
I oczywiście, jeżeli jedna wkładki są twoje rzeczy, to może być zagęszczony:
awk -vd=1 '$1~/^#(\.#)*\)$/{t=split($1,_,".");if(t<d)for(;d>t;d--)delete v[d];d=t;v[d]++;for(i=1;i<=d;i++)sub(/#/,v[i],$0)}1'
które mogłyby również być wyrażona, w celu łatwiejszego czytania, tak:
awk -vd=1 '$1~/^#(\.#)*\)$/{ # match only the lines we care about
t=split($1,_,"."); # this line has 't' levels
if (t<d) for(;d>t;d--) delete v[d]; # if levels decrease, trim the array
d=t; v[d]++; # reset our depth, increment last number
for (i=1;i<=d;i++) sub(/#/,v[i],$0) # replace hash characters one by one
} 1' # and print.
UPDATE
Po krótkim rozmyślaniu, zdaję sobie sprawę, że można go jeszcze bardziej zmniejszyć. Pętla for
zawiera swój własny stan, nie ma potrzeby umieszczania go wewnątrz urządzenia if
.I
awk '{
t=split($1,_,"."); # get current depth
v[t]++; # increment counter for depth
for(;d>t;d--) delete v[d]; # delete record for previous deeper counters
d=t; # record current depth for next round
for (i=1;i<=d;i++) sub(/#/,v[i],$0) # replace hashes as required.
} 1'
Które minifies kurs do jednej liniowej jak ten:
awk '{t=split($1,_,".");v[t]++;for(;d>t;d--)delete v[d];d=t;for(i=1;i<=d;i++)sub(/#/,v[i],$0)}1' file
Oczywiście, można dodać warunek początkowy mecz, jeśli tego wymagają, tak, że tylko linie technologiczne, które wyglądają jak tytuły .
Pomimo kilku znaków dłuższych, uważam, że ta wersja działa trochę wolniej niż podobne rozwiązanie Karakfy, prawdopodobnie dlatego, że unika dodatkowej wartości if
dla każdej iteracji pętli for
.
UPDATE # 2
I to ten, bo to, bo uważam, że to zabawa i ciekawy. Możesz to zrobić w bashu sam, bez potrzeby awk. I to nie jest dużo dłużej pod względem kodu.
#!/usr/bin/env bash
while read word line; do
if [[ $word =~ [#](\.#)*\) ]]; then
IFS=. read -ra a <<<"$word"
t=${#a[@]}
((v[t]++))
for ((; d > t ; d--)); do unset v[$d]; done
d=t
for ((i=1 ; i <= t ; i++)); do
word=${word/[#]/${v[i]}}
done
fi
echo "$word $line"
done < input.txt
Wynika to z samej logiki jak skrypt awk powyżej, ale działa całkowicie w bash za pomocą parametru Expansion zastąpić #
znaków. Jedną z wad jest to, że nie utrzymuje białych znaków wokół pierwszego słowa na każdej linii, więc stracisz wszystkie wcięcia. Przy odrobinie pracy można to złagodzić.
Ciesz się.
Myślę, że będzie to świetne pytanie dotyczące wywiadu. – karakfa