rsync、scp “tab” 卡顿问题_服务器tab键反应慢怎么办


如果是用 apt-get 或者 yum 进行安装的,那么 bash-completion 的脚本位置位于



/usr/share/bash-completion/


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200814144533677.png#pic_center)  
 其中 bash\_completion 是主配置文件,定义了许多通用函数;completions 目录是用于存储规定各命令怎样补全的脚本。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200814144547189.png#pic_center)


那么对于bash-completion默认不支持补全功能的一些命令,就可以自行编写或下载对应的补全脚本放在 /usr/share/bash-completion/completions 目录下即可。


## 04解决方案


根据上文,我们知道 /usr/share/bash-completion/completions/ 目录下的文件即是用于存储各命令补全的脚本,那么解决卡顿问题最简单粗暴的方法就是删除对应的补全脚本文件。



mv /usr/share/bash-completion/completions/rsync /tmp/rsync
mv /usr/share/bash-completion/completions/scp /tmp/scp


退出终端后,测试,果然 rsync 与 scp 的 tab 补全失效了  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200814144631989.png#pic_center)


这虽然也解决了问题,但是却让 rsync 与 scp 的全部命令补全功能失效,因此方案需要进一步改进。


接下来查看一下 rsync 命令补全文件:



bash completion for rsync -*- shell-script -*-

_rsync()
{
# cur 表示当前光标下的单词
# prev 表示上一个单词
# words 数组,存放当前命令行中输入的所有单词
# cword 整数,当前光标下输入的单词位于 words 数组的索引
local cur prev words cword split
# 调用主配置文件的 init_completion() 函数初始化命令补全
_init_completion -s -n : || return
# 匹配当前光标的上一个单词
case $prev in
–config|–password-file|–include-from|–exclude-from|–files-from|
–log-file|–write-batch|–only-write-batch|–read-batch)
compopt +o nospace
# 调用 filedir() 函数输出当前目录下的文件与目录
_filedir
return
;;
–temp-dir|–compare-dest|–backup-dir|–partial-dir|–copy-dest|
–link-dest|-!(-*)T)
compopt +o nospace
# 调用 filedir() 函数加 -d 参数输出当前目录下的目录
_filedir -d
return
;;
–rsh|-!(-*)e)
compopt +o nospace
# COMPREPLY:候选的补全结果
# compgen 内置补全命令,根据不同的参数,生成匹配单词的候选补全列表
# -W 参数为指定空格分隔的单词列表
COMPREPLY=( ( c o m p g e n − W ′ r s h s s h ′ − − " (compgen -W 'rsh ssh' -- " (compgenWrshssh"cur") )
return
;;
–compress-level)
compopt +o nospace
COMPREPLY=( ( c o m p g e n − W ′ 1..9 ′ − − " (compgen -W '{1..9}' -- " (compgenW1..9"cur") )
return
;;
esac

$split && return

_expand || return
    # 匹配当前光标下的单词
case $cur in
    -\*)
        COMPREPLY=( $(compgen -W '--verbose --quiet --no-motd --checksum
 ...(此处省略)

’ – “$cur”) )
[[ KaTeX parse error: Undefined control sequence: \* at position 14: COMPREPLY == \̲*̲= ]] || compopt…{words[i]}" == -@(e|-rsh) ]]; then
shell=${words[i+1]}
break
fi
done
[[ KaTeX parse error: Expected 'EOF', got '&' at position 17: …hell == ssh ]] &̲& _xfunc ssh _s…cur"
# 调用命令补全脚本 ssh 下的 scp_local_files() 函数,补全本地文件列表
_xfunc ssh _scp_local_files
;;
esac
} &&
complete -F _rsync -o nospace rsync

ex: filetype=sh


scp 命令补全脚本基本也一致,其获取目标主机下的文件列表的代码如下



