如何创建Bash完成脚本

我最近致力于为一个项目创建Bash完成脚本,我非常喜欢它。 在本文中,我将尝试使您熟悉创建Bash完成脚本的过程。

什么是Bash完成?

Bash完成是一项功能,Bash通过该功能可以帮助用户更快,更轻松地键入命令。 通过在用户键入命令时按Tab键时显示可能的选项来实现此目的。



   
   
$ git < tab >< tab >
git                 git-receive-pack     git-upload-archive  
gitk                 git-shell           git-upload-pack    
$ git-s < tab >
$ git-shell

这个怎么运作

complete定义可以为给定可执行文件显示哪些完成建议 。 完成选项的性质各不相同,从简单的静态到高度复杂。

何必呢?

此功能通过以下方式为用户提供帮助:

  • 可以自动完成输入的文字保存起来
  • 帮助他们知道命令的可用延续
  • 通过隐藏或显示基于已键入内容的选项来防止错误并改善其体验

动手

这是本教程将要执行的操作:

我们将首先创建一个称为dothis的虚拟可执行脚本。 它所做的只是执行驻留在用户历史记录中作为参数传递的数字上的命令。 例如,假设历史记录中存在编号为235命令,则以下命令将仅执行ls -a命令:


dothis 235 

然后,我们将创建一个Bash完成脚本,该脚本将显示命令以及用户历史记录中的命令编号,并将其“绑定”到dothis可执行文件中。



   
   
$ dothis < tab >< tab >
215 ls
216 ls -la
217 cd ~
218 man history
219 git status
220 history | cut -c 8 -
dothis executable screen

您可以在GitHub上本教程的代码存储库中看到展示该功能的gif文件。

让演出开始。

创建可执行脚本

在工作目录中创建一个名为dothis的文件,并添加以下代码:



   
   
if [ -z "$1" ] ; then
  echo "No command number passed"
  exit 2
fi

exists =$ ( fc -l -1000 | grep ^ $1 -- 2 >/ dev / null )

if [ -n " $exists " ] ; then
  fc -s -- "$1"
else
  echo "Command with number $1 was not found in recent history"
  exit 2
fi

笔记:

  • 我们首先检查脚本是否通过参数调用
  • 然后,我们检查最近的1000个命令中是否包含特定的数字
    • 如果存在,我们将使用fc功能执行命令
    • 否则,我们会显示一条错误消息

使用以下命令使脚本可执行:


chmod +x . / dothis 

我们将在本教程中多次执行此脚本,因此建议您将其放在路径中包含的文件夹中,以便我们可以通过键入dothis从任何位置访问它。

我使用以下命令将其安装在我的家庭bin文件夹中:


install . / dothis ~ / bin / dothis 

如果您有~/bin文件夹,并且它包含在PATH变量中,则可以执行相同的操作。

检查它是否正常工作:


dothis 

您应该看到以下内容:



   
   
$ dothis
No command number passed

做完了

创建完成脚本

创建一个名为dothis-completion.bash的文件。 从现在开始,我们将使用术语完成脚本来引用该文件。

向其中添加一些代码后,我们将为其提供source ,以使完成生效。 我们必须source这个文件我们在它改变的东西每一次

在本教程的后面,我们将讨论每当Bash shell打开时用于注册该脚本的选项。

静态完成

假设dothis程序支持命令列表,例如:

  • now
  • tomorrow
  • never

让我们使用complete命令注册此列表以完成操作。 为了使用适当的术语,我们说我们使用complete命令来为程序定义完成规范( compspec )。

将此添加到完成脚本。



   
   
#/usr/bin/env bash
complete -W "now tomorrow never" dothis

这是我们在上面的complete命令中指定的内容:

  • 我们使用了-Wwordlist )选项来提供要完成的单词列表。
  • 我们定义了将这些完成词用于哪个“程序”( dothis参数)

源文件:


source . / dothis-completion.bash 

现在,尝试在命令行中按两次Tab键,如下所示:



   
   
$ dothis < tab >< tab >
never     now       tomorrow

输入n后再试一次:



   
   
$ dothis n < tab >< tab >
never now

魔法! 完成选项将自动过滤以仅匹配以n开头的选项。

注意:这些选项不会按照我们在单词列表中定义的顺序显示。 它们会自动排序。

