3

I'm looking for something similar to Bash's built-in command that will only run something if it is a function. So currently I have an insecure way of doing:

# Go through arguments in order
for i in $*; do
    if [ -z `which $i` ]; then
        # Run function
        $i && echo -n ' '
    fi
done

This if statement doesn't work properly. Anyway, even if I could check if it's a command and not a function, I can't run explicitly run a function, which is bad because if anyone has any programs in $PATH that are the same name as my function, they will be run. If I nullify PATH or set it to anything else, anyone could still use $i to run a program explicit, so that's also not a solution.

Any way I can "secure" my shell script?

  • 2
    This looks like a really poor way to work around just having proper argument parsing.. – kyrias Jul 21 '15 at 3:28
  • Right, so instead of functions, can I use something like e.g xargs to run certain bash functions? I dunno, I feel pretty limited with bash compared to something like Python, Perl or even C where I know if I run the name of a function from an argument, it's not going to run anything else. – mrrhq Jul 21 '15 at 3:37
4

With bash, you can do something like this:

for f do
  if declare -F -- "$f" >/dev/null 2>&1; then
    : "$f" is a function, do something with it
  fi
done

declare -F -- "$f" >/dev/null 2>&1 will return success code if $f is a bash function, output nothing.

You might also want to disable some special builtin commands when bash run in POSIX mode by adding builtin enable -n -- "$f".

  • This is really supposed to work? for f do? Shouldn't that be for f in <something> do instead? I'm wonder if a certain boolean condition could actually turn this into an infinite loop... – syntaxerror Jul 21 '15 at 4:26
  • 4
    @syntaxerror: for f do is shorthand and POSIX way of for f in "$@"; do. – cuonglm Jul 21 '15 at 4:27
  • Oh thanks, didn't know that. I'm not going to use it though; the latter variant you quoted will increase readability by at least 25 percent (IMHO), since the $@ will even be understood by bash programmers who only know the more basic syntax. – syntaxerror Jul 21 '15 at 4:29
  • 1
    one note. you should explicitly quote $f in eval to prevent an alias expansion in Posix-mode (or with shopt -s expand_aliases). See example – Evgeny Vereshchagin Jul 21 '15 at 5:12
  • 1
    There is another edge case in Posix-mode: a function with the same name as the special builtin. See example – Evgeny Vereshchagin Jul 21 '15 at 8:43
1

While you're probably well set in a bash shell, to do similar portably you might employ something like the following...

eval "  for c in $(unalias -a
        for c do case ${c#function}  in
                (*[!_[:alnum:]]*) ;; (?*)
                PATH=   command -v "$c" >&2 &&
                PATH=   command -V "$c"     &&
                        printf '\n'"'$c'"'\\\0'
        esac 2>/dev/null; done  |
        tr  '\t\n\0' '  \n'     |
        sed -ne's/.* function .* / /p')"'
;do     "$c"; printf %02s
done'

It works in stages:

  1. First weed out all args which contain any characters that might disqualify them as shell names in the first place.
  2. If the current arg is a valid name and a valid shell builtin or function print its name and type to stdout. Because of the way shell's can vary their output for command -V its output cannot be counted upon to be a single line. So follow the output with our verified name and and a null-byte.
  3. translate all newlines and tabs to spaces, and all null-bytes to newlines.
  4. with sed, s///ubstitute away all of any line up the last space character thereon and print the results if the line also matches the string function.
  5. All of sed's output is rounded up as iterators for the outer for loop and each of these is executed in turn, and after each is executed printf writes out two spaces.
  • Try this code. There are three functions in the script, but dash script foo bar cd detects only bar function. – Evgeny Vereshchagin Jul 21 '15 at 18:57
  • @EvgenyVereshchagin - yeah, that was the point - to avoid aliases and builtins. If the opposite behavior is desired, then for aliases i can just unconditionally unalias (and add a quote to echo "$c"), and for builtins i can just test for altered output. The whole thing can be done simply like case $(command -V -- "$c") in (*"$c"*function*|*function*"$c"*)... but it requires a subshell every test. – mikeserv Jul 21 '15 at 19:24
  • thanks for the explanation. declare -F-bashism detects everything. +1 for magical loop body:) – Evgeny Vereshchagin Jul 21 '15 at 19:46
  • @EvgenyVereshchagin - you know what, I thought about it, and you had a pretty good point. Try it again. – mikeserv Jul 21 '15 at 22:39
  • 1
    @EvgenyVereshchagin - Oh. Well then it just does the right thing and refuses to define a function named set(). That has nothing to do with my code. Line 10 is the set(){ ... } line. And yes, bash is the worst - it is the slowest, buggiest shell I've ever used. Here are some tests. – mikeserv Jul 22 '15 at 5:53
1

The formal way Bash provides to do it is using the type builtin as of type -t:

function fun() {
  : foo bar
}

if [ function = $(type -t fun) ]; then
  echo fun is a function.
fi

If you are suspicious about the function name from your parameter list, just double-quote it: $(type -t "$arg")

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.