------------------------------------------------------------------------------- Output functions declare -f some_function For variables use -p instead of -f ------------------------------------------------------------------------------- Function Returns The standard way to return information from a function is to print it and then save the result in a variable. add_numbers() { local result=$(( $1 + $2 )) echo "$result" } total="$( add_numbers 22 33 )" echo "$total" # Output => 55 However this forks a sub-shell, which can be expensive. --- A different way is to pass the function a variable by reference to save the result into. https://blog.brujordet.no/post/bash/how-a-simple-bash-prompt-became-a-complicated-problem/ add_numbers() { local -n result="$1" # variable reference shift result=$(( $1 + $2 )) } add_numbers total 33 44 echo "$total" # Output => 77 As an added advantage, you are not trying up STDOUT, so the function can still use make use of standard output to print things to the user. ------------------------------------------------------------------------------- Parralel Concurancy. If you are doing lots of small tasks, you can run them all in background and wait till they have all finished, outputing results into temp files. i=0 segement1 args > "${tempdir}/${i}" & pids[i++]=$! segement2 args > "${tempdir}/${i}" & pids[i++]=$! segement3 args > "${tempdir}/${i}" & pids[i++]=$! for i in "${!pids[@]}"; do wait "${pids[i]}" done See also see "xargs" and GNU "parallel" command with their forking functionality. ------------------------------------------------------------------------------- Rename a existing function... For example add a Wrapper around the output of "some_function"... some_function() { echo "some_function"; } eval "$(echo "orig_some_function()"; declare -f some_function | tail -n +2)" some_function() { echo "START"; orig_some_function; echo END; } The output of "some_function" is now... START some_function END The above does not work for recursive functions, unless the function definition is also fixed... This should work in 'most' cases.... eval "$( echo "b/orig_some_function()" declare -f some_function | sed '1d;s/\bsome_function\b/orig_some_function/g)" ---------- Switch between different functions... Replace the bash_completion "_known_hosts_real" function So that the "ssh" completion will use your version... But allow it to be turned off and on... _my_known_hosts() { # ... _bash_known_hosts "$@" # ... } # Now replace "_known_hosts_real()"... if type -t _bash_known_hosts >/dev/null; then : # function has already been redefined else eval "$( echo "_bash_known_hosts()" declare -f _known_hosts_real | tail -n +2 )" fi # Set to use 'my' version _known_hosts_real() { _my_known_hosts "$@"; } # Set to use 'bash' version # _known_hosts_real() { _bash_known_hosts "$@"; } ------------------------------------------------------------------------------ Functions in Sourced Scripts Script which normally do a task, but if sourced you want them to just define library functions or other envinoment settings. This is actually very python-like... =======8<--------CUT HERE---------- #!/bin/bash # # do_something [args...] # # Do something to something... # # This script may be executed, or sourced to define the function. # do_something() { ... } # was script sourced? -- just return, function now defined sourced() { [[ ${FUNCNAME[1]} = source ]] } sourced && return # script was executed so just run the function with given args do_something "$@" =======8<--------CUT HERE---------- Note that the name "source" gets put on the stack even if the script was sourced using the dot (".") command, so this check is sufficient for both use cases. =============================================================================== Exported Functions... Also see Shell Shock "shell_shock.txt" for the major bug that was found. This has not very well documented, and only 'mentioned' in "ENVIRONMENT" section of the BASH manpage (this may have changed). It is also mentioned in "rbash" section as something that is 'not performed'. This was why the shell shock bug was not found for so long. Exporting a function (must be explict), lets you pass a function to a sub-shell, by storing that function in a environment variable. $ foo() { echo foobar; } # Define a function foo $ foo # Execute it foobar $ bash # Start a subshell $ foo # foo is not defined in the subshell bash: foo: command not found $ exit # Return to the outer level $ export -f foo # export foo() to the environment $ bash # Start another subshell $ foo # foo is now available to the subshell foobar $ env | grep -A1 BASH_FUNC_foo%%= # The env variable (post shellshock) BASH_FUNC_foo%%=() { echo foobar } Note the '%%' prevents bash from directly using that variable in scripts. For a period the characters '()' were used instead of '%%' Also you can no longer export functions with '.', ':', or '/', in their name. --- List Exported functions declare -x -F export -f env | sed -n 's/^BASH_FUNC_\([a-z]*\)()=.*/\1/p' List just the exported function names... declare -x -F | awk '{print $NF}' env | sed -n 's/^BASH_FUNC_\([A-Za-z_]*\)%%=.*/\1/p' --- Practical example... Export a function to use in a sub-shell of another command. =======8<--------CUT HERE---------- # Function to caclulate a result send_web_result() { output=( ROCK PAPER SCISSORS ) result=${output[RANDOM%3]}$'\r\n' printf "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n%s" \ ${#result} "$result" } # export function to sub-shells... export -f send_web_result # looped forking server, using socat socat tcp-listen:8080,fork exec:'bash -c send_web_result' =======8<--------CUT HERE---------- --- Bash Verbose flag... When running bash -v it will print all imported functions that it is given. Giving it a command (like ":") stops it outputting sourced profiles bash -v -s foo() { echo foobar #> } ------------------------------------------------------------------------------- Exported Functions - Injection Attack Example... function ls() { echo INJECTION; } ls #> INJECTION export -f ls bash -c 'ls' #> INJECTION unset ls Using environment variables env "BASH_FUNC_ls%%=() { echo INJECTION ; }" bash -c 'ls' #> INJECTION This also works to replace a bash built-in!!! env "BASH_FUNC_echo%%=() { command echo INJECTION; }" bash -c 'echo hello' #> INJECTION Even "unset" and "command" can be injected! env "BASH_FUNC_unset%%=() { echo INJECTION; }" bash -c 'unset echo' #> INJECTION env "BASH_FUNC_command%%=() { echo INJECTION; }" bash -c 'command unset echo' #> INJECTION --- Basically just about anything can be replaced by a function including builtins. The exception is "special builtins", but only in posix mode! https://www.gnu.org/software/bash/manual/html_node/Special-Builtins.html ------------------------------------------------------------------------------- Protecting a script from function injection attack A script (or even your login shell) may not want any imported functions. But how to do that from a script that wants to protect itself! * A Bash running setuid, it will not load exported functions. * Dash automatically clears enviornment with unusual characters whcih is why many systems link "/bin/sh" to it, so system() calls will clear exported functions. * You can not import functions with '/' in the name so you can call external commands to 'fix' the problem, except external commands can't modify the shells environment very easilly. (How to use this?) The only GOOD way of protecting such a script is for a wrapper program (like "sudo") to clear environment of all unwanted variables. --- Self sanitising -- not fool proof! BUT these removal command can be circumvented, unless "unset" or "command" can be protected from function injection, at a minimum. Unset ALL exported/imported functions using "sed" command unset -f env sed unset -f `env | sed -n 's/^BASH_FUNC_\([a-z]*\)%%=.*/\1/p'` Alturnative using "declare" and "awk" unset -f declare awk unset -f `declare -x -F | awk '{print $NF}'` ------------------------------------------------------------------------------