2015-12-07 26 views
7

Mam dokument, którego potrzebuję do dynamicznego tworzenia/aktualizowania indeksów. Próbuję to osiągnąć przy pomocy awk. Mam częściowy przykład pracy, ale teraz jestem zakłopotany.dynamiczne indeksowanie dokumentów awk

Przykładowy dokument wygląda następująco.

numbers.txt: 
    #) Title 
    #) Title 
    #) Title 
    #.#) Subtitle 
    #.#.#) Section 
    #.#) Subtitle 
    #) Title 
    #) Title 
    #.#) Subtitle 
    #.#.#) Section 
    #.#) Subtitle 
    #.#.#) Section 
    #.#.#.#) Subsection 
    #) Title 
    #) Title 
    #.#) Subtitle 
    #.#.#) Section 
    #.#.#.#) Subsection 
    #.#.#.#) Subsection 

pożądany wynik byłby:

1) Title 
2) Title 
3) Title 
3.1) Subtitle 
3.1.1) Section 
3.2) Subtitle 
4) Title 
5) Title 
5.1) Subtitle 
5.1.1) Section 
5.2) Subtitle 
5.2.1) Section 
5.2.1.1) Subsection 
6) Title 
7) Title 
7.1) Subtitle 
7.1.1) Section 
7.1.1.1) Subsection 
7.1.1.2) Subsection 

Kod AWK mam który częściowo działa, jest następujący.

numbers.sh: 
    awk '{for(w=1;w<=NF;w++)if($w~/^#\)/){sub(/^#/,++i)}}1' number.txt 

Każda pomoc w tej sprawie byłaby bardzo doceniana.

+1

Myślę, że będzie to świetne pytanie dotyczące wywiadu. – karakfa

Odpowiedz

4

Zaimplementowałem skrypt AWK dla Ciebie! I nadal będzie działać dla więcej niż czterech indeksów poziomu! ;)

postaram się wyjaśnić to trochę z inline komentarze:

#!/usr/bin/awk -f 

# Clears the "array" starting from "from"          
function cleanArray(array,from){             
    for(w=from;w<=length(array);w++){           
     array[w]=0                
    }                   
}                    

# This is executed only one time at beginning.         
BEGIN {                   
    # The key of this array will be used to point to the "text index". 
    # I.E., an array with (1 2 2) means an index "1.2.2)"   
    array[1]=0  
}                    

# This block will be executed for every line.         
{                    
    # Amount of "#" found.              
    amount=0                  

    # In this line will be stored the result of the line.      
    line=""                  

    # Let's save the entire line in a variable to modify it.      
    rest_of_line=$0                

    # While the line still starts with "#"...         
    while(rest_of_line ~ /^#/){             

     # We remove the first 2 characters.          
     rest_of_line=substr(rest_of_line, 3, length(rest_of_line))    

     # We found one "#", let's count it!          
     amount++                 

     # The line still starts with "#"?          
     if(rest_of_line ~ /^#/){             
      # yes, it still starts.            

      # let's print the appropiate number and a ".".      
      line=line""array[amount]            
      line=line"."               
     }else{                 
      # no, so we must add 1 to the old value of the array.  
      array[amount]++              

      # And we must clean the array if it stores more values    
      # starting from amount plus 1. We don't want to keep     
      # storing garbage numbers that may harm our accounting    
      # for the next line.             
      cleanArray(array,amount + 1)           

      # let's print the appropiate number and a ")".      
      line=line""array[amount]            
      line=line")"               
     }                  
    }                   

    # Great! We have the line with the appropiate indexes!      
    print line""rest_of_line              
} 

Jeśli więc zapisać go jako script.awk, to można wykonać to dodanie uprawnienia do wykonywania plik:

chmod u+x script.awk 

Wreszcie, można go wykonać:

./script.awk <path_to_number.txt> 

Jako przykład, jeśli zapiszesz skrypt script.awk w tym samym katalogu, w którym znajduje się plik number.txt, następnie zmień katalog do tego katalogu i wykonaj:

./script.awk number.txt 

Tak jeśli masz ten number.txt

#) Title 
#) Title 
#) Title 
#.#) Subtitle 
#.#.#) Section 
#.#) Subtitle 
#) Title 
#) Title 
#.#) Subtitle 
#.#.#) Section 
#.#) Subtitle 
#.#.#) Section 
#.#.#.#) Subsection 
#) Title 
#) Title 
#.#) Subtitle 
#.#.#) Section 
#.#.#.#) Subsection 
#.#.#.#.#) Subsection 
#.#.#.#.#) Subsection 
#.#.#.#.#) Subsection 
#.#.#.#.#.#) Subsection 
#.#.#.#.#) Subsection 
#.#.#.#.#.#) Subsection 
#.#.#.#.#.#) Subsection 
#.#.#.#.#.#) Subsection 
#.#.#.#.#.#) Subsection 
#.#.#.#.#) Subsection 
#.#.#.#) Subsection 
#.#.#) Section 

to będzie wyjście (Zauważ, że rozwiązanie nie jest ograniczona przez ilość „#”):

1) Title 
2) Title 
3) Title 
3.1) Subtitle 
3.1.1) Section 
3.2) Subtitle 
4) Title 
5) Title 
5.1) Subtitle 
5.1.1) Section 
5.2) Subtitle 
5.2.1) Section 
5.2.1.1) Subsection 
6) Title 
7) Title 
7.1) Subtitle 
7.1.1) Section 
7.1.1.1) Subsection 
7.1.1.1.1) Subsection 
7.1.1.1.2) Subsection 
7.1.1.1.3) Subsection 
7.1.1.1.3.1) Subsection 
7.1.1.1.4) Subsection 
7.1.1.1.4.1) Subsection 
7.1.1.1.4.2) Subsection 
7.1.1.1.4.3) Subsection 
7.1.1.1.4.4) Subsection 
7.1.1.1.5) Subsection 
7.1.1.2) Subsection 
7.1.2) Section 

Mam nadzieję, że ci to pomoże!

+1

Facundo, to wspaniale, że poświęcasz swój czas w ten sposób. Aby zachować dobrą wolę i przydatność we wszystkich kierunkach, czy mogę zalecić dodanie wyjaśnienia do odpowiedzi, aby działał on jako narzędzie edukacyjne, a także jako rozwiązanie jednego problemu opisanego w pytaniu? – ghoti

+0

Dziękuję Ghoti! Oczywiście, pozwól mi to zaktualizować. –

4

awk na ratunek!

nie jestem pewien, że to optymalny sposób w ten sposób, ale działa ...

awk 'BEGIN{d="."} 
/#\.#\.#\.#/ {sub("#.#.#.#", i d a[i] d b[i d a[i]] d (++c[i d a[i] d b[i d a[i]]]))} 
    /#\.#\.#/ {sub("#.#.#" , i d a[i] d (++b[i d a[i]]))} 
     /#\.#/ {sub("#.#" , i d (++a[i]))} 
     /#/ {sub("#"  , (++i))} 1' 

UPDATE: Powyższy ogranicza się tylko do 4 poziomów. Tutaj jest lepszy dla nieograniczonej liczby poziomów

awk '{d=split($1,a,"#")-1;    # find the depth 
     c[d]++;        # increase counter for current   
     for(i=pd+1;i<=d;i++) c[i]=1;  # reset when depth increases 
     for(i=1;i<=d;i++) {sub(/#/,c[i])}; # replace digits one by one 
     pd=d} 1'       # set previous depth and print 

może przywrócić czynności mogą być łączone z pętli głównej, ale myślę, że w ten sposób wyraźniejszy.

UPDATE 2:

myślę z tą logiką, po to najkrótszy możliwy.

$ awk '{d=split($1,_,"#")-1;  # find the depth 
     c[d]++;     # increment counter for current depth 
     for(i=1;i<=d;i++)   # start replacement 
      {if(i>pd)c[i]=1;  # reset the counters 
      sub(/#/,c[i])   # replace placeholders with counters 
      } 
      pd=d} 1' file   # set the previous depth 

lub jako jedną wkładką

$ awk '{d=split($1,_,"#")-1;c[d]++;for(i=1;i<=d;i++){if(i>pd)c[i]=1;sub(/#/,c[i])}pd=d}1' 
1

Oto kolejny sposób, aby to zrobić.

Wyjaśnienie znajduje się poniżej kodu.

awk 'BEGIN {n0=1; prev=0} 
    {n1=split($1, elems, "."); # Get the number of pound signs 
    dif = (n1-n0);    # Increase in topic depth from previous line 
    scale = (10^dif);  # 10 raised to dif 
    current=(int(prev*scale)+1); # scale the number by change in depth 
    withdots=gensub(/([0-9])/, "\\1." , "g", current); # dot between digits 
    {print withdots, $2 } 
    n0=n1; 
    prev=current}' number.txt 


1) Title 
2) Title 
3) Title 
3.1) Subtitle 
3.1.1) Section 
3.2) Subtitle 
4) Title 

Rozważ numery tematów jako liczby dziesiętne.
Dostajemy aktualny numer z poprzedniego za pomocą wzoru 10^dif + 1,

gdzie DIF = (Increase in number of levels from previous line) Początkowo dif wynosi zero, więc mamy 2 od 1 do 3 z 2,
przez 1 * (10^0) +1 = 1 * 1 + 1 = 2
i 2 * (10^0) +1 = 2 * 1 + 1 = 3

Następnie otrzymujemy 31 od 3 przez 3 * (10^1) + 1
32 z 311 przez 311 * (10^-1) + 1 i tak dalej

+0

Należy zauważyć, że to rozwiązanie działa w GNU awk (gawk), ale nie awk w OSX, FreeBSD itp. 'Gensub' nie jest przenośny. – ghoti

2

gawk

awk 'function w(){ 
    k=m>s?m:s 
    for(i=1;i<=k;i++){ 
     if(i>m){ 
      a[i]=0 
     } 
     else{ 
      a[i]=(i==m)?++a[i]:a[i] #ended "#" increase 
      sub("#",a[i]=a[i]?a[i]:1) 
     } 
    } 
    s=m 
} 
{m=split($1,t,"#")-1;w()}1' file 



1) Title 
2) Title 
3) Title 
3.1) Subtitle 
3.1.1) Section 
3.2) Subtitle 
4) Title 
5) Title 
5.1) Subtitle 
5.1.1) Section 
5.2) Subtitle 
5.2.1) Section 
5.2.1.1) Subsection 
6) Title 
7) Title 
7.1) Subtitle 
7.1.1) Section 
7.1.1.1) Subsection 
7.1.1.2) Subsection 
2

samo podejście jak na @ karakfa (krótkie i słodkie) i tym samym zastrzeżeniem, o zakładanej maksymalnej liczby podpozycjami, ale trochę krótsze i bardziej efektywny:

awk 'BEGIN{d="."} 
    /#\.#\.#\.#/ {sub("#.#.#.#", i d a d b d (++c))} 
    /#\.#\.#/ {sub("#.#.#" , i d a d (++b)); c=0;} 
     /#\.#/ {sub("#.#" , i d (++a));  b=0;} 
      /#/ {sub("#"  , (++i));   a=0;} 1' 
+0

inteligentne ustalanie chaosu macierzy. – karakfa

2

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ę.