在 bash 中,要将变量或函数传递给子进程,需要将其导出。
#!/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"
但是,我似乎无法达到与
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
我知道我可以内联声明该函数:
git rebase --exec 'f() { echo hello; }; f'
或将该函数放入另一个脚本中并调用它:git rebase --exec './f.sh'
有办法让它发挥作用吗?我错过了什么?
Bash 的
export -f
是一个很好的 hack,而不是“标准”的东西。请参阅以下命令和输出(在 Linux 上):
$ 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
}
正如我们所见,bash 将导出的
foo()
的定义放入名为 BASH_FUNC_foo%%
的环境变量中。
如果当前 bash 的子进程是另一个 bash,则子 bash 会很高兴地识别这个奇怪的环境变量并将其恢复为函数,并且一切正常。
但是如果子进程不是 bash,则行为是未定义的。子进程可以选择将奇怪的环境变量传递给它的子进程,但它也可以选择取消设置这个奇怪的环境变量(因为它看起来很奇怪/无效或可能是恶意的!)所以它的子进程(比如说,另一个bash ) 不会看到它。
请注意,导出函数的环境变量命名格式曾经更改过以处理shellshock问题。
尝试将函数序列化为文本:
f() { echo hello; }
git rebase --exec "$(printf "%q " bash -c "$(declare -f f); f")"
如前所述,导出的函数存储在 POSIX 不涉及的命名空间中的环境变量中;既不要求 shell 向子进程传递这些名称,也不禁止这样做。
一个简单的解决方法是将
eval
可内容放入具有明确合法名称的环境变量中。
#!/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"\'' "$@"
这是我的解决方案。感谢 @CharlesDuffy 的帮助。
#!/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\'"'
注意:如果您需要使用 exec 进行 rebase(就像您的
git rebase -r --root --exec $'bash -c "eval $cmd_q \'$args\'"'
)也安静,请确保使用 Git 2.47(2024 年第 4 季度),batch 10:“git rebase -x --quiet
”()哥们)之前不安静,已更正。
请参阅提交 4bdd6b7(2024 年 8 月 20 日),作者:Matheus Tavares (
matheustavares
)。gitster
-- 合并于 commit 029c870,2024 年 8 月 28 日)
:尊重rebase --exec
--quiet
报道者:林肯裕二
报道者:Rodrigo Siqueira
签署人:Matheus Tavares
(man)不服从rebase --exec
并最终打印有关正在执行的命令的消息:--quiet
git rebase HEAD~3 --quiet --exec true Executing: true Executing: true Executing: true
让我们通过在使用
时省略“Executing
”消息来解决这个问题。--quiet
此外,定序器代码还包含一些对
的调用,它会打印一个特殊的字符序列来擦除 stderr 上显示的前一行(即使尚未打印任何内容)。term_clear_line()
对于以交互方式运行命令的用户来说,无论有没有调用此函数的最终效果都与字符在终端中不可见相同。--quiet
然而,当将输出重定向到文件或管道到另一个命令时,这些不可见字符的存在是显而易见的,并且它可能会破坏用户的期望,因为没有得到尊重。--quiet
当使用
时,我们可以跳过term_clear_line()
调用,就像我们对“执行”消息所做的那样,但在 stderr 为 TTY 时进行行清理更有意义,因为这些字符实际上只是有用的用于 TTY 输出。--quiet