case $cur in
!(*😗)/*|[.~]*) ;; # looks like a path
*😗) _scp_remote_files ; return 0 ;;
esac


此时就有一个解决方案就是注释掉 rsync 和 scp 的获取目标主机下文件列表的代码,这样就不会造成卡顿,rsync 和 scp 的其它命令补全仍然可用。但是这样也有一个缺点就是,在目标主机后使用 tab 就什么都 tab 不出来。  
 而线上服务器的很多文件路径都是一致的,因此想让在目标主机后 tab 出现的是本地文件路径。  
 那么下面让我们再来看看补全脚本中补全目标主机下的文件路径与补全本地文件路径的代码。



rsync:
*😗)
# 获取远程主机下的文件路径
# find which remote shell is used
local i shell=ssh
for (( i=1; i < cword; i++ )); do
if [[ “ w o r d s [ i ] " = = − @ ( e ∣ − r s h ) ] ] ; t h e n s h e l l = {words[i]}" == -@(e|-rsh) ]]; then shell= words[i]"==@(ersh)]];thenshell={words[i+1]}
break
fi
done
[[ KaTeX parse error: Expected 'EOF', got '&' at position 17: …hell == ssh ]] &̲& _xfunc ssh _s…cur”
# 调用命令补全脚本 ssh 下的 scp_local_files() 函数,补全本地文件列表
_xfunc ssh _scp_local_files
;;

scp:
case $cur in
!(*😗)/*|[.~]*) ;; # looks like a path
*😗) _scp_remote_files ; return 0 ;;
esac


补全远程主机下的文件路径主要是通过调用 ssh 的 scp\_remote\_files() 函数,而补全本地文件路径则是通过调用 ssh scp\_local\_files() 函数。


下面先看一下 scp\_remote\_files 函数:



Complete remote files with ssh. If the first arg is -d, complete on dirs

only. Returns paths escaped with three backslashes.

_scp_remote_files()
{
local IFS=$‘\n’

# remove backslash escape from the first colon
# 将当前光标下的单词中的 "\:" 转换为 ":" 
cur=${cur/\\:/:}
    # 获取目标主机名
local userhost=${cur%%?(\\):\*}
# 获取路径
local path=${cur#\*:}

# unescape (3 backslashes to 1 for chars we escaped)
path=$( sed -e 's/\\\\\\\('$_scp_path_esc'\)/\\\1/g' <<<"$path" )

# default to home dir of specified user on remote host
if [[ -z $path ]]; then
    path=$(ssh -o 'Batchmode yes' $userhost pwd 2>/dev/null)
fi

local files
# 到远程去补全路径
if [[ $1 == -d ]]; then
    # escape problematic characters; remove non-dirs
    files=$( ssh -o 'Batchmode yes' $userhost \
        command ls -aF1dL "$path\*" 2>/dev/null | \
        sed -e 's/'$_scp_path_esc'/\\\\\\&/g' -e '/[^\/]$/d' )
else
    # escape problematic characters; remove executables, aliases, pipes
    # and sockets; add space at end of file names
    files=$( ssh -o 'Batchmode yes' $userhost \
        command ls -aF1dL "$path\*" 2>/dev/null | \
        sed -e 's/'$_scp_path_esc'/\\\\\\&/g' -e 's/[\*@|=]$//g' \
        -e 's/[^\/]$/& /g' )
fi
COMPREPLY+=( $files )

}


scp\_local\_files() 函数如下



This approach is used instead of _filedir to get a space appended

after local file/dir completions, and -o nospace retained for others.

If first arg is -d, complete on directory names only. The next arg is

an optional prefix to add to returned completions.

_scp_local_files()
{
local IFS=$‘\n’

local dirsonly=false
if [[ $1 == -d ]]; then
    dirsonly=true
    shift
fi

if $dirsonly ; then
    COMPREPLY+=( $( command ls -aF1dL $cur\* 2>/dev/null | \
        sed -e "s/$\_scp\_path\_esc/\\\\&/g" -e '/[^\/]$/d' -e "s/^/$1/") )
else
    COMPREPLY+=( $( command ls -aF1dL $cur\* 2>/dev/null | \
        sed -e "s/$\_scp\_path\_esc/\\\\&/g" -e 's/[\*@|=]$//g' \
        -e 's/[^\/]$/& /g' -e "s/^/$1/") )
fi

}


从上述看出,当执行下列命令后,bash-completion 首先会调用 rsync 命令补全脚本进行匹配,然后通过一个判断选择匹配到 “*:*” 项,调用 scp\_remote\_file 函数获取目标主机路径。scp\_remote\_file() 函数中,此时 cur 变量的值为"compute:",那么 path 变量的值为空 ,在通过一个 if 语句判断如果 path 值为空,则到 ssh 到远程目标主机执行 pwd 命令并将结果赋值给 path,然后通过 ssh 远程到目标主机执行 ls -aF1dL “$path\*” 命令获取所有以 $path 开头的最小路径。



rsync -av compute:[按 tab键]


scp\_local\_files() 函数也是一样,不过只是少了 ssh 到远程主机执行对应的补全命令。  
 因此我们若是想要让在目标主机后按 tab 键补全的是本地文件路径,那么我们就可以调用 scp\_local\_files 函数进行补全,不过在调用之前,需要将 cur 的值进行一个切割,只保留其后面的路径部分,因此对于 rsync 命令补全脚本,我们可以这么改。



将 /usr/share/bash-completion/completions/rsync 配置文件中 *😗) 选项下的命令更改为

*😗)
cur=${cur#*:}
_xfunc ssh _scp_local_files

# find which remote shell is used

local i shell=ssh

for (( i=1; i < cword; i++ )); do

if [[ “${words[i]}” == -@(e|-rsh) ]]; then

shell=${words[i+1]}

break

fi

done

[[ $shell == ssh ]] && _xfunc ssh _scp_remote_files

;;


  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值