除了本节中使用的-W ,还有许多其他选项可以使用。 大多数产品以固定的方式生产完井液,这意味着我们不会动态干预以过滤其输出。

例如,如果要使用目录名作为dothis程序的完成词,则可以将complete命令更改为以下内容:


complete -A directory dothis 

dothis程序之后按Tab键将为我们提供执行脚本的当前目录中的目录列表:



   
   
$ dothis < tab >< tab >
dir1 / dir2 / dir3 /

在《 Bash参考手册 》中找到可用标志的完整列表。

动态完成

我们将使用以下逻辑生成dothis可执行文件的完成内容:

  • 如果用户在命令后立即按Tab键,我们将在历史记录中显示最近执行的50条命令及其编号
  • 如果用户在键入与历史记录中多个命令匹配的数字后按Tab键,我们将仅显示这些命令及其历史记录中的数字
  • 如果用户在与历史记录中的一个命令完全匹配的数字后按Tab键,我们将自动完成该数字而不附加命令的文字(如果这样会使您感到困惑,请不要担心,您稍后会理解)

让我们首先定义一个函数,该函数将在用户每次使用dothis命令请求完成时执行。 将完成脚本更改为此:



   
   
#/usr/bin/env bash
_dothis_completions ( )
{
  COMPREPLY+= ( "now" )
  COMPREPLY+= ( "tomorrow" )
  COMPREPLY+= ( "never" )
}

complete -F _dothis_completions dothis

请注意以下几点:

  • 我们在complete命令中使用了-F标志,定义_dothis_completions是将提供dothis可执行文件完成功能的函数
  • COMPREPLY是用于存储COMPREPLY全的数组变量- COMPREPLY全机制使用此变量将其内容显示为COMPREPLY

现在获取脚本并完成操作:



   
   
$ dothis < tab >< tab >
never now tomorrow

完善。 我们使用单词列表产生与上一部分相同的补全。 或不? 试试这个:



   
   
$ dothis nev < tab >< tab >
never     now       tomorrow

如您所见,即使我们键入nev然后请求完成,可用选项也始终相同,并且不会自动完成任何操作。 为什么会这样呢?

  • 始终显示COMPREPLY变量的内容。 该功能现在负责从那里添加/删除条目。
  • 如果COMPREPLY变量只有一个元素,则该单词将在命令中自动完成。 由于当前实现始终返回相同的三个单词,因此不会发生这种情况。

输入compgen :一个内置命令,它将生成支持complete命令的大多数选项(例如-W表示单词列表, -d表示目录)的compgen complete ,并根据用户已经输入的内容过滤它们。

如果您感到困惑,请不要担心。 以后一切都会变得清楚。

在控制台中键入以下内容,以更好地了解compgen作用:



   
   
$ compgen -W "now tomorrow never"
now
tomorrow
never
$ compgen -W "now tomorrow never" n
now
never
$ compgen -W "now tomorrow never" t
tomorrow

所以现在我们可以使用它,但是我们需要找到一种方法来知道在dothis命令之后键入的dothis 。 我们已经有了办法:Bash完成工具提供与完成有关的Bash变量 。 这里是更重要的:

  • COMP_WORDScompspec所属程序名称之后键入的所有单词的数组
  • COMP_CWORDCOMP_WORDS数组的索引, COMP_WORDS当前光标所在的单词,换句话说,当按下Tab键时,光标所在的单词的索引
  • COMP_LINE :当前命令行

要访问dothis单词之后的单词,我们可以使用COMP_WORDS[1]的值

再次更改完成脚本:



   
   
#/usr/bin/env bash
_dothis_completions ( )
{
  COMPREPLY = ( $ ( compgen -W "now tomorrow never" " ${COMP_WORDS[1]} " ) )
}

complete -F _dothis_completions dothis

来源,您有:



   
   
 $ dothis
never     now       tomorrow  
$ dothis n
never  now

现在,我们希望从命令历史记录中看到实际数字,而不是现在,明天,从不

fc -l命令后跟负号-n显示最后n个命令。 因此,我们将使用:


fc -l -50 

其中列出了最近执行的50条命令及其编号。 我们需要做的唯一操作是用空格替换选项卡,以便在完成机制中正确显示它们。 sed营救。

更改完成脚本,如下所示:



   
   
