Ecco la mia opinione su questo. Testato in FreeBSD, quindi mi aspetto di lavorare un po 'ovunque ...
#!/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
L'idea di base è che manteniamo un array (value[]
) dei nostri valori capitolo nidificate. Dopo aver aggiornato la matrice come richiesto, passiamo attraverso i valori, sostituendo la prima occorrenza dell'ottotipo (#
) ogni volta con il valore corrente per quella posizione dell'array.
Questo gestirà qualsiasi livello di nidificazione e, come ho già detto, dovrebbe funzionare sia in GNU (Linux) che in versioni non GNU (FreeBSD, OSX, ecc.) Di awk.
E, naturalmente, se one-liners sono la vostra passione, questo può essere compresso:
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'
che potrebbe anche essere espressa, per facilitarne la lettura, in questo modo:
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
E dopo aver riflettuto su questo per un po ', mi rendo conto che questo può essere ridotto ulteriormente. Il ciclo for
contiene le sue condizioni, non è necessario collocarlo all'interno di uno if
.E
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'
Quale di minifies corso in uno di linea come questa:
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
Ovviamente, è possibile aggiungere la condizione partita iniziale, se lo richiedono, in modo che solo le linee di processo che sembrano titoli .
Nonostante alcuni caratteri siano più lunghi, credo che questa versione sia sempre leggermente più veloce della soluzione simile di karakfa, probabilmente perché evita l'ulteriore if
per ogni iterazione del ciclo for
.
UPDATE # 2
includo questo perché questo perché ho trovato divertente e interessante. Puoi farlo solo in bash, senza bisogno di awk. E non è molto più in termini di codice.
#!/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
Questo segue la stessa logica dello script awk sopra, ma funziona interamente in bash utilizzando il parametro di espansione per sostituire #
caratteri. Un difetto di cui soffre è che non mantiene gli spazi bianchi attorno alla prima parola su ogni riga, quindi perdi qualsiasi rientro. Con un po 'di lavoro, anche questo potrebbe essere mitigato.
Divertiti.
Penso che questa sarà una grande domanda per il colloquio. – karakfa