bashawksed

Printing bash function body and including the comments in the body


I am using shdoc to generate markdown files for my bash functions; however, I would like also to append the associated bash function body as well as the comments in that body to the markdown file generated by shdoc. How can I print a bash function body while also including the comments in the function body?

As a simple example, consider that all my bash files are contained in a directory called src and all those files end in .sh. The function below in file src/foo.sh

# @description A function that does something
foo() {
  # oh wow, a comment
  echo "something cool"
}

Now, if I want to get the function body without comments I can do this easily with declare -f as shown below in a file called src/doc.sh:

# assuming you are in the `src` dir and both .sh files are executable
. foo.sh
foo_doc=$(declare -f foo)
echo "$foo_doc"

But the problem is foo_doc only has the function body, since echo "$foo_doc"

foo () 
{ 
    echo "something cool"
}

and no comments at all. So if I were to append this information to a markdown generated by shdoc, it is not an ideal documentation of the source code.

I think one approach would be to use sed to determine the lines corresponding to the function of interest (my bash functions are "delimited" by { and }, though per the comments I understand there are other ways to declare valid bash functions), then I could use sed to get only those lines as done here.


Solution

  • Since you have access to the files that contain the function definitions, you could do this with any POSIX awk:

    awk '
        /^[[:alpha:]_][[:alnum:]_]*[[:space:]]*\(/ && ($1 !~ /^(if|elif|for|while|until)$/) {
            inFunc = 1
        }
        inFunc {
            print
            if ( /^[})]/ ) {
                inFunc = 0
            }
        }
    ' src/*.sh
    

    It's not 100% robust but will probably be good enough for your functions if they look like the example you provided (i.e. start with alphanumeric_name ( at the start of a line and end with a } or ) at the start of a line) and you don't have any other code in your scripts that also looks like that but is not a function definition.

    Add more keywords to the $1 !~ test if you like or you can make it more robust by adding a call to declare -F and then only accepting apparent function definitions matched by the above regexp if the string matches an actual function definition as reported by declare -F.

    For example:

    $ cat src/tst.sh
    # The following is a function
    # that does amazing things.
    foo() {
      # oh wow, a comment
      echo "something cool"
    }
    
    # other code
    if (( $# > 1 )); then
        echo "Eureka"
    fi
    
    # This function, however, is
    # useless, can't believe we
    # bothered to write it.
    bar() {
      # this sucks
      # hope it doesn't get called
      echo "the sky is falling"
    }
    

    $ . src/tst.sh
    

    $ declare -f foo bar
    foo ()
    {
        echo "something cool"
    }
    bar ()
    {
        echo "the sky is falling"
    }
    

    $ declare -F foo bar
    foo
    bar
    

    $ declare -F |
    awk '
        NR == FNR {
            funcs[$3]
            next
        }
        /^[[:alpha:]_][[:alnum:]_]*[[:space:]]*\(/ && ($1 in funcs) {
            inFunc = 1
        }
        inFunc {
            print
            if ( /^[})]/ ) {
                inFunc = 0
            }
        }
    ' - FS='[[:space:](]+' src/*.sh
    foo() {
      # oh wow, a comment
      echo "something cool"
    }
    bar() {
      # this sucks
      # hope it doesn't get called
      echo "the sky is falling"
    }
    

    and if you'd like to get any comments that end on the line immediately before each function too:

    $ declare -F |
    awk '
        NR == FNR {
            funcs[$3]
            next
        }
        /^[[:alpha:]_][[:alnum:]_]*[[:space:]]*\(/ && ($1 in funcs) {
            printf "%s", cmt
            inFunc = 1
        }
        { cmt = (/^#/ ? cmt $0 ORS : "") }
        inFunc {
            print
            if ( /^[})]/ ) {
                print ""
                inFunc = 0
            }
        }
    ' - FS='[[:space:](]+' src/*.sh
    # The following is a function
    # that does amazing things.
    foo() {
      # oh wow, a comment
      echo "something cool"
    }
    
    # This function, however, is
    # useless, can't believe we
    # bothered to write it.
    bar() {
      # this sucks
      # hope it doesn't get called
      echo "the sky is falling"
    }
    

    Note that that will only print the functions defined in your files and which are available in your environment, unlike the first script which will print all of the functions in your files.