user3021851
user3021851

Reputation: 141

Formatting text in BASH

I am absolute beginner to Shell scripting. My task is to make a script, which will show functions used in a file (caller and callee). I have used objdump, grep, awk etc. to get this output:

000000000040090d <usage>:
000000000040095d <failure>:
  400970:   e8 98 ff ff ff          callq  40090d <usage>
000000000040097f <strton>:
  4009bc:   e8 9c ff ff ff          callq  40095d <failure>
00000000004009c6 <main>:
  400a0e:   e8 6c ff ff ff          callq  40097f <strton>
  400a26:   e8 32 ff ff ff          callq  40095d <failure>
  400a41:   e8 39 ff ff ff          callq  40097f <strton>
  400a59:   e8 ff fe ff ff          callq  40095d <failure>
  400a9a:   e8 be fe ff ff          callq  40095d <failure>
  400aae:   e8 cc fe ff ff          callq  40097f <strton>
  400ac2:   e8 b8 fe ff ff          callq  40097f <strton>
  400ad1:   e8 87 fe ff ff          callq  40095d <failure>
  400afe:   e8 fe 01 00 00          callq  400d01 <set_timeout>
  400b1c:   e8 3c fe ff ff          callq  40095d <failure>
  400b26:   e8 19 00 00 00          callq  400b44 <print_fib_upto>
  400b37:   e8 89 00 00 00          callq  400bc5 <print_ackermann>

Okay, the result should look like this:

failure -> usage
strton -> failure
main -> failure
main -> print_ackermann
main -> print_fib_upto
main -> set_timeout
main -> strton

But I have no idea how to accomplish it. I'd know how to do it in C, etc, but not here. I think this is the right pseudo-code.

If (end of line == ">:")
         caller == last column;
         while (end of line == ">") {
                   callee == last column;
                   echo "$caller -> $callee"
         }

Can anyone tell me, how to write this in BASH? Thank you very much, Maybe it's a silly question, but I don't know anyting about shell yet.

Upvotes: 4

Views: 231

Answers (6)

user2766918
user2766918

Reputation: 584

Already answered, but for pure bash, it could be done as follows:

#!/bin/bash
while read line
do
    [[ "$line" =~ \<(.*)\>\:$ ]] && caller=${BASH_REMATCH[1]}
    [[ "$line" =~ \<(.*)\>$ ]] && callee=${BASH_REMATCH[1]}
    [ -n $caller -a -n $callee ] && echo "$caller -> $callee";
    callee=""
done < $1

The [[ expr ]] && means test expr, and if true, run what's afterwards. The =~ is a regexp matcher, where the value in the ()'s is stored in ${BASH_REMATCH[1]}.

Upvotes: 1

Emmet
Emmet

Reputation: 6421

The whole thing can be done with sed reasonably easily, negating the need for preprocessing with grep, etc. The following isn't perfect (you may have to tweak regexes and what is/isn't considered a match), but it should break the back of it.

#!/bin/bash

symbol='[_A-Za-z][_@A-Za-z0-9]\+'

objdump -d "$1" | sed -n "
/^[0-9a-f]\+ <\(${symbol}\)>:/{ # Match symbol in header
    s//\1/                      # Symbol only in pattern space
    h                           # Save symbol to hold space
    : loop                      # (until empty line)
    n                           # Next line
    /^$/!{                      # If not an empty line
        # Try matching 'callq' line and extract symbol:
        /^[:[:space:][:xdigit:]]\+callq[^<]\+<\($symbol\)>/{
            s//\1/              # Symbol only in pattern space
            G                   # Append hold space to pattern space
            # Swap words in pattern space, adding separator:
            s/\($symbol\)[[:space:]]\($symbol\)/\2 -> \1/g
            p                   # Print pattern space:
        }
        b loop                  # Try next line
    }
}"

Upvotes: 2

jaypal singh
jaypal singh

Reputation: 77185

You can try this awk:

$ awk 'NF==2 {
    gsub(/[[:punct:]]/, "", $NF)
    caller = $NF
    next
}
{
    gsub(/[[:punct:]]/, "", $NF)
    map[caller,$NF]++
}
END {
    for(calls in map) {
        n = split(calls, tmp, SUBSEP)
        print tmp[1]" -> "tmp[2]
    }
}' file
main -> printackermann
main -> settimeout
strton -> failure
main -> printfibupto
main -> failure
failure -> usage
main -> strton

or

$ awk '
NF==2 {
    for(keys in map) 
        print map[keys]" -> "keys;
    delete map
    gsub(/[[:punct:]]/, "", $NF)
    caller = $NF
    next
}
{
    gsub(/[[:punct:]]/, "", $NF)
    map[$NF] = caller
}
END {
    for(keys in map)  
        print map[keys]" -> "keys
}' file
failure -> usage
strton -> failure
main -> strton
main -> settimeout
main -> printackermann
main -> printfibupto
main -> failure

Upvotes: 2

clt60
clt60

Reputation: 63974

For example:

#!/bin/bash    
while read -r line
do
    case "$line" in
        *:) caller=$line; continue;;
        *)  echo "$caller $line";;
    esac
done < <(sed 's/.*</</' < caldata) | sed 's/[<>]//g;s/:/ -> /' | sort -u

produces:

failure ->  usage
main ->  failure
main ->  print_ackermann
main ->  print_fib_upto
main ->  set_timeout
main ->  strton
strton ->  failure

Upvotes: 4

kojiro
kojiro

Reputation: 77197

I think this might be easier with awk, but I interpreted your pseudocode directly into bash.

while read; do # If not given an argument `read` puts the line into `REPLY`
  case $REPLY in # Use a case expression for pattern matching.
    *>:) caller="${REPLY##*<}"
         caller="${caller%>:}" # Parameter expansions to strip off unwanted parts of the line.
         ;;
    *>) callee="${REPLY##*<}"
        callee="${callee%>}"
        ;;
  esac
  echo "$caller -> $callee"
done

Upvotes: 2

anubhava
anubhava

Reputation: 786329

You can use awk:

awk -F'[<>:]+' 'NF==3{p=$(NF-1); next} {print p, "->", $(NF-1)}' file
failure -> usage
strton -> failure
main -> strton
main -> failure
main -> strton
main -> failure
main -> failure
main -> strton
main -> strton
main -> failure
main -> set_timeout
main -> failure
main -> print_fib_upto
main -> print_ackermann

Upvotes: 2

Related Questions