6

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
  • 2
    This looks like a really poor way to work around just having proper argument parsing..
    – remmy
    Jul 21, 2015 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, 2015 at 3:37

3 Answers 3

3

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".

11
  • 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... Jul 21, 2015 at 4:26
  • 4
    @syntaxerror: for f do is shorthand and POSIX way of for f in "$@"; do.
    – cuonglm
    Jul 21, 2015 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. Jul 21, 2015 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 Jul 21, 2015 at 5:12
  • 1
    There is another edge case in Posix-mode: a function with the same name as the special builtin. See example Jul 21, 2015 at 8:43
3

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")

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.
8
  • Try this code. There are three functions in the script, but dash script foo bar cd detects only bar function. Jul 21, 2015 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, 2015 at 19:24
  • thanks for the explanation. declare -F-bashism detects everything. +1 for magical loop body:) Jul 21, 2015 at 19:46
  • @EvgenyVereshchagin - you know what, I thought about it, and you had a pretty good point. Try it again.
    – mikeserv
    Jul 21, 2015 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, 2015 at 5:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .