Classifique um arquivo ao agrupar linhas recuadas com seu pai (vários níveis)

2

Todos os níveis devem ser classificados em ordem alfabética (mas devem ser mantidos com seus pais)

Exemplo de arquivo:

first
    apple
    orange
        train
        car
    kiwi
third
    orange
    apple
        plane
second
    lemon

Resultado esperado:

first
    apple
    kiwi
    orange
        car
        train
second
    lemon
third
    apple
        plane
    orange

O comando a seguir foi usado, mas funciona somente se o arquivo tiver apenas dois níveis na árvore.

sed '/^[^[:blank:]]/h;//!G;s/\(.*\)\n\(.*\)/\x02/' infile | sort | sed 's/.*\x02//'

Como posso fazer para classificar todos os níveis corretamente?

Obrigado antecipadamente

    
por nick10 03.07.2018 / 18:59

4 respostas

1

Solução estendida Python :

Amostra infile contents (4 levels):

first
    apple
    orange
        train
        car
            truck
            automobile
    kiwi
third
    orange
    apple
        plane
second
    lemon
Script

sort_hierarchy.py :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import re

with open(sys.argv[1], 'rt') as f:
    pat = re.compile(r'^\s+')
    paths = []

    for line in f:
        offset = pat.match(line)
        item = line.strip()

        if not offset:
            offset = 0
            paths.append(item)
        else:
            offset = offset.span()[1]
            if offset > prev_offset:
                paths.append(paths[-1] + '.' + item)
            else:
                cut_pos = -prev_offset//offset
                paths.append('.'.join(paths[-1].split('.')[:cut_pos]) + '.' + item)

        prev_offset = offset

    paths.sort()
    sub_pat = re.compile(r'[^.]+\.')
    for i in paths:
        print(sub_pat.sub(' ' * 4, i))

Uso:

python sort_hierarchy.py path/to/infile

A saída:

first
    apple
    kiwi
    orange
        car
            automobile
            truck
        train
second
    lemon
third
    apple
        plane
    orange
    
por 04.07.2018 / 00:00
0

Awk solução:

Amostra infile contents (4 levels):

first
    apple
    orange
        train
        car
            truck
            automobile
    kiwi
third
    orange
    apple
        plane
second
    lemon
awk '{
         offset = gsub(/ /, "");
         if (offset == 0) { items[NR] = $1 }
         else if (offset > prev_ofst) { items[NR] = items[NR-1] "." $1 }
         else {
             prev_item = items[NR-1];
             gsub("(\.[^.]+){" int(prev_ofst / offset) "}$", "", prev_item);
             items[NR] = prev_item "." $1
         }
         prev_ofst = offset;
     }
     END{
         asort(items);
         for (i = 1; i <= NR; i++) {
             gsub(/[^.]+\./, "    ", items[i]);
             print items[i]
         }
     }' infile

A saída:

first
    apple
    kiwi
    orange
        car
            automobile
            truck
        train
second
    lemon
third
    apple
        plane
    orange
    
por 04.07.2018 / 00:06
0

funciona para qualquer profundidade

#!/usr/bin/python3
lines = open('test_file').read().splitlines()

def yield_sorted_lines(lines):
        sorter = []
        for l in lines:
                fields = l.split('\t')
                n = len(fields)
                sorter = sorter[:n-1] + fields[n-1:]
                yield sorter, l


prefixed_lines = yield_sorted_lines(lines)
sorted_lines = sorted(prefixed_lines, key=lambda x: x[0])
for x, y in sorted_lines:
        print(y)

Ou um pipeline

awk -F'\t' '{a[NF]=$NF; for (i=1; i<=NF; ++i) printf "%s%s", a[i], i==NF? "\n": "\t"}' file|
sort | awk -F'\t' -vOFS='\t' '{for (i=1; i<NF; ++i) $i=""; print}'
    
por 04.07.2018 / 13:40
0
sed '/^ /{H;$!d};x;1d;s/\n/\x7/g' | sort | tr \a \n

O /continuation/{H;$!d};x;1d (ou /firstline/! etc) é um "slurp", ele só passa quando tem uma linha completa de bando no buffer.

Se você conseguir um bando de linha única no final, adicione ${p;x;/\n/d} para fazer a bomba dupla necessária para isso.

    
por 04.07.2018 / 21:40