0

I use Ubuntu 16.04 with Bash and I have a file with 10 functions. Each function does essentially a different task. In the end of each function, I call it this way:

x() {
    echo
}
x

The calls add 10 more lines to the file, lines that I would wish to save from the file from aesthetic reasons, because I execute all functions in that file anyway (in the order that they are already sorted at).

Of course, just sourcing (executing in the current session) or bashing (executing in a subsession) isn't enough (gladly, because sometimes one wants to source/bash without running all functions).

The solution I'd prefer for this minor aesthetic problem would be to call all of functions, somehow, directly from the command line, right after the sourcing/bashing of the file.

Is it possible in Bash?

  • I'm hesitant to say no because almost anything is possible, but in this case I think no, it is not possible. – Jesse_b Feb 15 '18 at 19:48
  • 1
    It seems to me that the clarity of knowing what you’re calling & when is worth a few bytes. – Jeff Schaller Feb 15 '18 at 20:00
  • The functions I call to are simple functions (each up to about 8 lines) that do various different task like showing all Ngiinx logs in a comfortable way or change permissions somewhere, etc), small personal tasks in a personal environment... – user9303970 Feb 15 '18 at 20:03
2

If you are only concerned with the line count of your file you could do something like this:

my_func () {
    echo "Hello, world"
} && my_func
2

You can use:

source <(declare -f | grep -o '^[[:alnum:]]\+')

This lists all the function definitions in scope (declare -f), then filters out just their names, which will be the only alphanumeric strings at the left margin (grep -o '^[[:alnum:]]\+'), and finally sourcing the resulting output, which is a one-per-line list of function names, via process substitution so that they all get called in the current shell.

The above works with GNU or BSD greps which have -o, but you can swap in a suitable sed or awk command instead if needed.


The functions won't be in any particular order here (Bash's hash-table order, I think, which is essentially random). If you need to preserve an ordering, name them suitably and add a sort into the middle:

source <(declare -f | grep -o '^[[:alnum:]]\+'|sort)

If the functions themselves are meant to be called independently, this naming structure might be undesirable. I don't see a simple way around it.


That works within a single file. If you're sourcing the file into a shell that already has functions defined in it, you need to remember which already existed and skip those:

funcs=($(declare -f | grep -o '^[[:alnum:]]\+'|sort))
source file
source <(declare -f | grep -o '^[[:alnum:]]\+' | sort - <(printf '%s\n' "${funcs[@]}" | uniq -u)

There are several ways you could do that, all of which are fairly awkward. This also falls over when you redefine an existing function. Another approach would be to grep out all the function definition lines from the file itself; if you're structured about how you write it that works too.


A possibly-better approach if you control the file contents would be to list the function names, in the order you want them called, in a single-line array definition at the bottom of your file. Then you could just loop over it in the sourcing shell and do what you need.

This approach would be clearer and less fragile. It does introduce the possibility of the two locations getting out of sync, however.

  • instead of using declare -f (which gives you all currently defined functions, regardless of where they were defined), why not just source the file and then grep the file for function definitions (e.g. with something like funcs="$(sed -E -n 's/^function *([A-Za-z0-9_]+) *\{/\1/p; s/^([A-Za-z0-9_]+) *\(\) *\{/\1/p' file)"), and then execute them in the order seen. Of course, that's totally ignoring the fact that what the OP wants is a bad idea in the first place but hey, it's his foot and he can shoot it off if he wants to. – cas Feb 16 '18 at 0:51
  • the sed regexes are just a first approximation and would need to be optimised. – cas Feb 16 '18 at 0:57
  • @cas Yeah, I alluded to that just above the last rule. If the code is well-structured it should work, but it's not as reliable as the normalised declare -f output. – Michael Homer Feb 16 '18 at 1:00
  • yeah, that's true. "insufficient coffee error: read failure". BTW, $BASH_SOURCE[0] could be used as the filename in the sourced file itself....could be used at the end of the sourced file so that if the file is sourced with -x or --execute as the first arg it self-executes all of its functions. still a terribly bad idea but entirely possible. – cas Feb 16 '18 at 1:24
1

If you really want a one-liner, I came up with this, assuming a file scripts/1.sh:

shopt -s extdebug; source scripts/1.sh; source="$_"; while IFS= read -r func; do declare_output="$(declare -F "${func##* }")"; [[ ${declare_output##* } == $source ]] && "${func##* }"; done < <(declare -F)

Basically, this uses declare to determine which functions in your environment came from the sourced file and runs them. I have three functions in scripts/1.sh which echo Goodbye, cruel, and world., and my output from that command is:

Goodbye,
cruel
world.

I don't know if the order of the declare output is guaranteed to be the order the functions were sourced in, so you might have to name the functions appropriately and use sort.

1

I would suggest keeping the explicit calls, for various reasons. They'd allow you to temporarily remove some functions from the call list, change the order, keep old versions of the functions around while editing, and have utility functions that are only called other functions (but not from the "main" level). The last one in particular seems important for proper structuring of the program.


But if you really want, you can do that. Though I might suggest at least adding some sort of a designator for the autorunnable functions. Partly based on @cas's comment, this runs all the functions tagged with # AUTORUN on the line above.

#!/bin/bash

foo() { echo foo; }

# AUTORUN
bar() { echo bar; }

while IFS= read -r f; do
    $f
done < <(sed -nE '/^# *AUTORUN/!d; n; s/^([a-zA-Z0-9_]+) *\(\).*/\1/p' "${BASH_SOURCE[0]}")

Probably needs GNU sed in the current form.

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.