我有一个名为(作为我的 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
和变量进行评估。LSARRAY
PWD
_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 == */* ]]
),因为目录名称永远不能包含,/
所以我删除了它。