英文:
How to pass a shell function to `git rebase --exec`
问题
在bash中,要将变量或函数传递给子进程,您需要导出它。
#!/bin/bash
f() { echo hello; }
# 在子进程中调用shell函数 `f`
bash -c "f" # 输出:hello
# 导出函数
export -f f
bash -c "f" # 输出:hello
然而,我似乎无法使用 git rebase --exec
实现相同的结果。
#!/bin/bash
f() { echo hello; }
# 导出函数
export -f f
git rebase --exec "f" # 无法正常工作
git rebase --exec "bash -c 'f'" # 无法正常工作
我知道可以内联声明函数:git rebase --exec 'f() { echo hello; }; f'
或者将函数放入另一个脚本并调用它:git rebase --exec './f.sh'
有没有办法使它工作?我漏掉了什么?
英文:
In bash, to pass a variable or function to a child process, you need to export it.
#!/bin/bash
f() { echo hello; }
# calling shell function `f` in child process
bash -c "f" # bash: f: command not found
# export the function
export -f f
bash -c "f" # "hello"
However, I can't seem to achieve the same result with git rebase --exec
#!/bin/bash
f() { echo hello; }
# export the function
export -f f
git rebase --exec "f" # does not work
git rebase --exec "bash -c 'f'" # does not work
I know I could declare the function inline: git rebase --exec 'f() { echo hello; }; f'
or put the function in another script and call it: git rebase --exec './f.sh'
Is there a way to make it work? What am I missing?
答案1
得分: 2
Bash的export -f
是一种聪明的技巧,而不是什么"标准"。请查看以下命令和输出(在Linux上):
$ foo() { echo hello; }
$ export -f foo
$ bash -c foo
hello
$ bash -c 'tr "$ foo() { echo hello; }
$ export -f foo
$ bash -c foo
hello
$ bash -c 'tr "\0" "\n" < /proc/$$/environ | grep -A 1 foo'
BASH_FUNC_foo%%=() { echo hello
}
" "\n" < /proc/$$/environ | grep -A 1 foo'
BASH_FUNC_foo%%=() { echo hello
}
正如我们所见,bash将导出的foo()
的定义放在名为BASH_FUNC_foo%%
的环境变量中。
如果当前的bash子进程是另一个bash,那么子bash将愉快地识别这个奇怪的环境变量,并将其恢复为函数,一切都按预期运行。
但是,如果子进程不是bash,则行为是未定义的。子进程可以选择将奇怪的环境变量传递给它的子进程,但它也可以选择取消设置这个奇怪的环境变量(因为它看起来奇怪/无效或可能是恶意的!),因此它的子进程(比如,另一个bash)将看不到它。
请注意,导出函数的环境变量命名格式曾经因处理shellshock问题而更改过。
英文:
Bash's export -f
is a nice hack rather than something "standard". See the following commands and outputs (on Linux):
$ foo() { echo hello; }
$ export -f foo
$ bash -c foo
hello
$ bash -c 'tr "$ foo() { echo hello; }
$ export -f foo
$ bash -c foo
hello
$ bash -c 'tr "\0" "\n" < /proc/$$/environ | grep -A 1 foo'
BASH_FUNC_foo%%=() { echo hello
}
" "\n" < /proc/$$/environ | grep -A 1 foo'
BASH_FUNC_foo%%=() { echo hello
}
As we can see, bash puts the exported foo()
's definition in an env var named BASH_FUNC_foo%%
.
If current bash's subprocess is another bash, the sub-bash would happily recognize this weird env var and restore it as a function and all works expected.
But if the subprocess is not bash, then the behavior is undefined. The subprocess may choose to pass the weird env var over to its subprocess, but it can also choose to unset this weird env var <sup>(because it looks weird/invalid or may be malicious!)</sup> so its subprocess <sup>(say, another bash)</sup> would not see it.
Note that the env var naming format for exported functions was ever changed to deal with the shellshock issue.
答案2
得分: 2
尝试将函数序列化为文本:
f() { echo hello; }
git rebase --exec "$(printf "%q " bash -c "$(declare -f f); f")"
英文:
Try serializing the function to text:
f() { echo hello; }
git rebase --exec "$(printf "%q " bash -c "$(declare -f f); f")"
答案3
得分: 1
如先前讨论的,导出的函数存储在 POSIX 未涉及的环境变量中;Shell 既不需要将这些名称传递给子进程,也不禁止这样做。
一个简单的解决方法是将可执行内容放入一个具有明确合法名称的环境变量中。
#!/usr/bin/env bash
f() { echo hello; }
cmd_q="$(declare -f f); f"
cmd_q=$cmd_q git rebase --exec $'bash -c '\''eval "$cmd_q"'\'' "$@"'
英文:
As previously discussed, exported functions are stored in environment variables in a namespace that POSIX does not speak to; shells are neither required to pass through such names to children, nor prohibited from doing so.
One easy workaround is to put eval
able content into an environment variable with an unambiguously legal name.
#!/usr/bin/env bash
f() { echo hello; }
cmd_q="$(declare -f f); f"
cmd_q=$cmd_q git rebase --exec $'bash -c \'eval "$cmd_q"\'' "$@"
答案4
得分: 0
这是我的解决方案。感谢@CharlesDuffy的帮助。
#!/usr/bin/env bash
f() {
for i in "$@"; do
echo "$i"
done
}
printf -v cmd_q '%q ' "$(declare -f f); f" # 序列化函数 f
export cmd_q
printf -v args '%q ' "$@" # 序列化参数
export args
# 删除 -r --root 并根据您的需求进行调整
git rebase -r --root --exec $'bash -c "eval $cmd_q \'$args\'"'
英文:
Here is my solution. Thanks to @CharlesDuffy for his help.
#!/usr/bin/env bash
f() {
for i in "$@"; do
echo "$i"
done
}
printf -v cmd_q '%q ' "$(declare -f f); f" # serialise function f
export cmd_q
printf -v args '%q ' "$@" # serialise arguments f
export args
# remove -r --root and adapt to your needs here
git rebase -r --root --exec $'bash -c "eval $cmd_q \'$args\'"'
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论