如何将 shell 函数传递给`git rebase --exec`

问题描述 投票:0回答:5

在 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 git shell rebase git-rebase
5个回答
2
投票

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问题。


2
投票

尝试将函数序列化为文本:

f() { echo hello; }
git rebase --exec "$(printf "%q " bash -c "$(declare -f f); f")"

1
投票

如前所述,导出的函数存储在 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"\'' "$@"

1
投票

这是我的解决方案。感谢 @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\'"'

0
投票

注意:如果您需要使用 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
)
(由 Junio C Hamano --
gitster
--
合并于 commit 029c870,2024 年 8 月 28 日)

rebase --exec
:尊重
--quiet

报道者:林肯裕二
报道者:Rodrigo Siqueira
签署人:Matheus Tavares

rebase --exec
(man)不服从
--quiet
并最终打印有关正在执行的命令的消息:

git rebase HEAD~3 --quiet --exec true
Executing: true
Executing: true
Executing: true

让我们通过在使用

Executing
时省略“
--quiet
”消息来解决这个问题。

此外,定序器代码还包含一些对

term_clear_line()
的调用,它会打印一个特殊的字符序列来擦除 stderr 上显示的前一行(即使尚未打印任何内容)。
对于以交互方式运行命令的用户来说,无论有没有
--quiet
调用此函数的最终效果都与字符在终端中不可见相同。
然而,当将输出重定向到文件或管道到另一个命令时,这些不可见字符的存在是显而易见的,并且它可能会破坏用户的期望,因为
--quiet
没有得到尊重。

当使用

term_clear_line()
时,我们可以跳过
--quiet
调用,就像我们对“执行”消息所做的那样,但在 stderr 为 TTY 时进行行清理更有意义,因为这些字符实际上只是有用的用于 TTY 输出。

© www.soinside.com 2019 - 2024. All rights reserved.