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?

cuonglm
  • 153,898
mrrhq
  • 83
  • 2
    This looks like a really poor way to work around just having proper argument parsing.. – remmy Jul 21 '15 at 03: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 03:37

3 Answers3

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

cuonglm
  • 153,898
  • 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 04:26
  • 4
    @syntaxerror: for f do is shorthand and POSIX way of for f in "$@"; do. – cuonglm Jul 21 '15 at 04: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 04: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 Jul 21 '15 at 05:12
  • Hey, thanks a lot for this. I had to use -F instead of +F though, but it worked! Since I know that all functions run before commands do, I shouldn't have to worry about that so much. Although it seems like you guys are implying that aliases run even before functions do. Is that true? @EvgenyVereshchagin Should I run eval "$f" instead of $f, is that what you're saying? – mrrhq Jul 21 '15 at 05:21
  • You can run $f. It's ok. But if you use $f inside eval you should quote it: eval "'$f'". See Shell Operation – Evgeny Jul 21 '15 at 05:42
  • Not sure what you mean. declare +X var removes the X attribute to the variable. For F, it doesn't make sense. With my version of bash (4.3.11), it doesn't work at all, it returns true for everything that is a valid variable name, and false for functions that don't have a valid variable name (like :). – Stéphane Chazelas Jul 21 '15 at 06:46
  • @StéphaneChazelas: My mis-understand from manpage, thanks for pointing out. – cuonglm Jul 21 '15 at 06:55
  • 1
    There is another edge case in Posix-mode: a function with the same name as the special builtin. See example – Evgeny Jul 21 '15 at 08:43
  • @EvgenyVereshchagin: Nice catch! Updated my answer. – cuonglm Jul 21 '15 at 08:57
  • You'd rather prefer the type -t builtin, as in https://unix.stackexchange.com/questions/217292/execute-only-if-it-is-a-bash-function/329230#329230 – Luchostein Nov 27 '23 at 19:14
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.
mikeserv
  • 58,310
  • Try this code. There are three functions in the script, but dash script foo bar cd detects only bar function. – Evgeny 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 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
  • wow! works fine in dash. There is an edge case in bash: function with the same name as the special builtin in Posix-mode. Try this code. bash script foo bar cd set detects all functions but run set builtin. – Evgeny Jul 22 '15 at 04:59
  • @EvgenyVereshchagin - can you just tell me what happens? What happens? And a shell should not be able to name a function named set - what kind of crazy stuff is that? I swear, bash is the worst shell. – mikeserv Jul 22 '15 at 05:12
  • Bash is not so bad:) bash --posix script foo bar cd set fails with script: line 10: set': is a special builtin. but in normal mode you can define a set function. builtin set -o posix force Posix-mode and special builtins are found before shell functions during command lookup. – Evgeny Jul 22 '15 at 05:44
  • 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 05:53