2014-03-25 57 views
4

我有像下一結構輸入文件:縮進線(樹)到路徑狀的線

a1 
    b1 
    c1 
    c2 
    c3 
    b2 
    c1 
     d1 
     d2 
    b3 
    b4 
a2 
a3 
    b1 
    b2 
    c1 
    c2 

每級由2個空格縮進。所需的輸出是:

a1/b1/c1 
a1/b1/c2 
a1/b1/c3 
a1/b2/c1/d1 
a1/b2/c1/d2 
a1/b3 
a1/b4 
a2 
a3/b1 
a3/b2/c1 
a3/b2/c2 

它就像一個文件系統,如果下一行有較大的缺口,當前的就像是一個「目錄」,當有相同縮進它就像一個「文件」。需要打印「文件」的完整路徑。

試圖解決這個沒有任何高級語言,如python,perl - 只有基本的bash命令。

我目前的代碼/想法是基於遞歸函數調用和使用堆棧,但有「邏輯」的問題。該代碼目前輸出下一個:

a1 b1 c1 
a1 b1 
a1 

DD: line 8: [0-1]: bad array subscript 

只有一號線是確定 - 所以在處理遞歸是錯誤的...

input="ifile.tree" 

#stack array 
declare -a stack 

#stack manipulation 
pushstack() { stack+=("$1"); } 
popstack() { unset stack[${#stack[@]}-1]; } 
printstack() { echo "${stack[*]}"; } 

#recursive function 
checkline() { 
    local uplev=$1 

    #read line - if no more lines - print the stack and return 
    read -r level text || (printstack; exit 1) || return 

    #if the current line level is largest than previous level 
    if [[ $uplev < $level ]] 
    then 
     pushstack "$text" 
     checkline $level #recurse 
    fi 

    printstack 
    popstack 
} 

# MAIN PROGRAM 

# change the input from indented spaces to 
# level_number<space>text 
(
    #subshell - change IFS 
    IFS=, 
    while read -r spaces content 
    do 
     echo $(((${#spaces}/2) + 1)) "$content" 
    done < <(sed 's/[^ ]/,&/' < "$input") 

) | ( #pipe to another subshell 
    checkline 0 #recurse by levels 
) 

Sry基因的長碼 - 任何人可以幫助?

+0

有什麼要放棄簡單的方法和尋找'試圖解決這個wi沒有任何高級語言,比如python,perl - 只有基本的bash命令。「# – BMW

+0

設置了限制自我僅用於練習。 – BMW

+0

@寶馬不理解你的觀點。簡單的''perl'也不''python'並且不知道awk。所以試着用我所知道的工具來解決問題。這有什麼問題?如果你可以用'awk'解決方案來幫助我,我會很高興......爲什麼選擇近距離投票? – cajwine

回答

5

有趣的問題。

這AWK(可能是一行程序)命令做這項工作:

awk -F' ' 'NF<=p{for(i=1;i<=p;i++)printf "%s%s", a[i],(i==p?RS:"/") 
      if(NF<p)for(i=NF;i<=p;i++) delete a[i]} 
      {a[NF] =$NF;p=NF } 
      END{for(i=1;i<=NF;i++)printf "%s%s", a[i],(i==NF?RS:"/")}' file 

,你可以在上面看到,有重複的代碼,你可以解壓縮到一個功能,如果你喜歡。

測試與您的數據:

kent$ cat f 
a1 
    b1 
    c1 
    c2 
    c3 
    b2 
    c1 
     d1 
     d2 
    b3 
    b4 
a2 
a3 
    b1 
    b2 
    c1 
    c2 

kent$ awk -F' ' 'NF<=p{for(i=1;i<=p;i++)printf "%s%s", a[i],(i==p?RS:"/") 
if(NF<p)for(i=NF;i<=p;i++) delete a[i]} 
{a[NF] =$NF;p=NF }END{for(i=1;i<=NF;i++)printf "%s%s", a[i],(i==NF?RS:"/")} ' f 
a1/b1/c1 
a1/b1/c2 
a1/b1/c3 
a1/b2/c1/d1 
a1/b2/c1/d2 
a1/b3 
a1/b4 
a2 
a3/b1 
a3/b2/c1 
a3/b2/c2  
+0

正如我上面告訴的那樣。 ;)非常緊湊,做什麼應該。必須學習'awk'這真的非常強大。老實說,有一個問題了解它(但),但承諾 - 將學習。 :) – cajwine

+1

關心詳細說明awk腳本的工作原理? – lesmana

4

最近,我不得不這樣做非常相似,以至於有一些調整,我可以張貼我的劇本的東西在這裏:

#!/bin/bash 

prev_level=-1 
# Index into node array 
i=0 

# Regex to screen-scrape all nodes 
tc_re="^(()*)(.*)$" 
while IFS= read -r ln; do 
    if [[ $ln =~ $tc_re ]]; then 
     # folder level indicated by spaces in preceding node name 
     spaces=${#BASH_REMATCH[1]} 
     # 2 space characters per level 
     level=$(($spaces/2)) 
     # Name of the folder or node 
     node=${BASH_REMATCH[3]}   
     # get the rest of the node path from the previous entry 
     curpath=(${curpath[@]:0:$level} $node) 

     # increment i only if the current level is <= the level of the previous 
     # entry 
     if [ $level -le $prev_level ]; then 
      ((i++)) 
     fi 

     # add this entry (overwrite previous if $i was not incremented) 
     tc[$i]="${curpath[@]}" 

     # save level for next iteration 
     prev_level=$level 
    fi 
done 

for p in "${tc[@]}"; do 
    echo "${p// //}" 
done 

輸入是從標準輸入獲得,所以在你必須這樣做:

$ ./tree2path.sh < ifile.tree 
a1/b1/c1 
a1/b1/c2 
a1/b1/c3 
a1/b2/c1/d1 
a1/b2/c1/d2 
a1/b3 
a1/b4 
a2 
a3/b1 
a3/b2/c1 
a3/b2/c2 
$ 
+0

這是UNBELIEVING優雅。沒有任何複雜的遞歸。謝謝。 :) – cajwine