如何在自定义Bash补全中避免source配置文件后fzf出现递归调用导致会话崩溃?
如何在自定义Bash补全中避免source配置文件后fzf出现递归调用导致会话崩溃?
这个问题我太有共鸣了!之前做自定义Bash补全时也踩过一模一样的坑——核心原因是每次source配置文件时,你的补全包装逻辑会重复执行,把已经包装过的补全函数再套一层,最终触发无限递归导致会话崩溃。咱们一步步拆解问题,再给出靠谱的修复方案:
问题到底出在哪?
咱们还原两次source .bashrc的完整过程:
第一次source:
_fzf_setup_completion path foo为foo设置了fzf的路径补全函数(比如内部叫_fzf_path_comp_foo)。- 接着source
foo_completion.sh:它提取出原始的fzf补全函数,定义_foo_comp调用这个原始函数并追加--bar,最后把foo的补全切换为_foo_comp。
这时候一切正常,_foo_comp只会调用一次原始的fzf函数。
第二次source:
- 关键细节:fzf的
_fzf_setup_completion函数会判断目标命令是否已有补全配置,如果有就不会重复设置(避免覆盖用户自定义的补全)。所以这一步不会把foo的补全改回fzf的函数。 - 然后再次source
foo_completion.sh:这时候complete -p foo返回的是当前的补全配置——也就是上一次定义的_foo_comp!你的脚本会把这个_foo_comp存为__orig_foo_completion_func,然后新的_foo_comp会调用它……等于自己调用自己,直接触发无限递归,会话瞬间崩溃。
- 关键细节:fzf的
修复方案1:添加“已包装”标记(最简单直接)
在foo_completion.sh开头加一个判断,只要已经执行过一次包装逻辑,就直接跳过,从根源上避免重复嵌套:
# ~/foo_completion.sh # 标记:如果已经包装过foo的补全,直接返回 if [[ -n "$__FOO_COMPLETION_WRAPPED" ]]; then return 0 fi # 处理foo原本无补全的边界情况 if ! complete -p foo 2>/dev/null; then _foo_comp() { if [[ $COMP_CWORD -eq 1 ]]; then COMPREPLY=($(compgen -W '--bar' -- "${COMP_WORDS[COMP_CWORD]}")) fi } complete -F _foo_comp foo export __FOO_COMPLETION_WRAPPED=1 return 0 fi # 提取原始补全函数并包装 __orig_foo_completion="$(complete -p foo 2>/dev/null)" __orig_foo_completion_func="$(echo "$__orig_foo_completion" | sed -r 's/.*\s+-F\s+([a-zA-Z_]+)\s+[a-zA-Z_]+/\1/')" _foo_comp() { "$__orig_foo_completion_func" # 优化:只在第一个参数位置补全--bar,避免重复添加 if [[ $COMP_CWORD -eq 1 ]]; then COMPREPLY+=($(compgen -W '--bar' -- "${COMP_WORDS[COMP_CWORD]}")) fi } complete -F _foo_comp foo # 设置全局标记,标记已完成包装 export __FOO_COMPLETION_WRAPPED=1
修复方案2:只保存一次原始补全函数
如果不想用全局标记,也可以判断原始补全函数变量是否已经存在,只在第一次时保存原始函数:
# ~/foo_completion.sh # 只有当原始补全函数还未保存时,才提取它 if [[ -z "$__orig_foo_completion_func" ]]; then # 处理foo原本无补全的边界情况 if ! complete -p foo 2>/dev/null; then __orig_foo_completion_func=": # 空函数占位" else __orig_foo_completion="$(complete -p foo 2>/dev/null)" __orig_foo_completion_func="$(echo "$__orig_foo_completion" | sed -r 's/.*\s+-F\s+([a-zA-Z_]+)\s+[a-zA-Z_]+/\1/')" fi fi # 只有当foo的补全还不是我们的自定义函数时,才设置 if ! complete -p foo 2>/dev/null | grep -q '_foo_comp'; then _foo_comp() { # 调用原始补全函数(如果存在) if [[ "$__orig_foo_completion_func" != ": # 空函数占位" ]]; then "$__orig_foo_completion_func" fi # 仅在第一个参数位置补全--bar if [[ $COMP_CWORD -eq 1 ]]; then COMPREPLY+=($(compgen -W '--bar' -- "${COMP_WORDS[COMP_CWORD]}")) fi } complete -F _foo_comp foo fi
这个方案更灵活,即使你手动修改了foo的补全,只要原始函数的引用还在,就能正确恢复逻辑。
额外优化建议
- 避免无差别追加补全项:我在代码里加了
if [[ $COMP_CWORD -eq 1 ]]的判断,只有当用户输入第一个参数时才补全--bar,避免在后续参数位置重复出现无效的补全项。 - 处理边界情况:增加了
foo原本没有任何补全函数的判断,让脚本在各种场景下都能正常工作。
备注:内容来源于stack exchange,提问作者solisoares
相关产品推荐
相关产品推荐