#/usr/bin/env bash
_dothis_completions ( )
{
  COMPREPLY = ( $ ( compgen -W " $(fc -l -50 | sed 's/\t//') " -- " ${COMP_WORDS[1]} " ) )
}

complete -F _dothis_completions dothis

在控制台中进行源代码和测试:



   
   
$ dothis < tab >< tab >
632 source dothis-completion.bash   649 source dothis-completion.bash   666 cat ~ / .bash_profile
633 clear                           650 clear                           667 cat ~ / .bashrc
634 source dothis-completion.bash   651 source dothis-completion.bash   668 clear
635 source dothis-completion.bash   652 source dothis-completion.bash   669 install . / dothis ~ / bin / dothis
636 clear                           653 source dothis-completion.bash   670 dothis
637 source dothis-completion.bash   654 clear                           671 dothis 6546545646
638 clear                           655 dothis 654                       672 clear
639 source dothis-completion.bash   656 dothis 631                       673 dothis
640 source dothis-completion.bash   657 dothis 150                       674 dothis 651
641 source dothis-completion.bash   658 dothis                           675 source dothis-completion.bash
642 clear                           659 clear                           676 dothis 651
643 dothis 623   ls -la               660 dothis                           677 dothis 659
644 clear                           661 install . / dothis ~ / bin / dothis   678 clear
645 source dothis-completion.bash   662 dothis                           679 dothis 665
646 clear                           663 install . / dothis ~ / bin / dothis   680 clear
647 source dothis-completion.bash   664 dothis                           681 clear
648 clear                           665 cat ~ / .bashrc

不错。

但是,我们确实有问题。 尝试键入一个在完成列表中看到的数字,然后再次按键。



   
   
$ dothis 623 < tab >
$ dothis 623   ls 623   ls -la
...
$ dothis 623   ls 623   ls 623   ls 623   ls 623   ls -la

之所以发生这种情况,是因为在完成脚本中,我们使用${COMP_WORDS[1]}来始终检查dothis命令之后的第一个键入的单词(上述代码中的数字623 )。 因此,当按Tab键时,完成将继续一次又一次建议相同的完成。

为了解决这个问题,如果已经输入了至少一个参数,我们将不允许进行任何形式的补全。 我们将在函数中添加一个条件,以检查上述COMP_WORDS数组的大小。



   
   
#/usr/bin/env bash
_dothis_completions ( )
{
  if [ " ${#COMP_WORDS[@]} " ! = "2" ] ; then
    return
  fi

  COMPREPLY = ( $ ( compgen -W " $(fc -l -50 | sed 's/\t//') " -- " ${COMP_WORDS[1]} " ) )
}

complete -F _dothis_completions dothis

来源并重试。



   
   
$ dothis 623 < tab >
$ dothis 623 ls -la < tab > # SUCCESS: nothing happens here

但是,还有另一件事我们不喜欢。 我们确实希望显示数字以及相应的命令来帮助用户确定所需的数字,但是当只有一个完成建议并且由完成机制自动选择时, 我们也不应附加命令文字

换句话说,我们的dothis可执行文件仅接受一个数字,并且我们没有添加任何功能来检查或期望其他参数。 当我们的完成函数仅给出一个结果时,我们应该修剪命令文字并仅以命令编号响应。

为此,我们将compgen命令的响应保留在一个数组变量中,如果它的大小为1 ,我们将修剪一个唯一的元素以仅保留数字。 否则,我们将按原样放置数组。

将完成脚本更改为此:



   
   
#/usr/bin/env bash
_dothis_completions ( )
{
  if [ " ${#COMP_WORDS[@]} " ! = "2" ] ; then
    return
  fi

  # keep the suggestions in a local variable
  local suggestions = ( $ ( compgen -W " $(fc -l -50 | sed 's/\t/ /') " -- " ${COMP_WORDS[1]} " ) )

  if [ " ${#suggestions[@]} " == "1" ] ; then
    # if there's only one match, we remove the command literal
    # to proceed with the automatic completion of the number
    local number =$ ( echo ${suggestions[0]/%\ */} )
    COMPREPLY = ( " $number " )
  else
    # more than one suggestions resolved,
    # respond with the suggestions intact
    COMPREPLY = ( " ${suggestions[@]} " )
  fi
}

