是否可以在bash中扩展类似
的内容 cd /u/lo/b
<点击标签>
到
cd /usr/local/bin
答案 0 :(得分:6)
抱歉,我之前无法发帖,我在工作,并且绑定功能比我最初想的更容易发生问题。
以下是我提出的建议:
绑定以下脚本:
#!/bin/bash
#$HOME/.bashrc.d/autocomplete.sh
autocomplete_wrapper() {
BASE="${READLINE_LINE% *} " #we save the line except for the last argument
[[ "$BASE" == "$READLINE_LINE " ]] && BASE=""; #if the line has only 1 argument, we set the BASE to blank
EXPANSION=($(autocomplete "${READLINE_LINE##* }"))
[[ ${#EXPANSION[@]} -gt 1 ]] && echo "${EXPANSION[@]:1}" #if there is more than 1 match, we echo them
READLINE_LINE="$BASE${EXPANSION[0]}" #the current line is now the base + the 1st element
READLINE_POINT=${#READLINE_LINE} #we move our cursor at the end of the current line
}
autocomplete() {
LAST_CMD="$1"
#Special starting character expansion for '~', './' and '/'
[[ "${LAST_CMD:0:1}" == "~" ]] && LAST_CMD="$HOME${LAST_CMD:1}"
S=1; [[ "${LAST_CMD:0:1}" == "/" || "${LAST_CMD:0:2}" == "./" ]] && S=2; #we don't expand those
#we do the path expansion of the last argument here by adding a * before each /
EXPANSION=($(echo "$LAST_CMD*" | sed s:/:*/:"$S"g))
if [[ ! -e "${EXPANSION[0]}" ]];then #if the path cannot be expanded, we don't change the output
echo "$LAST_CMD"
elif [[ "${#EXPANSION[@]}" -eq 1 ]];then #else if there is only one match, we output it
echo "${EXPANSION[0]}"
else
#else we expand the path as much as possible and return all the possible results
while [[ $l -le "${#EXPANSION[0]}" ]]; do
for i in "${EXPANSION[@]}"; do
if [[ "${EXPANSION[0]:$l:1}" != "${i:$l:1}" ]]; then
CTRL_LOOP=1
break
fi
done
[[ $CTRL_LOOP -eq 1 ]] && break
((l++))
done
#we add the partial solution at the beggining of the array of solutions
echo "${EXPANSION[0]:0:$l} ${EXPANSION[@]}"
fi
}
使用以下命令:
source "$HOME/.bashrc.d/autocomplete.sh"
bind -x '"\t" : autocomplete_wrapper'
输出:
>$ cd /u/lo/b<TAB>
>$ cd /usr/local/bin
>$ cd /u/l<TAB>
/usr/local /usr/lib
>$ cd /usr/l
可以将绑定行添加到您的~/.bashrc
文件中,执行以下操作:
if [[ -s "$HOME/.bashrc.d/autocomplete.sh" ]]; then
source "$HOME/.bashrc.d/autocomplete.sh"
bind -x '"\t" : autocomplete_wrapper'
fi
(摘自this answer)
此外,我强烈建议不要将此命令绑定到 Tab 键,因为它会覆盖默认的自动完成。
注意:在某些情况下,如果您尝试自动填充"/path/with spaces/something"
,则会出现错误,因为要完成的最后一个参数由${READLINE_LINE##* }
确定。如果在您的情况下这是一个问题,您应该编写一个函数,在考虑引号时返回行的最后一个参数
请随时要求进一步澄清,我欢迎任何改进此脚本的建议
答案 1 :(得分:1)
我想出了一种不破坏现有bash的替代解决方案 其他地方的完成规则。
这个想法是在路径的每个元素后面加上通配符(星号),
从那里调用正常的bash完成过程。所以当用户输入
/u/lo/b<Tab>
我的函数将其替换为/u*/lo*/b*
并调用bash
照常完成。
要从您的帐户启用描述的行为来源this file 〜/ .bashrc。支持的功能有:
_filedir
函数。不需要额外的配置。下面列出了完整的代码(不过,您应检查GitHub repo中是否有最新更新):
#!/usr/bin/env bash
#
# Zsh-like expansion of incomplete file paths for Bash.
# Source this file from your ~/.bashrc to enable the described behavior.
#
# Example: `/u/s/a<Tab>` will be expanded into `/usr/share/applications`
#
# Copyright 2018 Vitaly Potyarkin
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Take a single incomplete path and fill it with wildcards
# e.g. /u/s/app/ -> /u*/s*/app*
#
_put_wildcards_into_path() {
local PROCESSED TILDE_EXPANSION
PROCESSED=$( \
echo "$@" | \
sed \
-e 's:\([^\*\~]\)/:\1*/:g' \
-e 's:\([^\/\*]\)$:\1*:g' \
-e 's:\/$::g' \
-e 's:^\(\~[^\/]*\)\*\/:\1/:' \
-Ee 's:(\.+)\*/:\1/:g' \
)
eval "TILDE_EXPANSION=$(printf '%q' "$PROCESSED"|sed -e 's:^\\\~:~:g')"
echo "$TILDE_EXPANSION"
}
#
# Bash completion function for expanding partial paths
#
# This is a generic worker. It accepts 'file' or 'directory' as the first
# argument to specify desired completion behavior
#
_complete_partial() {
local WILDCARDS ACTION LINE OPTION INPUT UNQUOTED_INPUT QUOTE
ACTION="$1"
if [[ "_$1" == "_-d" ]]
then # _filedir compatibility
ACTION="directory"
fi
INPUT="${COMP_WORDS[$COMP_CWORD]}"
# Detect and strip opened quotes
if [[ "${INPUT:0:1}" == "'" || "${INPUT:0:1}" == '"' ]]
then
QUOTE="${INPUT:0:1}"
INPUT="${INPUT:1}"
else
QUOTE=""
fi
# Add wildcards to each path element
WILDCARDS=$(_put_wildcards_into_path "$INPUT")
# Collect completion options
COMPREPLY=()
while read -r -d $'\n' LINE
do
if [[ "_$ACTION" == "_directory" && ! -d "$LINE" ]]
then # skip non-directory paths when looking for directory
continue
fi
if [[ -z "$LINE" ]]
then # skip empty suggestions
continue
fi
if [[ -z "$QUOTE" ]]
then # escape special characters unless user has opened a quote
LINE=$(printf "%q" "$LINE")
fi
COMPREPLY+=("$LINE")
done <<< $(compgen -G "$WILDCARDS" "$WILDCARDS" 2>/dev/null)
return 0 # do not clutter $? value (last exit code)
}
# Wrappers
_complete_partial_dir() { _complete_partial directory; }
_complete_partial_file() { _complete_partial file; }
# Enable enhanced completion
complete -o bashdefault -o default -o nospace -D -F _complete_partial_file
# Optional. Make sure `cd` is autocompleted only with directories
complete -o bashdefault -o default -o nospace -F _complete_partial_dir cd
# Override bash-completion's _filedir (if it's in use)
# https://salsa.debian.org/debian/bash-completion
_filedir_original_code=$(declare -f _filedir|tail -n+2)
if [[ ! -z "$_filedir_original_code" ]]
then
eval "_filedir_original() $_filedir_original_code"
_filedir() {
_filedir_original "$@"
_complete_partial "$@"
}
fi
# Readline configuration for better user experience
bind 'TAB:menu-complete'
bind 'set colored-completion-prefix on'
bind 'set colored-stats on'
bind 'set completion-ignore-case on'
bind 'set menu-complete-display-prefix on'
bind 'set show-all-if-ambiguous on'
bind 'set show-all-if-unmodified on'