目录更改时的 Bash 选项卡完成刷新

目录更改时的 Bash 选项卡完成刷新

我有一个名为(作为我的 bashrc 中的函数)的命令up,它允许我轻松地转到我所在目录之上的指定目录。该命令运行良好,但是添加 bash 选项卡补全将使其更加有用。我编写了一个完成脚本,用于标识当前路径中的目录并列出它们以进行完成。我遇到的问题是,每当我更改目录时,完成脚本似乎都不会重新加载,给我的结果就像我仍在上一个目录中一样。我能够up通过添加exec bash命令函数来修复该命令,但是我不确定是否可以对任意目录更改命令执行此操作。我怀疑这可以通过在命令运行exec bash时添加 for 来完成cd,但我不确定执行此操作的最佳方法是什么。

举个例子:

如果我打开终端,我将位于我的主目录中:

~$

如果我输入up <TAB><TAB>,我会得到:

~$ up
username home

其中用户名是我在我的电脑上的用户名。然后我 cd 进入dir1/dir2/,然后输入up <TAB><TAB>,并得到:

~/dir1/dir2$ up
username home

再次,即使我已经更改了目录。

我的完成脚本和up脚本的代码如下:

完成脚本:

#/usr/bin/env bash
LS=$(echo $PWD)
LSARRAY=${LS//\// }
complete -W "$LSARRAY" up

Up 命令(在 .bashrc 中)

LS=$(echo $PWD)
LSARRAY=(${LS//\// })
i=1
if [[ $LS == *$1/* ]]; then
  FIND=$1
  if [[ $1 == */* ]]; then
    ARR=(${FIND//\// })
    FIND=${ARR[${#ARR[@]}-1]}
  fi
  while [ "${LSARRAY[${#LSARRAY[@]}-$i]}" != "$FIND" ] && [ $i != ${#LSARRAY[@]} ]; do
    cd ../
    i=$(($i+1))
  done
  exec bash
else
  echo "Did not find directory $1"
fi

答案1

这是函数的另一种实现up(包括其可编程完成对应项),它尝试:

  • 使用任意路径(包括空格字符和换行符);
  • 避免改变IFS变量,这对于正确恢复;
  • 显式地处理一些特殊目录(., ..; 此处以任意方式处理,您可能需要根据您的喜好调整代码);

功能up

up () {
  local path=$PWD
  case $1 in
    (''|'..')
      cd ..; return ;;
    (.)
      cd .; return ;;
    (/)
      cd /; return ;;
    (*/*)
      printf '"%s" %s\n' "$1" 'contains forward slashes, up will not work' >&2
      return 1 ;;
  esac
  if [[ $path != /* ]]; then
    printf '%s\n' 'PWD is not an absolute path, up will not work' >&2
    return 1
  fi
  until [[ $path = / ]]; do
    path=${path%/*}
    path=${path:-/}
    if [[ ${path%"$1"} != "$path" ]] # You may add && [[ -d ${path%"$1"} ]] to
                            # make "up oo" fail when you are in `/foo/bar`,
                            # while "up foo" would succeed anyway
    then
      cd -- "$path"
      return
    fi
  done
  printf '%s "%s" %s\n' 'Directory' "$1" 'not found' >&2
  return 1
}

及其_up完成功能和说明(complete命令):

_up () {
  local cur last path
  declare -A ancestors
  cur=${COMP_WORDS[$COMP_CWORD]}
  path=$PWD
  if [[ $path != /* ]]; then
    return 1
  fi
  until [[ -z $path ]]; do
    path=${path%/*}
    last=${path##*/}
    last=${last:-/}
    if [[ $last == "${cur}"* ]]; then
      ancestors+=( ["$last"]= )
    fi
  done
  COMPREPLY=( "${!ancestors[@]}" )
  return
}
complete -o nospace -o filenames -F _up up

这两个代码片段都应该放置在您的.bashrc.如果您正在使用bash 完成您可能更喜欢将补全函数和规范存储在$XDG_DATA_HOME/bash-completion~/.local/share/bash-completion或 中~/.bash_completion(有关更多信息,请参阅墨水页面上的常见问题解答)。

包含正斜杠的参数被故意排除,因为否则(在我看来),除此之外foo/bar我们还应该注意,例如foo///bar/./../baz(即执行某种形式的规范化)。

使用关联数组 ( declare -A) 需要 Bash >= 4.0。

答案2

LS您的问题是和变量的评估LSARRAY仅发生一次,因此它将始终使用定义完成时up的父文件夹来完成命令。PWD

为了动态提取父文件夹,您需要创建一个完成函数。这样,每次您尝试根据实际的 current 完成 up 命令时,都会对LS和变量进行评估。LSARRAYPWD

_complete_up()
{
  local cur=${COMP_WORDS[COMP_CWORD]}
  local LS=$(echo $PWD)
  local LSARRAY=${LS//\// }
  COMPREPLY=( $(compgen -W "${LSARRAY}" -- $cur) )
  return 0
}

complete -F _complete_up up

答案3

你可以这样做:

_up () {
        local oldIFS="$IFS" LS="${PWD%/*}"
        IFS='/
'
        if [ -n "$2" ]; then
                COMPREPLY=($(compgen -W "${LS#/}" "$2"))
        else
                COMPREPLY=(${LS#/})
        fi
        IFS="$oldIFS"
}

complete -F _up up

up () {
        local oldIFS="$IFS" i=1 LSARRAY LS="$PWD" FIND ARR
        IFS='/
'
        LSARRAY=(${PWD#/})
        if [[ $LS == */$1/* ]]; then
                FIND=$1
                while [ "${LSARRAY[${#LSARRAY[@]}-$i]}" != "$FIND" ] && [ $i != ${#LSARRAY[@]} ]; do
                        cd ..
                        i=$(($i+1))
                done
        else
                echo "Did not find directory '$1'"
        fi
        IFS="$oldIFS"
}

主要问题是(如前面的答案中提到的)您的完成可能性不是动态生成的。

此外,您的代码不适用于目录名称中的空格。我的不支持目录名称中的换行符。

我还从选择列表中删除了当前目录,因为这没有意义。

我不明白你if [[ $1 == */* ]]应该做什么(无论如何应该if [[ $FIND == */* ]]),因为目录名称永远不能包含,/所以我删除了它。

相关内容