diff --git a/shell/bash-completion/README.md b/shell/bash-completion/README.md new file mode 100644 index 0000000000000000000000000000000000000000..643af9509236856c685126501468267d2782d74d --- /dev/null +++ b/shell/bash-completion/README.md @@ -0,0 +1,11 @@ +# bash 补全脚本 (隐藏的 bullshit) + +> 在编写补全脚本时,一个个隐藏的细节让你大呼 ooooh shit! + +- 详细说明了两个 compgen 的 bullshit + +- 详细说明了IFS 与 '补全包含空格的结果' 行为下的 bullshit + +参考灵感: + +- 某命令的补全脚本(重构中...):https://gitee.com/zinface/bash-completion.ywloader diff --git a/shell/bash-completion/bash-completion-comgen-and-IFS.sh b/shell/bash-completion/bash-completion-comgen-and-IFS.sh new file mode 100644 index 0000000000000000000000000000000000000000..4ff8b1fc7b94fe475c46c6a4966ed154f538bf53 --- /dev/null +++ b/shell/bash-completion/bash-completion-comgen-and-IFS.sh @@ -0,0 +1,180 @@ +#!/bin/bash + +# 首先看一下 compgen 的用法 +# compgen: + # 用法:compgen [-abcdefgjksuv] [-o 选项] [-A 动作] [-G 全局模式] [-W 词语列表] [-F 函数] [-C 命令] [-X 过滤模式] [-P 前缀] [-S 后缀] [词语] + + +# 在补全脚本中, COMPREPLY 是一个包含 compgen 结果的变量,这也是最终提供给到命令补全的结果 + + # 例如:我们使用 cat /etc/passwd | awk -F ":" '{print $1}' 来输出一个用户列表 + + # 它会输出一些预期结果,这是用户列表,用 USERS 变量装起来 + # root + # daemon + # sys + # sshd + # lightdm + # sync + + # 注意这些内容是没有 ' ' 空格分隔的,基本上就是连续词 + + USERS=`cat /etc/passwd | awk -F ":" '{print $1}'` + + COMPREPLY=( + $(compgen -W "${USERS}" -- s) + ) + + # 它会输出正常的预期的内容 + # sys + # sshd + # sync + + +# 为了准备接下来的 bullshit,先概述一下关于 USERS[@] 与 USERS[*] 的内容,其中包含 IFS. + + # 这里使用 COMPREPLY 中的结果,它是一个数组,有刚刚的 3 个元素 + + ehco "${COMPREPLY}" + # sys + + # 它会输出一个 sys,不达预期,因为是一个数组,只使用 ${array} 则只会输出第一个值 + + echo "${COMPREPLY[@]}" + # sys sshd sync + + echo "${COMPREPLY[*]}" + # sys sshd sync + + # 从 ${array[@]} 与 ${array[*]} 的输出中,感觉不到明显的差别,但这是一个区别非常大的东西 + + + IFS=',' + + # 当 IFS 被设置为 ',' 逗号时,两个输出的区别则会明显 + + echo "${COMPREPLY[@]}" + # sys sshd sync + + echo "${COMPREPLY[*]}" + # sys,sshd,sync + + # 注意,这里始终使用的是 " " 进行包围变量结果的形式 + +unset IFS + + + + +# 这里开始 bullshit 的准备,假设我有一个需要参与补全的结果,它是有空格的 + + cat ./result.txt + # RFC 1928 - SOCKS 5 协议中文文档「译」 + # github release 加速下载 + # M-Team - 一个 BT 论坛 + # fetch-latest-vscode-amd64-deb + # https://mirror.ghproxy.com/ + # frp 相关内容 + # 小白安装 Gentoo 简述(不错的文章) + # Ipv6 连接测试网页 + # connect-Mi10 + # Dockerfile 文件详解 + # Jenkins 下载页面 + + + # 这里,我以变量形式装起来 + + SUPPLY=`cat ./result.txt` + + # 如果使用 compgen -W "${SUPPLY}" ... 来直接尝试补全 fetch-latest-vscode-amd64-deb + + compgen -W "${SUPPLY}" fetch + # fetch-latest-vscode-amd64-deb + + # 它的输出结果是正确的 + + compgen -W "${SUPPLY}" connect + # connect-Mi10 + + # 它的输出结果是正确的 + + + # bullshit 开始 + compgen -W "${SUPPLY}" github + # github + + # 它的输出结果是错误的,疑似是结果中的空格成为了分隔符 + + compgen -W "${SUPPLY}" I + # Ipv6 + + # 它的输出结果仍是错误的 + + compgen -W "${SUPPLY}" RFC + # RFC + + # 它的输出结果仍是错误的 + + # 这里的输出的结果,不达预期,连续部分输出的结果是正常的,包含空格的部分则是有问题的 + + + IFS=$'\n' + # 注意这里的 $'\n',意思是让 '\n' 作为一个 IFS 的单字符值。 + # 如果没有 $ 符号则 IFS='\n' 表示 '\n' 是一个字符串,也等效于 IFS="\n",字符串值无法在后续结果中产生效果 + + # 再来测试补全结果 + compgen -W "${SUPPLY}" I + # Ipv6 连接测试网页 + + compgen -W "${SUPPLY}" RFC + # RFC 1928 - SOCKS 5 协议中文文档「译」 + + compgen -W "${SUPPLY}" fetch + # fetch-latest-vscode-amd64-deb + + compgen -W "${SUPPLY}" 小白 + # 小白安装 Gentoo 简述(不错的文章) + + # 经过 IFS 设置后,这里的补全结果是正常的 + + # 如果只是 compgen,这样就已经是 bullshit 预期的解决方案 + + # 如果要涉及到实际的命令行补全结果,例如 + + COMPREPLY=( + $(compgen -W "${SUPPLY}" RFC) + ) + + # <命令> RFC 1928 - SOCKS 5 协议中文文档「译」 + # ^ --------- 补全内容 ---------- ^ + # ^ 次级命令 + # ^ 主命令 + + # 这种预期结果是不可行的,次级命令接收的参数并非是一个整体的字符串参数,所以需要对 COMPREPLY 的内容进行处理 + + for (( i=0; i<${#COMPREPLY[@]}; i++ )); do + COMPREPLY[$i]="'${COMPREPLY[i]}'" + # 给每一个结果都加入 '' 单引号包围 + done + + # <命令> 'RFC 1928 - SOCKS 5 协议中文文档「译」' + # ^ ^ --------- 补全内容 ----------- ^ + # ^ 次级命令 + # ^ 主命令 + + # 这样,预期的结果次级命令接收的参数是一个整体的字符串参数,可达预期 + + unset IFS + + +# bullshit 关键点: + # 补全脚本如果涉及多行、包含空格的命令输出式结果形式的内容,一般形式写法可能会产生非预期行为 + + # 来自 man bash 的解释,但它却没有细说 + + # IFS 内部字段分隔符 Internal Field Separator 用来在扩展之后进行分词,使用内部命令 read 将行划分成词。默认值是 + # ``''。 + + + +# IFS 可能只适用于 linux bash shell 内部使用,而 bash 补全脚本正是运行在 bash 内部,提供命令强大的补全能力 \ No newline at end of file diff --git a/shell/bash-completion/bash-completion-comgen.sh b/shell/bash-completion/bash-completion-comgen.sh new file mode 100644 index 0000000000000000000000000000000000000000..e11df43e4845fd897a8184ed321ff65089428425 --- /dev/null +++ b/shell/bash-completion/bash-completion-comgen.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# 首先看一下 compgen 的用法 +# compgen: + # 用法:compgen [-abcdefgjksuv] [-o 选项] [-A 动作] [-G 全局模式] [-W 词语列表] [-F 函数] [-C 命令] [-X 过滤模式] [-P 前缀] [-S 后缀] [词语] + + +# 当我们第一次使用 compgen -W 通过词语列表来输出简单可能的补全词时,它没有任何问题 + + compgen -W "java javac rust python go gcc" g + + # 它会输出两个可能的补全: + # gcc + # go + +# 如果你想为 java 添加一些参数补全,例如 -jar -classpath .. 等,并提供 -j 为可能的补全参考(目的是直接补全 -jar) + + compgen -W "-jar -classpath --help -version" -j + + # 它会输出非预期的内容 - bullshit + # -jar + # -classpath + # --help + # -version + + # 正确的写法是,通过 -- 来隔断,这样才能输出预期内容 + + compgen -W "-jar -classpath --help -version" -- -j + + # -jar + + + +# bullshit 关键点: + # 补全脚本如果涉及参数形式的内容,一般形式写法可能会产生非预期行为 + + # 来自 man bash 的解释,但它却没有与 compgen 写在一块 + + # -- -- 标志选项的结束,禁止其余的选项处理。任何 -- 之后的参数将作为文件名和参数对待。参数 - 与此等价。 \ No newline at end of file diff --git a/shell/bash-completion/result.txt b/shell/bash-completion/result.txt new file mode 100644 index 0000000000000000000000000000000000000000..df39112035d7f719ff0f70d6d09fcbed1e50c8b0 --- /dev/null +++ b/shell/bash-completion/result.txt @@ -0,0 +1,11 @@ +RFC 1928 - SOCKS 5 协议中文文档「译」 +github release 加速下载 +M-Team - 一个 BT 论坛 +fetch-latest-vscode-amd64-deb +https://mirror.ghproxy.com/ +frp 相关内容 +小白安装 Gentoo 简述(不错的文章) +Ipv6 连接测试网页 +connect-Mi10 +Dockerfile 文件详解 +Jenkins 下载页面