【问题标题】:Indented lines (tree) to path-like lines缩进线(树)到类似路径的线
【发布时间】:2014-03-26 07:15:10
【问题描述】:

我有如下结构的输入文件:

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

它就像一个文件系统,如果下一行有更大的缩进,当前的就像一个“目录”,当它有相同的缩进时它就像一个“文件”。需要打印“文件”的完整路径。

尝试在不使用任何高级语言(例如 pythonperl)的情况下解决此问题 - 仅使用基本的 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
)

抱歉,代码太长了 - 有人可以帮忙吗?

【问题讨论】:

  • 放弃简单的方法并寻找Trying to solve this without any high-level language, like python, perl - with only basic bash commands.的意义何在
  • 自行设置限制,仅用于锻炼。
  • @BMW 不理解您的观点。简单的没有perl 也没有python 并且不知道awk。因此,尝试使用我所知道和拥有的工具来解决问题。这有什么问题?如果您可以帮助我解决 awk 解决方案,我会很高兴...为什么要投票?
  • @cajwine 你没有在你的问题中提到 awk 是“不允许的”,我也看到你在你的脚本中使用了sed。我假设我的 awk 答案不会被“列入黑名单”,对吧? ;)
  • @Kent SURE,awk 如果没问题(我记得你常用的 awk 解决方案,最多 60 个字符...;)

标签: bash recursion tree


【解决方案1】:

有趣的问题。

这个 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    

【讨论】:

  • 正如我上面所说的。 ;) 非常紧凑,做应该做的事。一定要学awk真的很强大。老实说,有问题了解它(还),但承诺 - 会学习。 :)
【解决方案2】:

我最近不得不做一些类似的事情,通过一些调整我可以在这里发布我的脚本:

#!/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

输入来自 STDIN,因此您必须执行以下操作:

$ ./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
$ 

【讨论】:

  • 这是令人难以置信的优雅。没有任何复杂的递归。谢谢你。 :)
猜你喜欢
  • 1970-01-01
  • 2017-05-30
  • 2014-07-29
  • 1970-01-01
  • 2021-02-02
  • 2011-10-13
  • 2014-01-22
  • 2021-10-17
  • 1970-01-01
相关资源
最近更新 更多