39

Is it possible to create something analogous to an anonymous function whose value can be assigned to an array element and later called? I can't seem to find a way to do this in a bash script but perhaps there's a workaround.

7 Answers 7

84

Short answer: No.

Long answer: Nooooooooooooo.

Complete answer: Functions in bash are not first-class objects, therefore there can be no such thing as an anonymous function in bash.

Sign up to request clarification or add additional context in comments.

7 Comments

As a workaround, I'd define the functions with names and store just the names in the array; then just eval the array element when you want to call the function.
@choroba Perhaps you should post this as an answer. Even though it's not directly possible as Ignacio described, your workaround is a good idea.
Does the bash bug in CVE-2014-6271 link confirm that anonymous functions can exist in shell scripts?
@Lizz: No. It simply takes advantage of a flaw in one of the ways to create a function.
The full explanation is NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO! No way. Forget it. Don't even think about it. Go away.
|
21

It is possible; I wrote a library to do exactly this, though it's a very strange project. The source code is available at http://github.com/spencertipping/bash-lambda. Using this library:

$ my_array=()
$ my_array[0]=$(fn x 'echo $((x + 1))')
$ my_array[1]=$(fn x 'echo $((x + 2))')
$ ${my_array[0]} 5
6
$ ${my_array[1]} 5
7
$

The trick is to have the fn function create a file containing the body of the function, chmod +x that file, then return its name. This causes stray files to accumulate, which is why the library also implements an asynchronous mark/sweep garbage collector.

3 Comments

Is there a way to to wait for a command to finishing executing before executing the anonymous function?
@William Shell scripts are synchronous by default. If you run some_command; ${my_array[0]} 5, or some_command && ${my_array[0]} 5, both expressions will only run "${my_array[0]} 5 after running some_command.
This answer is unintentionally funny to me. I look at the accepted answer, with its many jokes and comments, all saying "of course you cannot have anonymous functions in bash, you silly". Then Spencer here just ignore them and post how to do it. Yeah, it's a hack; but at least 80% of every bash script is a hack anyway, so I guess it's fine!
12

The common technique is to assign function definitions conditionally:

#!/bin/sh

case $1 in
a) foo() { echo case a; };;
b) foo() { echo case b; };;
*) foo() { echo default; } ;;
esac

foo

Comments

11

If you really need array to store the functions, you can define named functions and store just their names. You can then call the function as ${array[n]}. Or, you can name them func1 .. funcN and then just call func$n.

3 Comments

bash has declare -n reference_name - not anonymous, but name can be abstracted, e.g.: declare -n aref; a() { echo "function a"; }; aref=a then $aref will execute a.
@JackWasey: Can you elaborate? I'm not getting the output back.
I mistyped. Try echo ${!aref}, then ${!aref}
1

well bash is turing complete, soo thats perfectly possible ;)

but aside from this its not really worth the consideration.

you could simulate such behaviour though with something along this line:

echo myval ; ( foocmd "$_" && barcmd "$_" )

but why?!?

1 Comment

probably I just lost the joke, but I'll say anyway: most assemblies, not to mention the original turing machine, lack anonymous functions - but they are still turing complete.
0

I came across this question because I was looking for how to do something analogous to

[1,2,3,4,5].map(x => x*2).filter(x => x < 7);

i.e. Being able to manipulate previous results in a pipeline in a flexible way.


xargs

A nice way I had heard of to handle this on the command line is through the xargs command. My first impression was it wasn't very flexible

$ printf "one two\nthree\tfour" | xargs echo "args> "
# => args>  one two three four
$ printf "one two three four" | xargs echo "args> "
# => args>  one two three four

By default it splits standard input by any whitespace into separate arguments, then feeds those arguments to the command (it'll be clearer with the examples below)

I just learned from this extremely helpful gist Brainiarc7 wrote how xargs can be really flexible and useful. Definitely read/skim that, it's really good with more explanation. I included examples and things I gleaned from it below though, hopefully it's helpful.

1. -n: Split up args into n-sized chunks

# Chunk args into groups of two
$ printf "one two\nthree\tfour" | xargs -n 2 echo "args> "
# =>
# args>  one two
# args>  three four

Using -n 1 will probably be very useful

2. -I: Interpolate args into a command using a placeholder

An example probably best illustrates how this one works.

# Indicate we want `%` to be the placeholder, split args into groups of two,
# then interpolate them in the command at the placeholder
printf 'one two three four' | xargs -I % -n 2 echo "Chunk of args> " % " <in the middle of a command"
# =>
# Chunk of args>  one two  <in the middle of a command
# Chunk of args>  three four  <in the middle of a command

You give -I the string you want to use as a placeholder. The placeholder can be whatever text you like (e.g. my_unique_placeholder), but % is conventional.

3. -0: Chunk args by the null character (UTF+0000)

This isn't as convenient, but you can use it to split by an arbitrary character via tr.

# Split arg chunks by tab character:
$ printf 'one two\tthree\tfour' | tr '\t' "\0" | xargs -0 -n 1 echo "args> "
# =>
# args>  one two
# args>  three
# args>  four

4. -L: split args on n lines into a chunk

# Take all args on every 2 lines as a chunk
$ printf 'one two\nthree\nfour' | xargs -L 2 echo "args> "
# =>
# args>  one two three
# args>  four

https://gist.github.com/Brainiarc7/133fd582e124981c08cbafca98455ee9 https://shapeshed.com/unix-xargs/

3 Comments

Not sure that your example 2 is proving that with xargs, ({arg}) => {body} is what's happening in your code?
You're right, that was a confusing explanation. I was using {} similar to how a manpage would, but that doesn't make sense in a javascript context! Hopefully the edit is clearer.
I was curious because that kind of implementation (arg) => body is what I was looking for. The edit makes sense. Thank you!
-1

Create the fn file in your PATH

#!/bin/sh

printusage () {
        printf "Create anonymous function, for example\n"
        printf "fn 'echo "$1 $2"'"
        exit 1
}


[ "$#" = "1" ] || printusage
fun=$1
[ "$fun" = "" ] && printusage
fun_file="$(mktemp /tmp/fun_XXXXXX)"

echo "#!/bin/sh" > "$fun_file"
echo "" >> "$fun_file"
echo "$fun" >> "$fun_file"
chmod u+x "$fun_file"

echo "$fun_file"

You can then do :

foo=$(fn 'echo $1')
${foo} "bar" 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.