我最近致力于为一个项目创建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 -
您可以在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
命令中指定的内容:
- 我们使用了
-W
( wordlist )选项来提供要完成的单词列表。 - 我们定义了将这些完成词用于哪个“程序”(
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_WORDS
:compspec
所属程序名称之后键入的所有单词的数组 -
COMP_CWORD
:COMP_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上找到本教程的代码。
有关反馈,评论,错别字等,请在存储库中打开一个问题 。
长发,猫照片
让我向您介绍我的调试器:
就是这样,伙计们!
该帖子最初发布在Iridakos.com上 。 经许可重新发布。
翻译自: https://opensource.com/article/18/3/creating-bash-completion-script