5

Using this file, I would like to print a tree of package dependencies, given a single base package. For example, take the Bash package

@ bash
# few lines removed
requires: coreutils libintl8 libncursesw10 libreadline7 _update-info-dir cygwin

I would like find-like output of the required packages, partial example

bash
bash coreutils
bash coreutils libattr1
bash coreutils libattr1 libintl8
bash coreutils libattr1 libintl8 libiconv2
bash coreutils libattr1 libintl8 _autorebase
bash coreutils libattr1 libintl8 _autorebase rebase
bash coreutils libattr1 libintl8 _autorebase rebase dash
bash coreutils libattr1 libintl8 _autorebase rebase dash cygwin
bash coreutils libattr1 libintl8 _autorebase rebase dash cygwin base-cygwin

I have this command but it does not recurse

#!awk -f
$1 == "@" {
  pkg = $2
}
$1 == "requires:" {
  for (i=2; i<=NF; i++)
    reqs[pkg][i-1] = $i
}
END {
  query = "bash"
  for (pkg in reqs[query]) {
    print reqs[query][pkg]
  }
}
Zombo
  • 1
  • 62
  • 391
  • 407
  • 1
    I would start with BEGIN{RS="@";FS="\n"} and compare $i to "requires:" where $1 will be the name to use in your associative array and some field i (use a for loop or whatever) will begin with require - use substr to remove before the ":" and store that as the value.... then in END you will use the associative array to print the values recursively - but beware of circular dependencies – technosaurus May 16 '13 at 06:09

3 Answers3

6

With Perl and no comments:

perl -lne '
  $k = $1 if /@\s*(\S+)/; 
  @r=split(); shift @r; $r{$k} = [@r] if /requires:/;
  END{
    $p = "bash"; @l = ( [$p, 0] );
    while ($p = pop @l) {
        next if $d{$p->[0]}++;
        print " " x $p->[1] . $p->[0];
        for $d(@{$r{$p->[0]}}) {
            push @l, [ $d, $p->[1]+1 ];
        }
    }
  }' setup.ini

Awk version:

awk '/^@ / { split($0, b); k = b[2]; }
     /^requires: / { a[k] = $0; }
     END {
       p[1] = "bash"; d["bash"] = 0;
       while (length(p)) {
           key = p[length(p)]; depth = d[key]; delete p[length(p)];
           if (!s[key]++) {
               printf "%*s %s\n", depth, "", key;
               split(a[key], r); delete r[1];
               for (req in r) {
                   p[length(p) + 1] = r[req]; d[r[req]] = depth + 1;
               }
           }
       }
     }
' setup.ini
Zombo
  • 1
  • 62
  • 391
  • 407
perreal
  • 94,503
  • 21
  • 155
  • 181
3

Using GNU awk for true multi-D arrays (but can obviously be tweaked for other awks):

$ cat tst.awk
/^@/         { pkg = $2 }
/^requires:/ { for (i=2;i<=NF;i++) reqs[pkg][$i]  }
END          { prtPkg(root) }

function prtPkg(pkg,    req) {
    if (!seen[pkg]++) {
        printf "%*s%s\n", indent, "", pkg
        indent += 2
        if (pkg in reqs)
            for (req in reqs[pkg])
                prtPkg(req)
        indent -= 2
    }
}

.

$ cat file3
@ bash
whatever
requires: libgcc1 libintl8 libncursesw10 libreadline7 _update-info-dir

@ libintl8
whatever
requires: libiconv2 bash common

@ libgcc1
whatever
requires: _autorebase common

.

$ awk -v root="bash" -f tst.awk file3
bash
  libncursesw10
  libgcc1
    _autorebase
    common
  _update-info-dir
  libreadline7
  libintl8
    libiconv2

Here is what we'd need for the new output format you'd like:

$ cat file3
@ bash
requires: libgcc1 libintl8 libncursesw10 libreadline7

@ libncursesw10
requires: libgcc1 libstdc++6 terminfo libreadline7

.

$ cat tst.awk
/^@/         { pkg = $2 }
/^requires:/ { for (i=2;i<=NF;i++) reqs[pkg][i-1]=$i  }
END          { setMinDepth(root); prtPkg(root) }

function setMinDepth(pkg,    req, i) {
    depth++
    minDepth[pkg] = ( !(pkg in minDepth) || (depth < minDepth[pkg]) ?
                        depth : minDepth[pkg])

    if (depth == minDepth[pkg]) {
        if (pkg in reqs)
            for (i=1; i in reqs[pkg]; i++) {
                req = reqs[pkg][i]
                setMinDepth(req)
            }
    }
    depth--
}

function prtPkg(pkg,    req, i) {
    depth++
    if ( (depth == minDepth[pkg]) && (!seen[pkg]++) ) {
        printf "%*s%s\n", indent, "", pkg
        indent += 2
        if (pkg in reqs)
            for (i=1; i in reqs[pkg]; i++) {
                req = reqs[pkg][i]
                prtPkg(req)
            }
        indent -= 2
    }
    depth--
}

.

$ awk -v root="bash" -f tst.awk file3
bash
  libgcc1
  libintl8
  libncursesw10
    libstdc++6
    terminfo
  libreadline7
Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • FYI functionally the difference between the 2 ways of saving `reqs[pkg]` is that if you use `reqs[pkg][$i]` it removes duplicates but does not preserve order while `reqs[pkg][i-1]=$i` does not remove duplicates but does preserve order. If you want to use the 2nd approach AND remove duplicates, the simplest thing is a second tmp "seen" array `seenFld[$i]++` checked during population and deleted afterwards before the next record is read and then using a tmp count variable `++j` instead of `i-1` for the array index. – Ed Morton May 27 '14 at 14:32
  • You might have a circular dependency declared in your file. That would cause infinite recursion in setMinDepth(). I updated the script above with a fix. – Ed Morton May 28 '14 at 05:57
0
#!/usr/bin/awk -f
@include "join"
$1 == "@" {
  apg = $2
}
$1 == "requires:" {
  for (z=2; z<=NF; z++)
    reqs[apg][z-1] = $z
}
END {
  prpg("bash")
}
function smartmatch(small, large,    values) {
  for (each in large)
    values[large[each]]
  return small in values
}
function prpg(fpg) {
  if (smartmatch(fpg, spath)) return
  spath[length(spath)+1] = fpg
  print join(spath, 1, length(spath))
  if (isarray(reqs[fpg]))
    for (each in reqs[fpg])
      prpg(reqs[fpg][each])
  delete spath[length(spath)]
}

Source

Zombo
  • 1
  • 62
  • 391
  • 407