これが私の見解です。FreeBSD でテスト済みなので、ほぼどこでも動作すると思います...
#!/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
value[]
基本的な考え方は、ネストされた章の値の配列 ( ) を維持することです。#
必要に応じて配列を更新した後、配列のその位置の現在の値でoctothorpe ( ) の最初の出現を毎回置き換えて、値をステップ実行します。
これはあらゆるレベルのネストを処理し、前述のように、awk の GNU (Linux) および非 GNU (FreeBSD、OSX など) バージョンの両方で動作するはずです。
そしてもちろん、ワンライナーがあなたのものなら、これはコンパクトにすることができます:
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'
読みやすくするために、次のように表現することもできます。
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.
アップデート
そして、これについて少し考えた後、これをさらに縮小できることに気付きました。for
ループには独自の条件が含まれているため、. 内に配置する必要はありませんif
。と
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'
もちろん、これは次のようなワンライナーに縮小されます。
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
明らかに、必要に応じて最初の一致条件を追加して、タイトルのように見える行のみを処理することができます。
数文字長いにもかかわらず、このバージョンは karakfa の同様のソリューションよりもわずかに速く実行されると思いif
ますfor
。
更新 #2
楽しくて面白いと思ったので、これを含めます。これは bash だけで実行でき、awk は必要ありません。また、コードに関してはそれほど長くはありません。
#!/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
これは上記の awk スクリプトと同じロジックに従いますが、完全に bash で動作し、パラメータ展開を使用して#
文字を置き換えます。欠点の 1 つは、すべての行の最初の単語の前後に空白が維持されないため、インデントが失われることです。少し手を加えるだけで、それも軽減される可能性があります。
楽しみ。