Shell脚本中的匿名函数

35

是否有可能创建类似于匿名函数的东西,其值可以分配给数组元素并稍后调用?我似乎找不到在bash脚本中实现这一点的方法,但也许有一些变通方法。

7个回答

77

简短回答:不行。

长篇回答:不-行-啊-啊-啊-啊-啊-啊-啊-啊-啊。

完整回答:在Bash中,函数不是一等对象,因此无法存在匿名函数。


8
作为一种解决方法,我会为函数定义名称并仅在数组中存储这些名称;当您要调用该函数时,只需要对数组元素进行eval - choroba
1
@choroba 也许你应该把这个发表为答案。尽管像Ignacio所描述的那样,它并不是直接可能的,但你提出的解决方法是一个好主意。 - Matty
1
CVE-2014-6271中的bash漏洞链接是否证实匿名函数可以存在于shell脚本中? - Lizz
1
@Lizz:不是的。它只是利用了创建函数的某种方式中的一个缺陷。 - Ignacio Vazquez-Abrams
1
完整的解释是绝对不可能的!不行。忘了它。别想了。走开。 - clearlight
显示剩余2条评论

18

有可能做到,我写了一个库来实现这个功能,不过这是一个非常奇怪的项目。源代码可在http://github.com/spencertipping/bash-lambda上找到。使用这个库:

$ 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
$

关键是让fn函数创建一个文件,其中包含函数的主体,然后chmod +x该文件,并返回其名称。这会导致杂散的文件积累,因此该库还实现了异步标记/清除垃圾回收器。


有没有一种方法可以在执行匿名函数之前等待命令执行完成? - Lime
@William Shell脚本默认是同步的。如果你运行some_command; ${my_array[0]} 5或者some_command && ${my_array[0]} 5,两个表达式都只会在运行some_command之后才会运行${my_array[0]} 5 - Nick Bull

10

如果您真的需要使用数组来存储函数,您可以定义命名函数并仅存储它们的名称。然后,您可以调用函数如${array[n]}。或者,您可以将它们命名为func1 .. funcN,然后只需调用func$n


1
Bash具有declare -n reference_name功能,其名称可以抽象化,例如:declare -n aref; a() { echo "function a"; }; aref=a,然后执行$aref将会运行函数a - Jack Wasey
@JackWasey:你能详细说明一下吗?我没有得到输出。 - choroba
我打错了。请尝试使用 echo ${!aref},然后 ${!aref} - Jack Wasey

9

常见的技巧是有条件地分配函数定义:

#!/bin/sh
case $1 in a) foo() { echo case a; };; b) foo() { echo case b; };; *) foo() { echo default; } ;; esac
foo

1

嗯,Bash是图灵完备的,所以这是完全可能的;)

但除此之外,这并不值得考虑。

不过你可以用类似以下的方式模拟这种行为:

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

但为什么?!?


0
我看到了这个问题,因为我正在寻找类似于如何做某事的东西。
[1,2,3,4,5].map(x => x*2).filter(x => x < 7);

即能够以灵活的方式操纵管道中的先前结果。


xargs

在命令行中处理这个问题的一种好方法是使用xargs命令。我的第一印象是它不太灵活。

$ 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

默认情况下,它将标准输入按任何空格分隔成单独的参数,然后将这些参数提供给命令(通过下面的示例会更清楚)

我刚学到了Brainiarc7编写的非常有用的Gist中xargs可以非常灵活和有用。 一定要阅读/浏览一下,它有更多的解释,非常好。 我在下面包含了示例和我从中获得的东西,希望对您有所帮助。

1. -n:将参数拆分为n个大小的块

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

使用-n 1可能非常有用。

2. -I: 使用占位符将参数插入命令中

一个例子可能最好地说明了这个功能的工作原理。

# 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

你需要给-I传入一个字符串作为占位符。占位符可以是任意文本(例如my_unique_placeholder),但%是常规选择。

3. -0:使用空字符(UTF+0000)对参数进行分块

虽然不太方便,但是你可以使用它通过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: 将参数在 n 行上拆分为一块

# 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/


你不确定你的第二个示例是否证明了在你的代码中使用xargs时,({arg}) => {body} 是正在发生的事情吗? - robrecord
你说得对,那个解释很令人困惑。我使用 {} 类似于 manpage 的方式,但在 JavaScript 上下文中这是没有意义的!希望修改后更清晰明了。 - matt0089
1
我很好奇,因为(arg) => body这种实现方式正是我正在寻找的。修改很有道理。谢谢! - robrecord

-1
在你的PATH中创建fn文件。
#!/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"

然后,您可以执行:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接