2

UPDATE:

Some Background:

zsh returns the line number inside a function when $LINENO is called inside a function. I need a way to get the line number in the file and to differentiate when zsh is giving me a file line number vs. a function line number.

I couldn't find a zsh environment variable to change this behavior to match other Bourne shells (e.g. bash always gives the file line number), so I was trying to see if I could create a function with logic that could always output the file line number regardless of context. This is why I was trying to determine the length of the function.

If anyone knows of a good way to get the file line number with $LINENO in zsh in all contexts, I'd appreciate it!


QUESTION:

I've searched this and this, but can't seem to find an answer. Is there a portable way to write the number of lines a function definition has? (Please see "Some Background" above.)

My initial thought was to capture the function contents and pipe it to wc -l.

Consider the following test file:

Test File:

#! /bin/sh
#
# test_file.sh

func1() { echo 'A one-liner'; } # With a nasty comment at the end func2 (){ echo "A sneaky } included"

Or an actual code block

{ echo 'hi' echo 'there' } }

func3() { echo "I'm on a line."; }; echo 'And so am I'

func4(){ echo "But I'm a "stand-alone" one-liner."; }

func5() { echo "I'm a nice function." echo "And you can too!"

}

echo "Can we do this?"

My initial attempt was to match corresponding pairs of {}'s with sed:

Solution Attempt:

#! /bin/sh
#
# function_length
#
# $1: string: absolute path to file
# $2: string: name of function (without ()'s)

fp=$(realpath "$1") func_name="$2"

func_contents=$(cat "${fp}" | sed -E -n ' /'"${func_name}"' ?[(][)]/{ :top /[}]/!{ H d } /[}]/{ x s/[{]// t next G b end } :next x b top :end p q }')

echo "${func_contents}" echo

func_len=$(echo "${func_contents}" | wc -l)

echo "Function Length: ${func_len}"

However, running this in zsh gives

$ ./function_length ./test_file.sh func1

func1() { echo 'A one-liner'; } # With a nasty comment at the end

Function Length: 2 $ ./function_length ./test_file.sh func2

Function Length: 1 $ ./function_length ./test_file.sh func3

func3() { echo "I'm on a line."; }; echo 'And so am I'

Function Length: 2 $ ./function_length ./test_file.sh func4

func4(){ echo "But I'm a "stand-alone" one-liner."; }

Function Length: 2 $ ./function_length ./test_file.sh func5

Function Length: 1

Does anyone know of a solution? Thank you!

  • 3
    What do you need the length for? What difference does it make? Please make sure there is no XY problem here. [Edit] the question if there is. – Kamil Maciorowski Jun 13 '21 at 21:36
  • @KamilMaciorowski This isn't that. I'm asking for any solution. I'm just trying to show that I've done some work before blindly asking for help. – adam.hendry Jun 13 '21 at 21:39
  • @KamilMaciorowski If it is proven there is no solution to the problem (getting the length of a function in shell), please provide evidence/reference to that as that would help as well. My understanding is some seem to only SUGGEST this is not possible. – adam.hendry Jun 13 '21 at 21:42
  • 2
    The help center states "You should only ask practical, answerable questions based on actual problems that you face". Because of this I hoped there was an actual problem that matters in practice. Now it seems there's none and the question is academic. Maybe it's not academic, maybe I'm missing something; therefore I asked what difference the length makes. – Kamil Maciorowski Jun 13 '21 at 21:59
  • You can't do this without writing a program to interpret shell that you can run on your source code to output function sizes. You certainly can't write a sed or awk script to do it robustly. – Ed Morton Jun 14 '21 at 15:23
  • @KamilMaciorowski My apologies, it was not my intent to ask a theoretical question. zsh returns the line number inside a function when $LINENO is called and I needed a way to get the line number in the file. I couldn't find an environment variable to change this behavior to match other Bourne shells (bash, e.g.), so I was trying to see if I could create a function with logic that could output the file line number. I'll update my question. – adam.hendry Jun 15 '21 at 15:16
  • 1
    @EdMorton Thank you. Yes, I certainly see that now. This is a nontrivial problem. Thanks again! – adam.hendry Jun 15 '21 at 15:17

1 Answers1

8

There is no portable way to retrieve the content of a shell function. Some shells have one, some don't. The popular shell dash has no way do do anything about a function's body except evaluate it.

dash/src $ grep -F ndefun.body *.c
eval.c: evaltree(func->n.ndefun.body, flags & EV_TESTED);
parser.c:                               n->ndefun.body = command();

Further examination of the source code reveals there is no separate data structure containing the “length” of the function, whatever that means.

In shells that do have a way to print the definition of a function, it may be formatted differently from the source code. So the “length” is not a meaningful number.

$ bash -c 'f () { echo hello; echo world; }; typeset -f f'
f () 
{ 
    echo hello;
    echo world
}
$ ksh -c 'f () { echo hello; echo world; }; typeset -f f'; echo
f() { echo hello; echo world; };
$ mksh -c 'f () { echo hello; echo world; }; typeset -f f'
f() {
        \echo hello 
        \echo world 
} 
$ zsh -c 'f () { echo hello; echo world; }; typeset -f f'
f () {
        echo hello
        echo world
}