bash expand cd with shortcuts like zsh

丶灬走出姿态 提交于 2019-11-30 14:28:35
Aserre

Sorry I couldn't post earlier, I was held at work, and the bind function was more issue-prone than I first thought.

Here is what I came up with :

Bind the following script :

#!/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 } 

with the following command :

    source "$HOME/.bashrc.d/autocomplete.sh"      bind -x '"\t" : autocomplete_wrapper' 

Output :

>$ cd /u/lo/b<TAB> >$ cd /usr/local/bin   >$ cd /u/l<TAB> /usr/local /usr/lib >$ cd /usr/l 

The bind line could be added to your ~/.bashrc file, doing something like this :

if [[ -s "$HOME/.bashrc.d/autocomplete.sh" ]]; then     source "$HOME/.bashrc.d/autocomplete.sh"      bind -x '"\t" : autocomplete_wrapper' fi 

(taken from this answer)

Furthermore, I would strongly advise against binding this command to your Tab key as it would override the default autocomplete.

Note: In some cases, this will misbehave, for isntance if you try to autocomplete "/path/with spaces/something", as the last argument to complete is determined by ${READLINE_LINE##* }. If this is an issue in your case, you should code a function that returns the last argument of a line when considering quotes

Feel free to ask for further clarification, and I welcome any suggestion to improve this script

I have come up with an alternative solution that does not break existing bash completion rules in other places.

The idea is to append a wildcard (asterisk) to every element of the path and invoke normal bash completion process from there. So when user types /u/lo/b<Tab> my function substitutes that with /u*/lo*/b* and invokes bash completion as usual.

To enable the described behavior source this file from your ~/.bashrc. Supported features are:

  • Special characters in completed path are automatically escaped if present
  • Tilde expressions are properly expanded (as per bash documentation)
  • If user had started writing the path in quotes, no character escaping is applied. Instead the quote is closed with a matching character after expanding the path.
  • If bash-completion package is already in use, this code will safely override its _filedir function. No extra configuration is required.

Watch a demo screencast to see this feature in action:

Full code listing below (you should check the GitHub repo for latest updates though):

#!/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' 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!