complete -F _dothis_completions dothis

注册完成脚本

如果要仅在计算机上启用完成功能,您要做的就是在.bashrc文件中添加以下脚本源代码:


source < path-to-your-script >/ dothis-completion.bash 

如果要为所有用户启用完成功能,只需将脚本复制到/etc/bash_completion.d/下,Bash将自动加载该脚本。

微调完成脚本

这里有一些额外的步骤以获得更好的结果:

在新行中显示每个条目

在我正在处理的Bash完成脚本中,我也不得不提出由两部分组成的建议。 我想将第一部分显示为默认颜色,将第二部分显示为灰色,以将其区分为帮助文本。 在本教程的示例中,最好以默认颜色显示数字,而以不太花哨的数字显示命令文字。

不幸的是,这至少在目前是不可能的,因为\e[34mBlue显示为纯文本,并且不处理颜色指令(例如: \e[34mBlue )。

我们可以(或不可以)改善用户体验的方法是在新行中显示每个条目。 该解决方案并不是那么明显,因为我们不能仅在每个COMPREPLY条目中添加COMPREPLY 。 我们将遵循一种颇为骇人听闻的方法,并将建议文字填充到填充终端的宽度。

输入printf 。 如果要在每行上显示每个建议,请将完成脚本更改为以下内容:



   
   
#/usr/bin/env bash
_dothis_completions ( )
{
  if [ " ${#COMP_WORDS[@]} " ! = "2" ] ; then
    return
  fi

  local IFS =$ '\n'
  local suggestions = ( $ ( compgen -W " $(fc -l -50 | sed 's/\t//') " -- " ${COMP_WORDS[1]} " ) )

  if [ " ${#suggestions[@]} " == "1" ] ; then
    local number = " ${suggestions[0]/%\ */} "
    COMPREPLY = ( " $number " )
  else
    for i in " ${!suggestions[@]} " ; do
      suggestions [ $i ] = " $(printf '%*s' "-$COLUMNS"  "${suggestions[$i]}") "
    done

    COMPREPLY = ( " ${suggestions[@]} " )
  fi
}

complete -F _dothis_completions dothis

来源和测试:



   
   
dothis < tab >< tab >
...
499 source dothis-completion.bash                  
500 clear
...      
503 dothis 500
可自定义的行为

在我们的案例中,我们进行了硬编码以显示最后50条要完成的命令。 这不是一个好习惯。 我们应该首先尊重每个用户可能喜欢的东西。 如果他/她没有任何偏好,我们应该默认为50。

为此,我们将检查是否已设置环境变量DOTHIS_COMPLETION_COMMANDS_NUMBER

最后一次更改完成脚本:



   
   
#/usr/bin/env bash
_dothis_completions ( )
{
  if [ " ${#COMP_WORDS[@]} " ! = "2" ] ; then
    return
  fi

  local commands_number = ${DOTHIS_COMPLETION_COMMANDS_NUMBER:-50}
  local IFS =$ '\n'
  local suggestions = ( $ ( compgen -W " $(fc -l -$commands_number | sed 's/\t//') " -- " ${COMP_WORDS[1]} " ) )

  if [ " ${#suggestions[@]} " == "1" ] ; then
    local number = " ${suggestions[0]/%\ */} "
    COMPREPLY = ( " $number " )
  else
    for i in " ${!suggestions[@]} " ; do
      suggestions [ $i ] = " $(printf '%*s' "-$COLUMNS"  "${suggestions[$i]}") "
    done

    COMPREPLY = ( " ${suggestions[@]} " )
  fi
}

complete -F _dothis_completions dothis

来源和测试:



   
   
export DOTHIS_COMPLETION_COMMANDS_NUMBER = 5
$ dothis < tab >< tab >
505 clear
506 source . / dothis-completion.bash
507 dothis clear
508 clear
509 export DOTHIS_COMPLETION_COMMANDS_NUMBER = 5

有用的链接

代码和注释

您可以在GitHub上找到本教程的代码。

有关反馈,评论,错别字等,请在存储库中打开一个问题

长发,猫照片

让我向您介绍我的调试器:

cat debugger

就是这样,伙计们!

该帖子最初发布在Iridakos.com上 经许可重新发布。

翻译自: https://opensource.com/article/18/3/creating-bash-completion-script

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值