POSIX compliant way to see if a function is define

2019-02-15 14:12发布

问题:

I'm after THE proper way to see if a function is defined or not. A POSIX compliant way.

__function_defined() {
    FUNC_NAME=$1
    d=$(declare -f $FUNCNAME)

    if [ "${DISTRO_NAME_L}" = "centos" ]; then
        if typeset -f $FUNC_NAME &>/dev/null ; then
            echo " * INFO: Found function $FUNC_NAME"
            return 0
        fi

    # Try POSIXLY_CORRECT or not
    elif test -n "${POSIXLY_CORRECT+yes}"; then
        if typeset -f ${FUNC_NAME} >/dev/null 2>&1 ; then
            echo " * INFO: Found function $FUNC_NAME"
            return 0
        fi
    else
        # Arch linux seems to fall here
        if $( type ${FUNC_NAME}  >/dev/null 2>&1 ) ; then
            echo " * INFO: Found function $FUNC_NAME"
            return 0
        fi
    echo " * INFO: $FUNC_NAME not found...."
    return 1
}

All of the above are considered bash'isms according to debian's checkbashisms script.

Trying to grep the script is also not ok. for example:

    if [ "$(grep $FUNC_NAME $(dirname $0)/$(basename $0))x" != "x" ]; then
        # This is really ugly and counter producing but it was, so far, the
        # only way we could have a POSIX compliant method to find if a function
        # is defined within this script.
        echo " * INFO: Found function $FUNC_NAME"
        return 0
    fi

won't work because the script is also supposed to work like:

wget --no-check-certificate -O - http://URL_TO_SCRIPT | sudo sh

So, what's the proper, POSIX compliant way to do this?

Plain sh please, no bash, no ksh, no other shell, just plain sh. Oh, and without actually trying to run the function too :)

Is it possible?

I have found a POSIX compliant solution:

if [ "$(command -v $FUNC_NAME)x" != "x" ]; then
    echo " * INFO: Found function $FUNC_NAME"
    return 0
fi

The question now, Is there a better solution?

回答1:

I have found a POSIX compliant way

if [ "$(command -v $FUNC_NAME)x" != "x" ]; then
    echo " * INFO: Found function $FUNC_NAME"
    return 0
fi

The question now, Is there a better solution?



回答2:

While I can understand why you might think so, dynamically pulling your script from another location is no great barrier to parsing (or "grepping") it as you like before you run it. Such a thing can be accomplished in several ways, but, as far as I can make out, they will all require a basic file descriptor for functionally complete operation.

Bash and its ilk provide us the convenience of transparent file descriptors via redirected sub-shelled commands which allow for all kinds of pipeline flexibility like this:

eval "$(wget --no-check-certificate -O - ${_URL} | tee \
    >(read func; echo '_FUNC='"$(grep -Eo '^[^ |^#]*()' <(echo "${func}"))") \
    | sudo sh)" 

But, as has been mentioned already, such syntax devices are not specified by POSIX, and so you must find another way. Also, shorter though it may be, that can be a little confusing. Probably there are much better ways to do that, but I imagine them requiring 2 or 3 subshell levels at least. (It sure would be cool to be shown otherwise, though.)

What POSIX does specify, however, is the very useful heredoc. The heredoc is rare among declarable POSIX variable syntax types not only because it can neatly span multiple lines without escape kludges, or because it allows for clean definition of shell substitution by simply quoting its terminator, but most importantly because it describes a file type handle. This quality of the heredoc is often overlooked, perhaps because Bash and co have largely obviated its importance for some time now, but we should be reminded of it every time we adhere to the convention of terminating a heredoc with the "end of file" acronym EOF.

In order to pull in a script file, modify it programmatically, and then execute it with a shell interpreter you will need a file descriptor. You can read and write to /tmp/ to allow for this, of course, but I generally try to avoid that because it just feels sloppy. You could even, perhaps, rewrite yourself if you must, but it is probably not the best habit to get into. If you find real cause to do this, however, the safest way is probably to define the rewrite code in a function at the beginning of your script and only call it at the very end so you can be sure the entire file has been fully loaded into memory. Something like this:

_rewrite_me() {printf %s "${1}" > ${0} && exec ${0}} 
#SHELL CODE
#MORE 
_NEW_ME="$(printf %s "${_MORE_SHELL_CODE}" | cat <${0})"
rewrite_me ${_NEW_ME}

Still, I like the heredoc best, and I think it is the missing puzzle piece in this case. The following is made short and simple with use of the heredoc, should be fully POSIX compliant, and should easily be adapted to your purposes, I hope:

#!/bin/sh
_URL='http://URL/TO/SCRIPT'

_SCRIPT="$(wget --no-check-certificate -O - ${_URL} |\
    grep -Ev '^#!')" 

_FUNC="$(sed -n 's:^\([^ |^#|^=]*\)().*{.*$:\1:p' <<_EOF_)"
${_SCRIPT}
_EOF_
echo "${_FUNC}"

sh <<_EOF_
${_SCRIPT}
_EOF_


标签: posix sh