diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ec2ba40..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "lib/bash-completion"] - path = lib/bash-completion - url = https://github.com/scop/bash-completion diff --git a/lib/bash-completion b/lib/bash-completion deleted file mode 160000 index 8a8880d..0000000 --- a/lib/bash-completion +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8a8880db78e9b04fb3d66681028af701701f93f2 diff --git a/lib/bash-completion/COPYING b/lib/bash-completion/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/lib/bash-completion/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/lib/bash-completion/README.md b/lib/bash-completion/README.md new file mode 100644 index 0000000..256df62 --- /dev/null +++ b/lib/bash-completion/README.md @@ -0,0 +1,356 @@ +# bash-completion + +[![CI](https://github.com/scop/bash-completion/actions/workflows/ci.yaml/badge.svg)](https://github.com/scop/bash-completion/actions/workflows/ci.yaml) + +## Introduction + +bash-completion is a collection of command line command completions for the +[Bash shell](https://www.gnu.org/software/bash/), collection of helper +functions to assist in creating new completions, and set of facilities for +loading completions automatically on demand, as well as installing them. + +## Installation + +The easiest way to install this software is to use a package; refer to +[Repology](https://repology.org/project/bash-completion) for a comprehensive +list of operating system distributions, package names, and available versions. + +Depending on the package, you may still +need to source it from either `/etc/bashrc` or `~/.bashrc` (or any +other file sourcing those). If you have _only_ bash >= 4.2 installed, you can +do this by simply using: + +```shell +# Use bash-completion, if available +[[ $PS1 && -f /usr/share/bash-completion/bash_completion ]] && \ + . /usr/share/bash-completion/bash_completion +``` + +If you have older bash versions in use, their loading of `bash_completion` +should be prevented. See further for more info. + +If you don't have the package readily available for your distribution, or +you simply don't want to use one, you can install bash completion using the +standard commands for GNU autotools packages: + +```shell +autoreconf -i # if not installing from prepared release tarball +./configure +make # GNU make required +make check # optional +make install # as root +make installcheck # optional, requires python3 with pytest >= 3.6, pexpect +``` + +These commands install the completions and helpers, as well as a +`profile.d` script that loads `bash_completion` where appropriate. + +If your system does not use the `profile.d` directory (usually below +`/etc`) mechanism (i.e., does not automatically source shell scripts in +it), you can source the `$sysconfdir/profile.d/bash_completion.sh` +script in `/etc/bashrc` or `~/.bashrc`. + +The `profile.d` script provides a configuration file hook that can be +used to prevent loading `bash_completion` on per user basis when it's +installed system wide. To do this: + +1. Turn off programmable completion with `shopt -u progcomp` in + `$XDG_CONFIG_HOME/bash_completion` (or `~/.config/bash_completion` + if `$XDG_CONFIG_HOME` is not set) +2. Turn it back on (for example in `~/.bashrc`) if you want to use + programmable completion for other purposes. + +### macOS (OS X) + +If you're using macOS (formerly OS X), `/etc/bashrc` is apparently not sourced at +all. In that case, you can put the `bash_completion` file in `/sw/etc` +and add the following code to `~/.bash_profile`: + +```shell +if [ -f /sw/etc/bash_completion ]; then + . /sw/etc/bash_completion +fi +``` + +## Troubleshooting + +If you find that a given function is producing errors or does not work +as it should under certain circumstances when you attempt completion, +try running `set -x` or `set -v` prior to attempting the completion +again. This will produce useful debugging output that will aid us in +fixing the problem if you are unable to do so yourself. Turn off the +trace output by running either `set +x` or `set +v`. + +If you are filing an issue, please attach the generated debugging output +in `set -x` mode copy-pasted to a separate, attached file in the report. +Before doing so, be sure to review the output for anything you may not want +to share in public, and redact as appropriate. + +To debug dynamic loading of a completion, tracing needs to be turned +on before the debugged completion is attempted the first time. The +easiest way to do this is to start a new shell session, and to turn +tracing on in it before doing anything else there. + +## Known problems + +1. Many of the completion functions assume GNU versions of the various + text utilities that they call (e.g. `grep`, `sed`, and `awk`). Your + mileage may vary. + +## FAQ + +**Q. The bash completion code inhibits some commands from completing on + files with extensions that are legitimate in my environment. Do I + have to disable completion for that command in order to complete on + the files that I need to?** + +A. No. If needed just once in a while, + use `M-/` to (in the words of the bash man page) attempt file + name completion on the text to the left of the cursor. This will + circumvent any file type restrictions put in place by the bash + completion code. If needed more regularly, see the next question: + +**Q. How can I override a completion shipped by bash-completion?** + +A. Install a local completion of your own appropriately for the desired + command, and it will take precedence over the one shipped by us. See the + next answer for details where to install it, if you are doing it on per user + basis. If you want to do it system wide, you can install eagerly loaded + files in `compatdir` (see a couple of questions further down for more + info. To get the path of `compatdir` for the current system, the output of + `pkg-config bash-completion --variable compatdir` can be used) and install a + completion for the commands to override our completion for in them. + + If you want to use bash's default completion instead of one of ours, + something like this should work (where `$cmd` is the command to override + completion for): `complete -o default -o bashdefault $cmd` + +**Q. Where should I install my own local completions?** + +A. Put them in the `completions` subdir of `$BASH_COMPLETION_USER_DIR` + (defaults to `$XDG_DATA_HOME/bash-completion` or + `~/.local/share/bash-completion` + if `$XDG_DATA_HOME` is not set) to have them loaded automatically + on demand when the respective command is being completed. + See also the next question's answer for considerations for these + files' names, they apply here as well. Alternatively, you can write + them directly in `~/.bash_completion` which is loaded eagerly by + our main script. + +**Q. I author/maintain package X and would like to maintain my own + completion code for this package. Where should I put it to be sure + that interactive bash shells will find it and source it?** + +A. [ Disclaimer: Here, how to make the completion code visible to + bash-completion is explained. We do not require always making the + completion code visible to bash-completion. In what condition the + completion code is installed should be determined at the author/maintainers' + own discretion. ] + + Install it in one of the directories pointed to by bash-completion's + `pkgconfig` file variables. There are two alternatives: + + - The recommended directory is `completionsdir`, which you can get with + `pkg-config --variable=completionsdir bash-completion`. From this + directory, completions are automatically loaded on demand based on invoked + commands' names, so be sure to name your completion file accordingly, and + to include (for example) symbolic links in case the file provides + completions for more than one command. The completion filename for + command `foo` in this directory should be either `foo`, or `foo.bash`. + (Underscore prefixed `_foo` works too, but is reserved for + bash-completion internal use as a deprecation/fallback marker.) + - The other directory which is only present for backwards compatibility, + its usage is no longer recommended, is `compatdir` (get it with + `pkg-config --variable=compatdir bash-completion`). From this + directory, files are loaded eagerly when `bash_completion` is loaded. + + For packages using GNU autotools the installation can be handled + for example like this in `configure.ac`: + + ```m4 + PKG_CHECK_VAR(bashcompdir, [bash-completion], [completionsdir], , + bashcompdir="${sysconfdir}/bash_completion.d") + AC_SUBST(bashcompdir) + ``` + + ...accompanied by this in `Makefile.am`: + + ```makefile + bashcompdir = @bashcompdir@ + dist_bashcomp_DATA = your-completion-file # completion files go here + ``` + + For cmake we ship the `bash-completion-config.cmake` and + `bash-completion-config-version.cmake` files. Example usage: + + ```cmake + find_package(bash-completion) + if(BASH_COMPLETION_FOUND) + message(STATUS + "Using bash completion dir ${BASH_COMPLETION_COMPLETIONSDIR}") + else() + set (BASH_COMPLETION_COMPLETIONSDIR "/etc/bash_completion.d") + message (STATUS + "Using fallback bash completion dir ${BASH_COMPLETION_COMPLETIONSDIR}") + endif() + + install(FILES your-completion-file DESTINATION + ${BASH_COMPLETION_COMPLETIONSDIR}) + ``` + + In bash-completion >= 2.12, we search the data directory of + `bash-completion` under the installation prefix where the target command is + installed. When one can assume that the version of the target + bash-completion is 2.12 or higher, the completion script can actually be + installed to `$PREFIX/share/bash-completion/completions/` under the same + installation prefix as the target program installed under `$PREFIX/bin/` or + `$PREFIX/sbin/`. For the detailed search order, see also "Q. What is the + search order for the completion file of each target command?" below. + + Example for `Makefile.am`: + + ```makefile + bashcompdir = $(datarootdir)/bash-completion/completions + dist_bashcomp_DATA = your-completion-file + ``` + + Example for `CMakeLists.txt`: + + ```cmake + install(FILES your-completion-file DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/bash-completion/completions") + ``` + +**Q. When completing on a symlink to a directory, bash does not append + the trailing `/` and I have to hit <Tab> again. + I don't like this.** + +A. This has nothing to do with `bash_completion`. It's the default for + completing symlinks to directories since bash 2.05a, and was added + because sometimes you want to operate on the symlink itself, rather + than what it points to. + + You can get the pre-2.05a behaviour back by putting `set + mark-symlinked-directories on` in your `/etc/inputrc` or + `~/.inputrc` file. + +**Q. Completion goes awry when I try to complete on something that contains + a colon.** + +A. This is actually a 'feature' of bash. bash recognises a colon as + starting a new completion token, which is often what you want when + completing something like a `PATH` variable: + + ```shell + export PATH=/bin:/sbin:/usr + ``` + + Without the special treatment of the colon, the above wouldn't work + without programmable completion, so it has long been a feature of + the shell. + + Unfortunately, you don't want the colon to be treated as a special + case when doing something like: + + ```shell + man File::B + ``` + + Here, the colons make bash think that it's completing a new token + that begins with 'B'. + + Unfortunately, there's no way to turn this off. The only thing you + can do is escape the colons with a backslash. + +**Q. Why is `rpm` completion so slow with `-q`?** + +A. Probably because the database is being queried every time and this uses a + lot of memory. + + You can make this faster by pregenerating the list of installed + packages on the system. Make sure you have a readable file called + `/var/log/rpmpkgs`. It's generated by `/etc/cron.daily/rpm` on + some Red Hat and Mandrake and derivative Linux systems. + + If you don't have such a cron job, make one: + + ```shell + #!/bin/sh + + rpm -qa --qf '%{name}-%{version}-%{release}.%{arch}.rpm\n' 2>&1 \ + | sort >/var/log/rpmpkgs + ``` + + rpm completion will use this flat text file instead of the RPM database, + unless it detects that the database has changed since the file was created, + in which case it will still use the database to ensure accuracy. + +**Q. bash-completion interferes with my `command_not_found_handle` function + (or the other way around)!** + +A. If your `command_not_found_handle` function is not intended to + address (possibly missing) commands invoked during bash + programmable completion functions, you can account for this + in the function by, for example, testing if the `$COMP_LINE` + variable is set and taking appropriate action, typically returning + early and silently with success. + +**Q. Can tab completion be made even easier?** + +A. The `readline(3)` library offers a few settings that can make tab + completion easier (or at least different) to use. + + For example, try putting the following in either `/etc/inputrc` or + `~/.inputrc`: + + ```inputrc + set show-all-if-ambiguous on + ``` + + This will allow single tab completion as opposed to requiring a + double tab. This makes things much more pleasant, in our opinion. + + ```inputrc + set visible-stats on + ``` + + This will suffix each returned file completion with a character + denoting its type, in a similar way to `ls(1)` with `-F` or `--classify`. + + ```inputrc + set page-completions off + ``` + + This turns off the use of the internal pager when returning long + completion lists. + +**Q. Is bash the be-all-and-end-all of completion as far as shells go?** + +A. Absolutely not. zsh has an extremely sophisticated completion system + that offers many features absent from the bash implementation. Its + users often cannot resist pointing this out. More information can + be found at . + +**Q. What is the search order for the completion file of each target command?** + +A. The completion files of commands are looked up by the shell function + `__load_completion`. Here, the search order in bash-completion >= 2.12 is + explained. + + 1. `BASH_COMPLETION_USER_DIR`. The subdirectory `completions` of each paths + in `BASH_COMPLETION_USER_DIR` separated by colons is considered for a + completion directory. + 2. The location of the main `bash_completion` file. The subdirectory + `completions` in the same directory as `bash_completion` is considered. + 3. The location of the target command. When the real location of the command + is in the directory `/bin` or `/sbin`, the directory + `/share/bash-completion/completions` is considered. + 4. `XDG_DATA_DIRS` (or the system directories `/usr/local/share:/usr/share` + if empty). The subdirectory `bash-completion/completions` of each paths + in `XDG_DATA_DIRS` separated by colons is considered. + + The completion files of the name `` or `.bash`, where `` is + the name of the target command, are searched in the above completion + directories in order. The file that is found first is used. When no + completion file is found in any completion directories in this process, the + completion files of the name `_` is next searched in the completion + directories in order. diff --git a/lib/bash-completion/bash_completion b/lib/bash-completion/bash_completion new file mode 100644 index 0000000..a6b707e --- /dev/null +++ b/lib/bash-completion/bash_completion @@ -0,0 +1,3474 @@ +# -*- shell-script -*- +# +# bash_completion - programmable completion functions for bash 4.2+ +# +# Copyright © 2006-2008, Ian Macdonald +# © 2009-2020, Bash Completion Maintainers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# The latest version of this software can be obtained here: +# +# https://github.com/scop/bash-completion + +BASH_COMPLETION_VERSINFO=( + 2 # x-release-please-major + 15 # x-release-please-minor + 0 # x-release-please-patch +) + +if [[ $- == *v* ]]; then + _comp__init_original_set_v="-v" +else + _comp__init_original_set_v="+v" +fi + +if [[ ${BASH_COMPLETION_DEBUG-} ]]; then + set -v +else + set +v +fi + +# Turn on extended globbing and programmable completion +shopt -s extglob progcomp + +# Declare a compatibility function name +# @param $1 Version of bash-completion where the deprecation occurred +# @param $2 Old function name +# @param $3 New function name +# @since 2.12 +_comp_deprecate_func() +{ + if (($# != 3)); then + printf 'bash_completion: %s: usage: %s DEPRECATION_VERSION OLD_NAME NEW_NAME\n' "$FUNCNAME" "$FUNCNAME" + return 2 + fi + if [[ $2 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "\$2: invalid function name '$1'" >&2 + return 2 + elif [[ $3 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "\$3: invalid function name '$2'" >&2 + return 2 + fi + eval -- "$2() { $3 \"\$@\"; }" +} + +# Declare a compatibility variable name. +# For bash 4.3+, a real name alias is created, allowing value changes to +# "apply through" when the variables are set later. For bash versions earlier +# than that, the operation is once-only; the value of the new variable +# (if it's unset) is set to that of the old (if set) at call time. +# +# @param $1 Version of bash-completion where the deprecation occurred +# @param $2 Old variable name +# @param $3 New variable name +# @since 2.12 +_comp_deprecate_var() +{ + if (($# != 3)); then + printf 'bash_completion: %s: usage: %s DEPRECATION_VERSION OLD_NAME NEW_NAME\n' "$FUNCNAME" "$FUNCNAME" + return 2 + fi + if [[ $2 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "\$2: invalid variable name '$1'" >&2 + return 2 + elif [[ $3 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "\$3: invalid variable name '$2'" >&2 + return 2 + fi + if ((BASH_VERSINFO[0] >= 5 || BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 3)); then + eval "declare -gn $2=$3" + elif [[ -v $2 && ! -v $3 ]]; then + printf -v "$3" %s "$2" + fi +} + +# A lot of the following one-liners were taken directly from the +# completion examples provided with the bash 2.04 source distribution + +# start of section containing compspecs that can be handled within bash + +# user commands see only users +complete -u groups slay w sux + +# bg completes with stopped jobs +complete -A stopped -P '"%' -S '"' bg + +# other job commands +complete -j -P '"%' -S '"' fg jobs disown + +# readonly and unset complete with shell variables +complete -v readonly unset + +# shopt completes with shopt options +complete -A shopt shopt + +# unalias completes with aliases +complete -a unalias + +# type and which complete on commands +complete -c command type which + +# builtin completes on builtins +complete -b builtin + +# start of section containing completion functions called by other functions + +# Check if we're running on the given userland +# @param $1 userland to check for +# @since 2.12 +_comp_userland() +{ + local userland=$(uname -s) + [[ $userland == @(Linux|GNU/*) ]] && userland=GNU + [[ $userland == "$1" ]] +} + +# This function sets correct SysV init directories +# +# @since 2.12 +_comp_sysvdirs() +{ + sysvdirs=() + [[ -d /etc/rc.d/init.d ]] && sysvdirs+=(/etc/rc.d/init.d) + [[ -d /etc/init.d ]] && sysvdirs+=(/etc/init.d) + # Slackware uses /etc/rc.d + [[ -f /etc/slackware-version ]] && sysvdirs=(/etc/rc.d) + ((${#sysvdirs[@]})) +} + +# This function checks whether we have a given program on the system. +# +# @since 2.12 +_comp_have_command() +{ + # Completions for system administrator commands are installed as well in + # case completion is attempted via `sudo command ...'. + PATH=$PATH:/usr/sbin:/sbin:/usr/local/sbin type "$1" &>/dev/null +} + +# This function checks whether a given readline variable +# is `on'. +# +# @since 2.12 +_comp_readline_variable_on() +{ + [[ $(bind -v) == *$1+([[:space:]])on* ]] +} + +# This function shell-quotes the argument +# @param $1 String to be quoted +# @var[out] REPLY Resulting string +# @since 2.12 +_comp_quote() +{ + REPLY=\'${1//\'/\'\\\'\'}\' +} + +# shellcheck disable=SC1003 +_comp_dequote__initialize() +{ + local regex_param='\$([_a-zA-Z][_a-zA-Z0-9]*|[-*@#?$!0-9_])|\$\{[!#]?([_a-zA-Z][_a-zA-Z0-9]*(\[([0-9]+|[*@])\])?|[-*@#?$!0-9_])\}' + local regex_quoted='\\.|'\''[^'\'']*'\''|\$?"([^\"$`!]|'$regex_param'|\\.)*"|\$'\''([^\'\'']|\\.)*'\''' + _comp_dequote__regex_safe_word='^([^\'\''"$`;&|<>()!]|'$regex_quoted'|'$regex_param')*$' + unset -f "$FUNCNAME" +} +_comp_dequote__initialize + +# This function expands a word using `eval` in a safe way. This function can +# be typically used to get the expanded value of `${word[i]}` as +# `_comp_dequote "${word[i]}"`. When the word contains unquoted shell special +# characters, command substitutions, and other unsafe strings, the function +# call fails before applying `eval`. Otherwise, `eval` is applied to the +# string to generate the result. +# +# @param $1 String to be expanded. A safe word consists of the following +# sequence of substrings: +# +# - Shell non-special characters: [^\'"$`;&|<>()!]. +# - Parameter expansions of the forms $PARAM, ${!PARAM}, +# ${#PARAM}, ${NAME[INDEX]}, ${!NAME[INDEX]}, ${#NAME[INDEX]} +# where INDEX is an integer, `*` or `@`, NAME is a valid +# variable name [_a-zA-Z][_a-zA-Z0-9]*, and PARAM is NAME or a +# parameter [-*@#?$!0-9_]. +# - Quotes \?, '...', "...", $'...', and $"...". In the double +# quotations, parameter expansions are allowed. +# +# @var[out] REPLY Array that contains the expanded results. Multiple words or +# no words may be generated through pathname expansions. +# +# Note: This function allows parameter expansions as safe strings, which might +# cause unexpected results: +# +# * This allows execution of arbitrary commands through extra expansions of +# array subscripts in name references. For example, +# +# declare -n v='dummy[$(echo xxx >/dev/tty)]' +# echo "$v" # This line executes the command 'echo xxx'. +# _comp_dequote '"$v"' # This line also executes it. +# +# * This may change the internal state of the variable that has side effects. +# For example, the state of the random number generator of RANDOM can change: +# +# RANDOM=1234 # Set seed +# echo "$RANDOM" # This produces 30658. +# RANDOM=1234 # Reset seed +# _comp_dequote '"$RANDOM"' # This line changes the internal state. +# echo "$RANDOM" # This fails to reproduce 30658. +# +# We allow these parameter expansions as a part of safe strings assuming the +# referential transparency of the simple parameter expansions and the sane +# setup of the variables by the user or other frameworks that the user loads. +# @since 2.12 +_comp_dequote() +{ + REPLY=() # fallback value for unsafe word and failglob + [[ $1 =~ $_comp_dequote__regex_safe_word ]] || return 1 + eval "REPLY=($1)" 2>/dev/null # may produce failglob +} + +# Unset the given variables across a scope boundary. Useful for unshadowing +# global scoped variables. Note that simply calling unset on a local variable +# will not unshadow the global variable. Rather, the result will be a local +# variable in an unset state. +# Usage: local IFS='|'; _comp_unlocal IFS +# @param $* Variable names to be unset +# @since 2.12 +_comp_unlocal() +{ + if ((BASH_VERSINFO[0] >= 5)) && shopt -q localvar_unset; then + shopt -u localvar_unset + unset -v "$@" + shopt -s localvar_unset + else + unset -v "$@" + fi +} + +# Assign variables one scope above the caller +# Usage: local varname [varname ...] && +# _comp_upvars [-v varname value] | [-aN varname [value ...]] ... +# Available OPTIONS: +# -aN Assign next N values to varname as array +# -v Assign single value to varname +# @return 1 if error occurs +# @see https://fvue.nl/wiki/Bash:_Passing_variables_by_reference +# @since 2.12 +_comp_upvars() +{ + if ! (($#)); then + echo "bash_completion: $FUNCNAME: usage: $FUNCNAME" \ + "[-v varname value] | [-aN varname [value ...]] ..." >&2 + return 2 + fi + while (($#)); do + case $1 in + -a*) + # Error checking + [[ ${1#-a} ]] || { + echo "bash_completion: $FUNCNAME:" \ + "\`$1': missing number specifier" >&2 + return 1 + } + printf %d "${1#-a}" &>/dev/null || { + echo bash_completion: \ + "$FUNCNAME: \`$1': invalid number specifier" >&2 + return 1 + } + # Assign array of -aN elements + # shellcheck disable=SC2015,SC2140 # TODO + [[ $2 ]] && unset -v "$2" && eval "$2"=\(\"\$"{@:3:${1#-a}}"\"\) && + shift $((${1#-a} + 2)) || { + echo bash_completion: \ + "$FUNCNAME: \`$1${2+ }$2': missing argument(s)" \ + >&2 + return 1 + } + ;; + -v) + # Assign single value + # shellcheck disable=SC2015 # TODO + [[ $2 ]] && unset -v "$2" && eval "$2"=\"\$3\" && + shift 3 || { + echo "bash_completion: $FUNCNAME: $1:" \ + "missing argument(s)" >&2 + return 1 + } + ;; + *) + echo "bash_completion: $FUNCNAME: $1: invalid option" >&2 + return 1 + ;; + esac + done +} + +# Get the list of filenames that match with the specified glob pattern. +# This function does the globbing in a controlled environment, avoiding +# interference from user's shell options/settings or environment variables. +# @param $1 array_name Array name +# The array name should not start with an underscore "_", which is internally +# used. The array name should not be "GLOBIGNORE" or "GLOBSORT". +# @param $2 pattern Pattern string to be evaluated. +# This pattern string will be evaluated using "eval", so brace expansions, +# parameter expansions, command substitutions, and other expansions will be +# processed. The user-provided strings should not be directly specified to +# this argument. +# @return 0 if at least one path is generated, 1 if no path is generated, or 2 +# if the usage is incorrect. +# @since 2.12 +_comp_expand_glob() +{ + if (($# != 2)); then + printf 'bash-completion: %s: unexpected number of arguments\n' "$FUNCNAME" >&2 + printf 'usage: %s ARRAY_NAME PATTERN\n' "$FUNCNAME" >&2 + return 2 + elif [[ $1 == @(GLOBIGNORE|GLOBSORT|_*|*[^_a-zA-Z0-9]*|[0-9]*|'') ]]; then + printf 'bash-completion: %s: invalid array name "%s"\n' "$FUNCNAME" "$1" >&2 + return 2 + fi + + # Save and adjust the settings. + local _original_opts=$SHELLOPTS:$BASHOPTS + set +o noglob + shopt -s nullglob + shopt -u failglob dotglob + + # Also the user's GLOBIGNORE and GLOBSORT (bash >= 5.3) may affect the + # result of pathname expansions. + local GLOBIGNORE="" GLOBSORT=name + + # To canonicalize the sorting order of the generated paths, we set + # LC_COLLATE=C and unset LC_ALL while preserving LC_CTYPE. + local LC_COLLATE=C LC_CTYPE=${LC_ALL:-${LC_CTYPE:-${LANG-}}} LC_ALL= + + eval -- "$1=()" # a fallback in case that the next line fails. + eval -- "$1=($2)" + + # Restore the settings. Note: Changing GLOBIGNORE affects the state of + # "shopt -q dotglob", so we need to explicitly restore the original state + # of "shopt -q dotglob". + _comp_unlocal GLOBIGNORE + if [[ :$_original_opts: == *:dotglob:* ]]; then + shopt -s dotglob + else + shopt -u dotglob + fi + [[ :$_original_opts: == *:nullglob:* ]] || shopt -u nullglob + [[ :$_original_opts: == *:failglob:* ]] && shopt -s failglob + [[ :$_original_opts: == *:noglob:* ]] && set -o noglob + eval "((\${#$1[@]}))" +} + +# Split a string and assign to an array. This function basically performs +# `IFS=; =()` but properly handles saving/restoring the +# state of `IFS` and the shell option `noglob`. A naive splitting by +# `arr=(...)` suffers from unexpected IFS and pathname expansions, so one +# should prefer this function to such naive splitting. +# OPTIONS +# -a Append to the array +# -F sep Set a set of separator characters (used as IFS). The default +# separator is $' \t\n' +# -l The same as -F $'\n' +# @param $1 array_name The array name +# The array name should not start with an underscores "_", which is +# internally used. The array name should not be either "IFS" or +# "OPT{IND,ARG,ERR}". +# @param $2 text The string to split +# @return 2 when the usage is wrong, 0 when one or more completions are +# generated, or 1 when the execution succeeds but no candidates are +# generated. +# @since 2.12 +_comp_split() +{ + local _append="" IFS=$' \t\n' + + local OPTIND=1 OPTARG="" OPTERR=0 _opt + while getopts ':alF:' _opt "$@"; do + case $_opt in + a) _append=set ;; + l) IFS=$'\n' ;; + F) IFS=$OPTARG ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 2 + ;; + esac + done + shift "$((OPTIND - 1))" + if (($# != 2)); then + printf '%s\n' "bash_completion: $FUNCNAME: unexpected number of arguments" >&2 + printf '%s\n' "usage: $FUNCNAME [-al] [-F SEP] ARRAY_NAME TEXT" >&2 + return 2 + elif [[ $1 == @(*[^_a-zA-Z0-9]*|[0-9]*|''|_*|IFS|OPTIND|OPTARG|OPTERR) ]]; then + printf '%s\n' "bash_completion: $FUNCNAME: invalid array name '$1'" >&2 + return 2 + fi + + local _original_opts=$SHELLOPTS + set -o noglob + + local _old_size _new_size + if [[ $_append ]]; then + eval "$1+=()" # in case $1 is unset + eval "_old_size=\${#$1[@]}" + eval "$1+=(\$2)" + else + _old_size=0 + eval "$1=(\$2)" + fi + eval "_new_size=\${#$1[@]}" + + [[ :$_original_opts: == *:noglob:* ]] || set +o noglob + ((_new_size > _old_size)) +} + +# Helper function for _comp_compgen +# @var[in] $? +# @var[in] _var +# @var[in] _append +# @return original $? +_comp_compgen__error_fallback() +{ + local _status=$? + if [[ $_append ]]; then + # make sure existence of variable + eval -- "$_var+=()" + else + eval -- "$_var=()" + fi + return "$_status" +} + +# Provide a common interface to generate completion candidates in COMPREPLY or +# in a specified array. +# OPTIONS +# -a Append to the array +# -v arr Store the results to the array ARR. The default is `COMPREPLY`. +# The array name should not start with an underscores "_", which is +# internally used. The array name should not be any of "cur", "IFS" +# or "OPT{IND,ARG,ERR}". +# -U var Unlocalize VAR before performing the assignments. This option can +# be specified multiple times to register multiple variables. This +# option is supposed to be used in implementing a generator (G1) when +# G1 defines a local variable name that does not start with `_`. In +# such a case, when the target variable specified to G1 by `-v VAR1` +# conflicts with the local variable, the assignment to the target +# variable fails to propagate outside G1. To avoid such a situation, +# G1 can call `_comp_compgen` with `-U VAR` to unlocalize `VAR` +# before accessing the target variable. For a builtin compgen call +# (i.e., _comp_compgen [options] -- options), VAR is unlocalized +# after calling the builtin `compgen` but before assigning results to +# the target array. For a generator call (i.e., _comp_compgen +# [options] G2 ...), VAR is unlocalized before calling the child +# generator function `_comp_compgen_G2`. +# -c cur Set a word used as a prefix to filter the completions. The default +# is ${cur-}. +# -R The same as -c ''. Use raw outputs without filtering. +# -C dir Evaluate compgen/generator in the specified directory. +# @var[in,opt] cur Used as the default value of a prefix to filter the +# completions. +# +# Usage #1: _comp_compgen [-alR|-F sep|-v arr|-c cur|-C dir] -- options... +# Call `compgen` with the specified arguments and store the results in the +# specified array. This function essentially performs arr=($(compgen args...)) +# but properly handles shell options, IFS, etc. using _comp_split. This +# function is equivalent to `_comp_split [-a] -l arr "$(IFS=sep; compgen +# args... -- cur)"`, but this pattern is frequent in the codebase and is good +# to separate out as a function for the possible future implementation change. +# OPTIONS +# -F sep Set a set of separator characters (used as IFS in evaluating +# `compgen'). The default separator is $' \t\n'. Note that this is +# not the set of separators to delimit output of `compgen', but the +# separators in evaluating the expansions of `-W '...'`, etc. The +# delimiter of the output of `compgen` is always a newline. +# -l The same as -F $'\n'. Use lines as words in evaluating compgen. +# @param $1... options Arguments that are passed to compgen (if $1 starts with +# a hyphen `-`). +# +# Note: References to positional parameters $1, $2, ... (such as -W '$1') +# will not work as expected because these reference the arguments of +# `_comp_compgen' instead of those of the caller function. When there are +# needs to reference them, save the arguments to an array and reference the +# array instead. +# +# Note: The array option `-V arr` in bash >= 5.3 should be instead specified +# as `-v arr` as a part of the `_comp_compgen` options. +# @return True (0) if at least one completion is generated, False (1) if no +# completion is generated, or 2 with an incorrect usage. +# +# Usage #2: _comp_compgen [-aR|-v arr|-c cur|-C dir|-i cmd|-x cmd] name args... +# Call the generator `_comp_compgen_NAME ARGS...` with the specified options. +# This provides a common interface to call the functions `_comp_compgen_NAME`, +# which produce completion candidates, with custom options [-alR|-v arr|-c +# cur]. The option `-F sep` is not used with this usage. +# OPTIONS +# -x cmd Call exported generator `_comp_xfunc_CMD_compgen_NAME` +# -i cmd Call internal generator `_comp_cmd_CMD__compgen_NAME` +# @param $1... name args Calls the function _comp_compgen_NAME with the +# specified ARGS (if $1 does not start with a hyphen `-`). The options +# [-alR|-v arr|-c cur] are inherited by the child calls of `_comp_compgen` +# inside `_comp_compgen_NAME` unless the child call `_comp_compgen` receives +# overriding options. +# @var[in,opt,internal] _comp_compgen__append +# @var[in,opt,internal] _comp_compgen__var +# @var[in,opt,internal] _comp_compgen__cur +# These variables are internally used to pass the effect of the options +# [-alR|-v arr|-c cur] to the child calls of `_comp_compgen` in +# `_comp_compgen_NAME`. +# @return Exit status of the generator. +# +# @remarks When no options are supplied to _comp_compgen, `_comp_compgen NAME +# args` is equivalent to the direct call `_comp_compgen_NAME args`. As the +# direct call is slightly more efficient, the direct call is preferred over +# calling it through `_comp_compgen`. +# +# @remarks Design `_comp_compgen_NAME`: a function that produce completions can +# be defined with the name _comp_compgen_NAME. The function is supposed to +# generate completions by calling `_comp_compgen`. To reflect the options +# specified to the outer calls of `_comp_compgen`, the function should not +# directly modify `COMPREPLY`. To add words, one can call +# +# _comp_compgen -- -W '"${words[@]}"' +# +# To directly add words without filtering by `cur`, one can call +# +# _comp_compgen -R -- -W '"${words[@]}"' +# +# or use the utility `_comp_compgen_set`: +# +# _comp_compgen_set "${words[@]}" +# +# Other nested calls of _comp_compgen can also be used. The function is +# supposed to replace the existing content of the array by default to allow the +# caller control whether to replace or append by the option `-a`. +# +# @since 2.12 +_comp_compgen() +{ + local _append= + local _var= + local _cur=${_comp_compgen__cur-${cur-}} + local _dir="" + local _ifs=$' \t\n' _has_ifs="" + local _icmd="" _xcmd="" + local -a _upvars=() + + local _old_nocasematch="" + if shopt -q nocasematch; then + _old_nocasematch=set + shopt -u nocasematch + fi + local OPTIND=1 OPTARG="" OPTERR=0 _opt + while getopts ':av:U:Rc:C:lF:i:x:' _opt "$@"; do + case $_opt in + a) _append=set ;; + v) + if [[ $OPTARG == @(*[^_a-zA-Z0-9]*|[0-9]*|''|_*|IFS|OPTIND|OPTARG|OPTERR|cur) ]]; then + printf 'bash_completion: %s: -v: invalid array name `%s'\''\n' "$FUNCNAME" "$OPTARG" >&2 + return 2 + fi + _var=$OPTARG + ;; + U) + if [[ $OPTARG == @(*[^_a-zA-Z0-9]*|[0-9]*|'') ]]; then + printf 'bash_completion: %s: -U: invalid variable name `%s'\''\n' "$FUNCNAME" "$OPTARG" >&2 + return 2 + elif [[ $OPTARG == @(_*|IFS|OPTIND|OPTARG|OPTERR|cur) ]]; then + printf 'bash_completion: %s: -U: unnecessary to mark `%s'\'' as upvar\n' "$FUNCNAME" "$OPTARG" >&2 + return 2 + fi + _upvars+=("$OPTARG") + ;; + c) _cur=$OPTARG ;; + R) _cur="" ;; + C) + if [[ ! $OPTARG ]]; then + printf 'bash_completion: %s: -C: invalid directory name `%s'\''\n' "$FUNCNAME" "$OPTARG" >&2 + return 2 + fi + _dir=$OPTARG + ;; + l) _has_ifs=set _ifs=$'\n' ;; + F) _has_ifs=set _ifs=$OPTARG ;; + [ix]) + if [[ ! $OPTARG ]]; then + printf 'bash_completion: %s: -%s: invalid command name `%s'\''\n' "$FUNCNAME" "$_opt" "$OPTARG" >&2 + return 2 + elif [[ $_icmd ]]; then + printf 'bash_completion: %s: -%s: `-i %s'\'' is already specified\n' "$FUNCNAME" "$_opt" "$_icmd" >&2 + return 2 + elif [[ $_xcmd ]]; then + printf 'bash_completion: %s: -%s: `-x %s'\'' is already specified\n' "$FUNCNAME" "$_opt" "$_xcmd" >&2 + return 2 + fi + ;;& + i) _icmd=$OPTARG ;; + x) _xcmd=$OPTARG ;; + *) + printf 'bash_completion: %s: usage error\n' "$FUNCNAME" >&2 + return 2 + ;; + esac + done + [[ $_old_nocasematch ]] && shopt -s nocasematch + shift "$((OPTIND - 1))" + if (($# == 0)); then + printf 'bash_completion: %s: unexpected number of arguments\n' "$FUNCNAME" >&2 + printf 'usage: %s [-alR|-F SEP|-v ARR|-c CUR] -- ARGS...' "$FUNCNAME" >&2 + return 2 + fi + if [[ ! $_var ]]; then + # Inherit _append and _var only when -v var is unspecified. + _var=${_comp_compgen__var-COMPREPLY} + [[ $_append ]] || _append=${_comp_compgen__append-} + fi + + if [[ $1 != -* ]]; then + # usage: _comp_compgen [options] NAME args + if [[ $_has_ifs ]]; then + printf 'bash_completion: %s: `-l'\'' and `-F sep'\'' are not supported for generators\n' "$FUNCNAME" >&2 + return 2 + fi + + local -a _generator + if [[ $_icmd ]]; then + _generator=("_comp_cmd_${_icmd//[^a-zA-Z0-9_]/_}__compgen_$1") + elif [[ $_xcmd ]]; then + _generator=(_comp_xfunc "$_xcmd" "compgen_$1") + else + _generator=("_comp_compgen_$1") + fi + if ! declare -F -- "${_generator[0]}" &>/dev/null; then + printf 'bash_completion: %s: unrecognized generator `%s'\'' (function %s not found)\n' "$FUNCNAME" "$1" "${_generator[0]}" >&2 + return 2 + fi + shift + + _comp_compgen__call_generator "$@" + else + # usage: _comp_compgen [options] -- [compgen_options] + if [[ $_icmd || $_xcmd ]]; then + printf 'bash_completion: %s: generator name is unspecified for `%s'\''\n' "$FUNCNAME" "${_icmd:+-i $_icmd}${_xcmd:+x $_xcmd}" >&2 + return 2 + fi + + # Note: $* in the below checks would be affected by uncontrolled IFS in + # bash >= 5.0, so we need to set IFS to the normal value. The behavior + # in bash < 5.0, where unquoted $* in conditional command did not honor + # IFS, was a bug. + # Note: Also, ${_cur:+-- "$_cur"} and ${_append:+-a} would be affected + # by uncontrolled IFS. + local IFS=$' \t\n' + # Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string + # "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9] + # separately. + if [[ $* == *\$[0-9]* || $* == *\$\{[0-9]* ]]; then + printf 'bash_completion: %s: positional parameter $1, $2, ... do not work inside this function\n' "$FUNCNAME" >&2 + return 2 + fi + + _comp_compgen__call_builtin "$@" + fi +} + +# Helper function for _comp_compgen. This function calls a generator. +# @param $1... generator_args +# @var[in] _dir +# @var[in] _cur +# @arr[in] _generator +# @arr[in] _upvars +# @var[in] _append +# @var[in] _var +_comp_compgen__call_generator() +{ + ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" + + if [[ $_dir ]]; then + local _original_pwd=$PWD + local PWD=${PWD-} OLDPWD=${OLDPWD-} + # Note: We also redirect stdout because `cd` may output the target + # directory to stdout when CDPATH is set. + command cd -- "$_dir" &>/dev/null || + { + _comp_compgen__error_fallback + return + } + fi + + local _comp_compgen__append=$_append + local _comp_compgen__var=$_var + local _comp_compgen__cur=$_cur cur=$_cur + # Note: we use $1 as a part of a function name, and we use $2... as + # arguments to the function if any. + # shellcheck disable=SC2145 + "${_generator[@]}" "$@" + local _status=$? + + # Go back to the original directory. + # Note: Failure of this line results in the change of the current + # directory visible to the user. We intentionally do not redirect + # stderr so that the error message appear in the terminal. + # shellcheck disable=SC2164 + [[ $_dir ]] && command cd -- "$_original_pwd" + + return "$_status" +} + +# Helper function for _comp_compgen. This function calls the builtin compgen. +# @param $1... compgen_args +# @var[in] _dir +# @var[in] _ifs +# @var[in] _cur +# @arr[in] _upvars +# @var[in] _append +# @var[in] _var +if ((BASH_VERSINFO[0] > 5 || BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3)); then + # bash >= 5.3 has `compgen -V array_name` + _comp_compgen__call_builtin() + { + if [[ $_dir ]]; then + local _original_pwd=$PWD + local PWD=${PWD-} OLDPWD=${OLDPWD-} + # Note: We also redirect stdout because `cd` may output the target + # directory to stdout when CDPATH is set. + command cd -- "$_dir" &>/dev/null || { + _comp_compgen__error_fallback + return + } + fi + + local -a _result=() + + # Note: We specify -X '' to exclude empty completions to make the + # behavior consistent with the implementation for Bash < 5.3 where + # `_comp_split -l` removes empty lines. If the caller specifies -X + # pat, the effect of -X '' is overwritten by the specified one. + IFS=$_ifs compgen -V _result -X '' "$@" ${_cur:+-- "$_cur"} || { + _comp_compgen__error_fallback + return + } + + # Go back to the original directory. + # Note: Failure of this line results in the change of the current + # directory visible to the user. We intentionally do not redirect + # stderr so that the error message appear in the terminal. + # shellcheck disable=SC2164 + [[ $_dir ]] && command cd -- "$_original_pwd" + + ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" + ((${#_result[@]})) || return + if [[ $_append ]]; then + eval -- "$_var+=(\"\${_result[@]}\")" + else + eval -- "$_var=(\"\${_result[@]}\")" + fi + return + } +else + _comp_compgen__call_builtin() + { + local _result + _result=$( + if [[ $_dir ]]; then + # Note: We also redirect stdout because `cd` may output the target + # directory to stdout when CDPATH is set. + command cd -- "$_dir" &>/dev/null || return + fi + IFS=$_ifs compgen "$@" ${_cur:+-- "$_cur"} + ) || { + _comp_compgen__error_fallback + return + } + + ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" + _comp_split -l ${_append:+-a} "$_var" "$_result" + } +fi + +# usage: _comp_compgen_set [words...] +# Reset COMPREPLY with the specified WORDS. If no arguments are specified, the +# array is cleared. +# +# When an array name is specified by `-v VAR` in a caller _comp_compgen, the +# array is reset instead of COMPREPLY. When the `-a` flag is specified in a +# caller _comp_compgen, the words are appended to the existing elements of the +# array instead of replacing the existing elements. This function ignores +# ${cur-} or the prefix specified by `-v CUR`. +# @return 0 if at least one completion is generated, or 1 otherwise. +# @since 2.12 +_comp_compgen_set() +{ + local _append=${_comp_compgen__append-} + local _var=${_comp_compgen__var-COMPREPLY} + eval -- "$_var${_append:++}=(\"\$@\")" + (($#)) +} + +# Simply split the text and generate completions. This function should be used +# instead of `_comp_compgen -- -W "$(command)"`, which is vulnerable because +# option -W evaluates the shell expansions included in the option argument. +# Options: +# -F sep Specify the separators. The default is $' \t\n' +# -l The same as -F $'\n' +# -X arg The same as the compgen option -X. +# -S arg The same as the compgen option -S. +# -P arg The same as the compgen option -P. +# -o arg The same as the compgen option -o. +# @param $1 String to split +# @return 0 if at least one completion is generated, or 1 otherwise. +# @since 2.12 +_comp_compgen_split() +{ + local _ifs=$' \t\n' + local -a _compgen_options=() + + local OPTIND=1 OPTARG="" OPTERR=0 _opt + while getopts ':lF:X:S:P:o:' _opt "$@"; do + case $_opt in + l) _ifs=$'\n' ;; + F) _ifs=$OPTARG ;; + [XSPo]) _compgen_options+=("-$_opt" "$OPTARG") ;; + *) + printf 'bash_completion: usage: %s [-l|-F sep] [--] str\n' "$FUNCNAME" >&2 + return 2 + ;; + esac + done + shift "$((OPTIND - 1))" + if (($# != 1)); then + printf 'bash_completion: %s: unexpected number of arguments.\n' "$FUNCNAME" >&2 + printf 'usage: %s [-l|-F sep] [--] str' "$FUNCNAME" >&2 + return 2 + fi + + local input=$1 IFS=$' \t\n' + _comp_compgen -F "$_ifs" -U input -- ${_compgen_options[@]+"${_compgen_options[@]}"} -W '$input' +} + +# Check if the argument looks like a path. +# @param $1 thing to check +# @return True (0) if it does, False (> 0) otherwise +# @since 2.12 +_comp_looks_like_path() +{ + [[ ${1-} == @(*/|[.~])* ]] +} + +# Reassemble command line words, excluding specified characters from the +# list of word completion separators (COMP_WORDBREAKS). +# @param $1 chars Characters out of $COMP_WORDBREAKS which should +# NOT be considered word breaks. This is useful for things like scp where +# we want to return host:path and not only path, so we would pass the +# colon (:) as $1 here. +# @param $2 words Name of variable to return words to +# @param $3 cword Name of variable to return cword to +# +_comp__reassemble_words() +{ + local exclude="" i j line ref + # Exclude word separator characters? + if [[ $1 ]]; then + # Yes, exclude word separator characters; + # Exclude only those characters, which were really included + exclude="[${1//[^$COMP_WORDBREAKS]/}]" + fi + + # Default to cword unchanged + printf -v "$3" %s "$COMP_CWORD" + # Are characters excluded which were former included? + if [[ $exclude ]]; then + # Yes, list of word completion separators has shrunk; + line=$COMP_LINE + # Re-assemble words to complete + for ((i = 0, j = 0; i < ${#COMP_WORDS[@]}; i++, j++)); do + # Is current word not word 0 (the command itself) and is word not + # empty and is word made up of just word separator characters to + # be excluded and is current word not preceded by whitespace in + # original line? + while [[ $i -gt 0 && ${COMP_WORDS[i]} == +($exclude) ]]; do + # Is word separator not preceded by whitespace in original line + # and are we not going to append to word 0 (the command + # itself), then append to current word. + [[ $line != [[:blank:]]* ]] && ((j >= 2)) && ((j--)) + # Append word separator to current or new word + ref="$2[$j]" + printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}" + # Indicate new cword + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" + # Remove optional whitespace + word separator from line copy + line=${line#*"${COMP_WORDS[i]}"} + # Indicate next word if available, else end *both* while and + # for loop + if ((i < ${#COMP_WORDS[@]} - 1)); then + ((i++)) + else + break 2 + fi + # Start new word if word separator in original line is + # followed by whitespace. + [[ $line == [[:blank:]]* ]] && ((j++)) + done + # Append word to current word + ref="$2[$j]" + printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}" + # Remove optional whitespace + word from line copy + line=${line#*"${COMP_WORDS[i]}"} + # Indicate new cword + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" + done + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" + else + # No, list of word completions separators hasn't changed; + for i in "${!COMP_WORDS[@]}"; do + printf -v "$2[i]" %s "${COMP_WORDS[i]}" + done + fi +} + +# @param $1 exclude Characters out of $COMP_WORDBREAKS which should NOT be +# considered word breaks. This is useful for things like scp where +# we want to return host:path and not only path, so we would pass the +# colon (:) as $1 in this case. +# @param $2 words Name of variable to return words to +# @param $3 cword Name of variable to return cword to +# @param $4 cur Name of variable to return current word to complete to +# @see _comp__reassemble_words() +_comp__get_cword_at_cursor() +{ + local cword words=() + _comp__reassemble_words "$1" words cword + + local i cur="" index=$COMP_POINT lead=${COMP_LINE:0:COMP_POINT} + # Cursor not at position 0 and not led by just space(s)? + if [[ $index -gt 0 && ($lead && ${lead//[[:space:]]/}) ]]; then + cur=$COMP_LINE + for ((i = 0; i <= cword; ++i)); do + # Current word fits in $cur, and $cur doesn't match cword? + while [[ ${#cur} -ge ${#words[i]} && + ${cur:0:${#words[i]}} != "${words[i]-}" ]]; do + # Strip first character + cur=${cur:1} + # Decrease cursor position, staying >= 0 + ((index > 0)) && ((index--)) + done + + # Does found word match cword? + if ((i < cword)); then + # No, cword lies further; + local old_size=${#cur} + cur=${cur#"${words[i]}"} + local new_size=${#cur} + ((index -= old_size - new_size)) + fi + done + # Clear $cur if just space(s) + [[ $cur && ! ${cur//[[:space:]]/} ]] && cur= + # Zero $index if negative + ((index < 0)) && index=0 + fi + + local IFS=$' \t\n' + local "$2" "$3" "$4" && _comp_upvars -a"${#words[@]}" "$2" ${words[@]+"${words[@]}"} \ + -v "$3" "$cword" -v "$4" "${cur:0:index}" +} + +# Get the word to complete and optional previous words. +# This is nicer than ${COMP_WORDS[COMP_CWORD]}, since it handles cases +# where the user is completing in the middle of a word. +# (For example, if the line is "ls foobar", +# and the cursor is here --------> ^ +# Also one is able to cross over possible wordbreak characters. +# Usage: _comp_get_words [OPTIONS] [VARNAMES] +# Available VARNAMES: +# cur Return cur via $cur +# prev Return prev via $prev +# words Return words via $words +# cword Return cword via $cword +# +# Available OPTIONS: +# -n EXCLUDE Characters out of $COMP_WORDBREAKS which should NOT be +# considered word breaks. This is useful for things like scp +# where we want to return host:path and not only path, so we +# would pass the colon (:) as -n option in this case. +# -c VARNAME Return cur via $VARNAME +# -p VARNAME Return prev via $VARNAME +# -w VARNAME Return words via $VARNAME +# -i VARNAME Return cword via $VARNAME +# +# Example usage: +# +# $ _comp_get_words -n : cur prev +# +# @since 2.12 +_comp_get_words() +{ + local exclude="" flag i OPTIND=1 + local cur cword words=() + local upargs=() upvars=() vcur="" vcword="" vprev="" vwords="" + + while getopts "c:i:n:p:w:" flag "$@"; do + case $flag in + [cipw]) + if [[ $OPTARG != [a-zA-Z_]*([a-zA-Z_0-9])?(\[*\]) ]]; then + echo "bash_completion: $FUNCNAME: -$flag: invalid variable name \`$OPTARG'" >&2 + return 1 + fi + ;;& + c) vcur=$OPTARG ;; + i) vcword=$OPTARG ;; + n) exclude=$OPTARG ;; + p) vprev=$OPTARG ;; + w) vwords=$OPTARG ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 1 + ;; + esac + done + while [[ $# -ge $OPTIND ]]; do + case ${!OPTIND} in + cur) vcur=cur ;; + prev) vprev=prev ;; + cword) vcword=cword ;; + words) vwords=words ;; + *) + echo "bash_completion: $FUNCNAME: \`${!OPTIND}':" \ + "unknown argument" >&2 + return 1 + ;; + esac + ((OPTIND += 1)) + done + + _comp__get_cword_at_cursor "${exclude-}" words cword cur + + [[ $vcur ]] && { + upvars+=("$vcur") + upargs+=(-v "$vcur" "$cur") + } + [[ $vcword ]] && { + upvars+=("$vcword") + upargs+=(-v "$vcword" "$cword") + } + [[ $vprev ]] && { + local value="" + ((cword >= 1)) && value=${words[cword - 1]} + upvars+=("$vprev") + upargs+=(-v "$vprev" "$value") + } + [[ $vwords ]] && { + # Note: bash < 4.4 has a bug that all the elements are connected with + # ${v+"$@"} when IFS does not contain whitespace. + local IFS=$' \t\n' + upvars+=("$vwords") + upargs+=(-a"${#words[@]}" "$vwords" ${words+"${words[@]}"}) + } + + ((${#upvars[@]})) && local "${upvars[@]}" && _comp_upvars "${upargs[@]}" +} + +# Generate the specified items after left-trimming with the word-to-complete +# containing a colon (:). If the word-to-complete does not contain a colon, +# this generates the specified items without modifications. +# @param $@ items to generate +# @var[in] cur current word to complete +# +# @remarks In Bash, with a colon in COMP_WORDBREAKS, words containing colons +# are always completed as entire words if the word to complete contains a +# colon. This function fixes this behavior by removing the +# colon-containing-prefix from the items. +# +# The preferred solution is to remove the colon (:) from COMP_WORDBREAKS in +# your .bashrc: +# +# # Remove colon (:) from list of word completion separators +# COMP_WORDBREAKS=${COMP_WORDBREAKS//:} +# +# See also: Bash FAQ - E13) Why does filename completion misbehave if a colon +# appears in the filename? - https://tiswww.case.edu/php/chet/bash/FAQ +# +# @since 2.12 +_comp_compgen_ltrim_colon() +{ + (($#)) || return 0 + local -a _tmp + _tmp=("$@") + if [[ $cur == *:* && $COMP_WORDBREAKS == *:* ]]; then + # Remove colon-word prefix from items + local _colon_word=${cur%"${cur##*:}"} + _tmp=("${_tmp[@]#"$_colon_word"}") + fi + _comp_compgen_set "${_tmp[@]}" +} + +# If the word-to-complete contains a colon (:), left-trim COMPREPLY items with +# word-to-complete. +# +# @param $1 current word to complete (cur) +# @var[in,out] COMPREPLY +# +# @since 2.12 +_comp_ltrim_colon_completions() +{ + ((${#COMPREPLY[@]})) || return 0 + _comp_compgen -c "$1" ltrim_colon "${COMPREPLY[@]}" +} + +# This function quotes the argument in a way so that readline dequoting +# results in the original argument. This is necessary for at least +# `compgen` which requires its arguments quoted/escaped: +# +# $ ls "a'b/" +# c +# $ compgen -f "a'b/" # Wrong, doesn't return output +# $ compgen -f "a\'b/" # Good +# a\'b/c +# +# See also: +# - https://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html +# - https://www.mail-archive.com/bash-completion-devel@lists.alioth.debian.org/msg01944.html +# @param $1 Argument to quote +# @var[out] REPLY Quoted result is stored in this variable +# @since 2.12 +# shellcheck disable=SC2178 # The assignment is not intended for the global "REPLY" +_comp_quote_compgen() +{ + if [[ $1 == \'* ]]; then + # Leave out first character + REPLY=${1:1} + else + printf -v REPLY %q "$1" + + # If result becomes quoted like this: $'string', re-evaluate in order + # to drop the additional quoting. See also: + # https://www.mail-archive.com/bash-completion-devel@lists.alioth.debian.org/msg01942.html + if [[ $REPLY == \$\'*\' ]]; then + local value=${REPLY:2:-1} # Strip beginning $' and ending '. + value=${value//'%'/%%} # Escape % for printf format. + # shellcheck disable=SC2059 + printf -v REPLY "$value" # Decode escape sequences of \.... + fi + fi +} + +# This function performs file and directory completion. It's better than +# simply using 'compgen -f', because it honours spaces in filenames. +# @param $1 If `-d', complete only on directories. Otherwise filter/pick only +# completions with `.$1' and the uppercase version of it as file +# extension. +# @return 0 if at least one completion is generated, or 1 otherwise. +# +# @since 2.12 +_comp_compgen_filedir() +{ + _comp_compgen_tilde && return + + local -a toks + local _arg=${1-} + + if [[ $_arg == -d ]]; then + _comp_compgen -v toks -- -d + else + local REPLY + _comp_quote_compgen "${cur-}" + local _quoted=$REPLY + _comp_unlocal REPLY + + # work around bash-4.2 where compgen -f "''" produces nothing. + [[ $_quoted == "''" ]] && _quoted="" + + # Munge xspec to contain uppercase version too + # https://lists.gnu.org/archive/html/bug-bash/2010-09/msg00036.html + # news://news.gmane.io/4C940E1C.1010304@case.edu + local _xspec=${_arg:+"!*.@($_arg|${_arg^^})"} _plusdirs=() + + # Use plusdirs to get dir completions if we have a xspec; if we don't, + # there's no need, dirs come along with other completions. Don't use + # plusdirs quite yet if fallback is in use though, in order to not ruin + # the fallback condition with the "plus" dirs. + local _opts=(-f -X "$_xspec") + [[ $_xspec ]] && _plusdirs=(-o plusdirs) + [[ ${BASH_COMPLETION_FILEDIR_FALLBACK-} || ! ${_plusdirs-} ]] || + _opts+=("${_plusdirs[@]}") + + _comp_compgen -v toks -c "$_quoted" -- "${_opts[@]}" + + # Try without filter if it failed to produce anything and configured to + [[ ${BASH_COMPLETION_FILEDIR_FALLBACK-} && + $_arg && ${#toks[@]} -lt 1 ]] && + _comp_compgen -av toks -c "$_quoted" -- \ + -f ${_plusdirs+"${_plusdirs[@]}"} + fi + + if ((${#toks[@]} != 0)); then + # Remove . and .. (as well as */. and */..) from suggestions, unless + # .. or */.. was typed explicitly by the user (for users who use + # tab-completion to append a slash after '..') + if [[ $cur != ?(*/).. ]]; then + _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' + fi + fi + + if ((${#toks[@]} != 0)); then + # 2>/dev/null for direct invocation, e.g. in the _comp_compgen_filedir + # unit test + compopt -o filenames 2>/dev/null + fi + + # Note: bash < 4.4 has a bug that all the elements are connected with + # ${v+"${a[@]}"} when IFS does not contain whitespace. + local IFS=$' \t\n' + _comp_compgen -U toks set ${toks[@]+"${toks[@]}"} +} + +# This function splits $cur=--foo=bar into $prev=--foo, $cur=bar, making it +# easier to support both "--foo bar" and "--foo=bar" style completions. +# `=' should have been removed from COMP_WORDBREAKS when setting $cur for +# this to be useful. +# Returns 0 if current option was split, 1 otherwise. +# +_comp__split_longopt() +{ + if [[ $cur == --?*=* ]]; then + # Cut also backslash before '=' in case it ended up there + # for some reason. + prev=${cur%%?(\\)=*} + cur=${cur#*=} + return 0 + fi + + return 1 +} + +# Complete variables. +# @return True (0) if variables were completed, +# False (> 0) if not. +# @since 2.12 +_comp_compgen_variables() +{ + if [[ $cur =~ ^(\$(\{[!#]?)?)([A-Za-z0-9_]*)$ ]]; then + # Completing $var / ${var / ${!var / ${#var + if [[ $cur == '${'* ]]; then + local arrs vars + _comp_compgen -v vars -c "${BASH_REMATCH[3]}" -- -A variable -P "${BASH_REMATCH[1]}" -S '}' + _comp_compgen -v arrs -c "${BASH_REMATCH[3]}" -- -A arrayvar -P "${BASH_REMATCH[1]}" -S '[' + if ((${#vars[@]} == 1 && ${#arrs[@]} != 0)); then + # Complete ${arr with ${array[ if there is only one match, and that match is an array variable + compopt -o nospace + _comp_compgen -U vars -U arrs -R -- -W '"${arrs[@]}"' + else + # Complete ${var with ${variable} + _comp_compgen -U vars -U arrs -R -- -W '"${vars[@]}"' + fi + else + # Complete $var with $variable + _comp_compgen -ac "${BASH_REMATCH[3]}" -- -A variable -P '$' + fi + return 0 + elif [[ $cur =~ ^(\$\{[#!]?)([A-Za-z0-9_]*)\[([^]]*)$ ]]; then + # Complete ${array[i with ${array[idx]} + local vars + _comp_compgen -v vars -c "${BASH_REMATCH[3]}" -- -W '"${!'"${BASH_REMATCH[2]}"'[@]}"' \ + -P "${BASH_REMATCH[1]}${BASH_REMATCH[2]}[" -S ']}' + # Complete ${arr[@ and ${arr[* + if [[ ${BASH_REMATCH[3]} == [@*] ]]; then + vars+=("${BASH_REMATCH[1]}${BASH_REMATCH[2]}[${BASH_REMATCH[3]}]}") + fi + # array indexes may have colons + if ((${#vars[@]})); then + _comp_compgen -U vars -c "$cur" ltrim_colon "${vars[@]}" + else + _comp_compgen_set + fi + return 0 + elif [[ $cur =~ ^\$\{[#!]?[A-Za-z0-9_]*\[.*\]$ ]]; then + # Complete ${array[idx] with ${array[idx]} + _comp_compgen -c "$cur" ltrim_colon "$cur}" + return 0 + fi + return 1 +} + +# Complete a delimited value. +# +# Usage: [-k] DELIMITER COMPGEN_ARG... +# -k: do not filter out already present tokens in value +# @since 2.12 +_comp_delimited() +{ + local prefix="" delimiter=$1 deduplicate=set + shift + if [[ $delimiter == -k ]]; then + deduplicate="" + delimiter=$1 + shift + fi + [[ $cur == *"$delimiter"* ]] && prefix=${cur%"$delimiter"*}$delimiter + + if [[ $deduplicate ]]; then + # We could construct a -X pattern to feed to compgen, but that'd + # conflict with possibly already set -X in $@, as well as have + # glob char escaping issues to deal with. Do removals by hand instead. + _comp_compgen -R -- "$@" + local -a existing + _comp_split -F "$delimiter" existing "$cur" + # Do not remove the last from existing if it's not followed by the + # delimiter so we get space appended. + [[ ! $cur || $cur == *"$delimiter" ]] || unset -v "existing[${#existing[@]}-1]" + if ((${#COMPREPLY[@]})); then + local x i + for x in ${existing+"${existing[@]}"}; do + for i in "${!COMPREPLY[@]}"; do + if [[ $x == "${COMPREPLY[i]}" ]]; then + unset -v 'COMPREPLY[i]' + continue 2 # assume no dupes in COMPREPLY + fi + done + done + ((${#COMPREPLY[@]})) && + _comp_compgen -c "${cur##*"$delimiter"}" -- -W '"${COMPREPLY[@]}"' + fi + else + _comp_compgen -c "${cur##*"$delimiter"}" -- "$@" + fi + + # It would seem that in some specific cases we could avoid adding the + # prefix to all completions, thereby making the list of suggestions + # cleaner, and only adding it when there's exactly one completion. + # The cases where this opportunity has been observed involve having + # `show-all-if-ambiguous` on, but even that has cases where it fails + # and the last separator including everything before it is lost. + # https://github.com/scop/bash-completion/pull/913#issuecomment-1490140309 + local i + for i in "${!COMPREPLY[@]}"; do + COMPREPLY[i]="$prefix${COMPREPLY[i]}" + done + + [[ $delimiter != : ]] || _comp_ltrim_colon_completions "$cur" +} + +# Complete assignment of various known environment variables. +# +# The word to be completed is expected to contain the entire assignment, +# including the variable name and the "=". Some known variables are completed +# with colon separated values; for those to work, colon should not have been +# used to split words. See related parameters to _comp_initialize. +# +# @param $1 variable assignment to be completed +# @return True (0) if variable value completion was attempted, +# False (> 0) if not. +# @since 2.12 +_comp_variable_assignments() +{ + local cur=${1-} i + + if [[ $cur =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then + prev=${BASH_REMATCH[1]} + cur=${BASH_REMATCH[2]} + else + return 1 + fi + + case $prev in + TZ) + cur=/usr/share/zoneinfo/$cur + _comp_compgen_filedir + if ((${#COMPREPLY[@]})); then + for i in "${!COMPREPLY[@]}"; do + if [[ ${COMPREPLY[i]} == *.tab ]]; then + unset -v 'COMPREPLY[i]' + continue + elif [[ -d ${COMPREPLY[i]} ]]; then + COMPREPLY[i]+=/ + compopt -o nospace + fi + COMPREPLY[i]=${COMPREPLY[i]#/usr/share/zoneinfo/} + done + fi + ;; + TERM) + _comp_compgen_terms + ;; + LANG | LC_*) + _comp_compgen_split -- "$(locale -a 2>/dev/null)" + ;; + LANGUAGE) + _comp_delimited : -W '$(locale -a 2>/dev/null)' + ;; + *) + _comp_compgen_variables && return 0 + _comp_compgen -a filedir + ;; + esac + + return 0 +} + +# Initialize completion and deal with various general things: do file +# and variable completion where appropriate, and adjust prev, words, +# and cword as if no redirections exist so that completions do not +# need to deal with them. Before calling this function, make sure +# cur, prev, words, and cword are local, ditto split if you use -s. +# +# Options: +# -n EXCLUDE Passed to _comp_get_words -n with redirection chars +# -e XSPEC Passed to _comp_compgen_filedir as first arg for stderr +# redirections +# -o XSPEC Passed to _comp_compgen_filedir as first arg for other output +# redirections +# -i XSPEC Passed to _comp_compgen_filedir as first arg for stdin +# redirections +# -s Split long options with _comp__split_longopt, implies -n = +# @param $1...$3 args Original arguments specified to the completion function. +# The first argument $1 is command name. The second +# argument $2 is the string before the cursor in the +# current word. The third argument $3 is the previous +# word. +# @var[out] cur Reconstructed current word +# @var[out] prev Reconstructed previous word +# @var[out] words Reconstructed words +# @var[out] cword Current word index in `words` +# @var[out] comp_args Original arguments specified to the completion +# function are saved in this array, if the arguments +# $1...$3 is specified. +# @var[out,opt] was_split When "-s" is specified, `"set"/""` is set depending +# on whether the split happened. +# @return True (0) if completion needs further processing, +# False (> 0) no further processing is necessary. +# +# @since 2.12 +_comp_initialize() +{ + local exclude="" opt_split="" outx="" errx="" inx="" + + local flag OPTIND=1 OPTARG="" OPTERR=0 + while getopts "n:e:o:i:s" flag "$@"; do + case $flag in + n) exclude+=$OPTARG ;; + e) errx=$OPTARG ;; + o) outx=$OPTARG ;; + i) inx=$OPTARG ;; + s) + opt_split="set" + was_split="" + exclude+="=" + ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 1 + ;; + esac + done + shift "$((OPTIND - 1))" + (($#)) && comp_args=("$@") + + COMPREPLY=() + local redir='@(?(+([0-9])|{[a-zA-Z_]*([a-zA-Z_0-9])})@(>?([>|&])|&])|<?(>))' + _comp_get_words -n "$exclude<>&" cur prev words cword + + # Complete variable names. + _comp_compgen_variables && return 1 + + # Complete on files if current is a redirect possibly followed by a + # filename, e.g. ">foo", or previous is a "bare" redirect, e.g. ">". + # shellcheck disable=SC2053 + if [[ $cur == $redir* || ${prev-} == $redir ]]; then + local xspec + case $cur in + 2'>'*) xspec=${errx-} ;; + *'>'*) xspec=${outx-} ;; + *'<'*) xspec=${inx-} ;; + *) + case $prev in + 2'>'*) xspec=${errx-} ;; + *'>'*) xspec=${outx-} ;; + *'<'*) xspec=${inx-} ;; + esac + ;; + esac + # shellcheck disable=SC2295 # redir is a pattern + cur=${cur##$redir} + _comp_compgen_filedir "$xspec" + return 1 + fi + + # Remove all redirections so completions don't have to deal with them. + local i skip + for ((i = 1; i < ${#words[@]}; )); do + if [[ ${words[i]} == $redir* ]]; then + # If "bare" redirect, remove also the next word (skip=2). + # shellcheck disable=SC2053 + [[ ${words[i]} == $redir ]] && skip=2 || skip=1 + words=("${words[@]:0:i}" "${words[@]:i+skip}") + ((i <= cword)) && ((cword -= skip)) + else + ((i++)) + fi + done + + ((cword <= 0)) && return 1 + prev=${words[cword - 1]} + + [[ $opt_split ]] && _comp__split_longopt && was_split="set" + + return 0 +} + +# Helper function for _comp_compgen_help and _comp_compgen_usage. +# Obtain the help output based on the arguments. +# @param $@ args Arguments specified to the caller. +# @var[out] _lines +# @return 2 if the usage is wrong, 1 if no output is obtained, or otherwise 0. +_comp_compgen_help__get_help_lines() +{ + local -a help_cmd + case ${1-} in + -) + if (($# > 1)); then + printf 'bash_completion: %s -: extra arguments for -\n' "${FUNCNAME[1]}" >&2 + printf 'usage: %s -\n' "${FUNCNAME[1]}" >&2 + printf 'usage: %s -c cmd args...\n' "${FUNCNAME[1]}" >&2 + printf 'usage: %s [-- args...]\n' "${FUNCNAME[1]}" >&2 + return 2 + fi + help_cmd=(exec cat) + ;; + -c) + if (($# < 2)); then + printf 'bash_completion: %s -c: no command is specified\n' "${FUNCNAME[1]}" >&2 + printf 'usage: %s -\n' "${FUNCNAME[1]}" >&2 + printf 'usage: %s -c cmd args...\n' "${FUNCNAME[1]}" >&2 + printf 'usage: %s [-- args...]\n' "${FUNCNAME[1]}" >&2 + return 2 + fi + help_cmd=("${@:2}") + ;; + --) shift 1 ;& + *) + local REPLY + _comp_dequote "${comp_args[0]-}" || REPLY=${comp_args[0]-} + help_cmd=("${REPLY:-false}" "$@") + ;; + esac + + local REPLY + _comp_split -l REPLY "$(LC_ALL=C "${help_cmd[@]}" 2>&1)" && + _lines=("${REPLY[@]}") +} + +# Helper function for _comp_compgen_help and _comp_compgen_usage. +# @var[in,out] options Add options +# @return True (0) if an option was found, False (> 0) otherwise +_comp_compgen_help__parse() +{ + local option option2 i + + # Take first found long option, or first one (short) if not found. + option= + local -a array + if _comp_split -F $' \t\n,/|' array "$1"; then + for i in "${array[@]}"; do + case "$i" in + ---*) break ;; + --?*) + option=$i + break + ;; + -?*) [[ $option ]] || option=$i ;; + *) break ;; + esac + done + fi + [[ $option ]] || return 1 + + # Expand --[no]foo to --foo and --nofoo etc + if [[ $option =~ (\[((no|dont)-?)\]). ]]; then + option2=${option/"${BASH_REMATCH[1]}"/} + option2=${option2%%[<{().[]*} + options+=("${option2/=*/=}") + option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"} + fi + + [[ $option =~ ^([^=<{().[]|\.[A-Za-z0-9])+=? ]] && + options+=("$BASH_REMATCH") +} + +# Parse GNU style help output of the given command and generate and store +# completions in an array. The help output is produced in the way depending on +# the usage: +# usage: _comp_compgen_help - # read from stdin +# usage: _comp_compgen_help -c cmd args... # run "cmd args..." +# usage: _comp_compgen_help [[--] args...] # run "${comp_args[0]} args..." +# When no arguments are specified, `--help` is assumed. +# +# @var[in] comp_args[0] +# @since 2.12 +_comp_compgen_help() +{ + (($#)) || set -- -- --help + + local -a _lines + _comp_compgen_help__get_help_lines "$@" || return "$?" + + local -a options=() + local _line + for _line in "${_lines[@]}"; do + [[ $_line == *([[:blank:]])-* ]] || continue + # transform "-f FOO, --foo=FOO" to "-f , --foo=FOO" etc + while [[ $_line =~ ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+([,_-]+[A-Z0-9]+)?(\.\.+)?\]? ]]; do + _line=${_line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"} + done + _comp_compgen_help__parse "${_line// or /, }" + done + ((${#options[@]})) || return 1 + + _comp_compgen -U options -- -W '"${options[@]}"' + return 0 +} + +# Parse BSD style usage output (options in brackets) of the given command. The +# help output is produced in the way depending on the usage: +# usage: _comp_compgen_usage - # read from stdin +# usage: _comp_compgen_usage -c cmd args... # run "cmd args..." +# usage: _comp_compgen_usage [[--] args...] # run "${comp_args[0]} args..." +# When no arguments are specified, `--usage` is assumed. +# +# @var[in] comp_args[0] +# @since 2.12 +_comp_compgen_usage() +{ + (($#)) || set -- -- --usage + + local -a _lines + _comp_compgen_help__get_help_lines "$@" || return "$?" + + local -a options=() + local _line _match _option _i _char + for _line in "${_lines[@]}"; do + while [[ $_line =~ \[[[:space:]]*(-[^]]+)[[:space:]]*\] ]]; do + _match=${BASH_REMATCH[0]} + _option=${BASH_REMATCH[1]} + case $_option in + -?(\[)+([a-zA-Z0-9?])) + # Treat as bundled short options + for ((_i = 1; _i < ${#_option}; _i++)); do + _char=${_option:_i:1} + [[ $_char != '[' ]] && options+=("-$_char") + done + ;; + *) + _comp_compgen_help__parse "$_option" + ;; + esac + _line=${_line#*"$_match"} + done + done + ((${#options[@]})) || return 1 + + _comp_compgen -U options -- -W '"${options[@]}"' + return 0 +} + +# This function completes on signal names (minus the SIG prefix) +# @param $1 prefix +# +# @since 2.12 +_comp_compgen_signals() +{ + local -a sigs + _comp_compgen -v sigs -c "SIG${cur#"${1-}"}" -- -A signal && + _comp_compgen -RU sigs -- -P "${1-}" -W '"${sigs[@]#SIG}"' +} + +# This function completes on known mac addresses +# +# @since 2.12 +_comp_compgen_mac_addresses() +{ + local _re='\([A-Fa-f0-9]\{2\}:\)\{5\}[A-Fa-f0-9]\{2\}' + local PATH="$PATH:/sbin:/usr/sbin" + local -a addresses + + # Local interfaces + # - ifconfig on Linux: HWaddr or ether + # - ifconfig on FreeBSD: ether + # - ip link: link/ether + _comp_compgen -v addresses split -- "$( + { + ip -c=never link show || ip link show || LC_ALL=C ifconfig -a + } 2>/dev/null | command sed -ne \ + "s/.*[[:space:]]HWaddr[[:space:]]\{1,\}\($_re\)[[:space:]].*/\1/p" -ne \ + "s/.*[[:space:]]HWaddr[[:space:]]\{1,\}\($_re\)[[:space:]]*$/\1/p" -ne \ + "s|.*[[:space:]]\(link/\)\{0,1\}ether[[:space:]]\{1,\}\($_re\)[[:space:]].*|\2|p" -ne \ + "s|.*[[:space:]]\(link/\)\{0,1\}ether[[:space:]]\{1,\}\($_re\)[[:space:]]*$|\2|p" + )" + + # ARP cache + _comp_compgen -av addresses split -- "$( + { + arp -an || ip -c=never neigh show || ip neigh show + } 2>/dev/null | command sed -ne \ + "s/.*[[:space:]]\($_re\)[[:space:]].*/\1/p" -ne \ + "s/.*[[:space:]]\($_re\)[[:space:]]*$/\1/p" + )" + + # /etc/ethers + _comp_compgen -av addresses split -- "$(command sed -ne \ + "s/^[[:space:]]*\($_re\)[[:space:]].*/\1/p" /etc/ethers 2>/dev/null)" + + _comp_compgen -U addresses ltrim_colon "${addresses[@]}" +} + +# This function completes on configured network interfaces +# +# @since 2.12 +_comp_compgen_configured_interfaces() +{ + local -a files + if [[ -f /etc/debian_version ]]; then + # Debian system + _comp_expand_glob files '/etc/network/interfaces /etc/network/interfaces.d/*' || return 0 + _comp_compgen -U files split -- "$(command sed -ne \ + 's|^iface \([^ ]\{1,\}\).*$|\1|p' "${files[@]}" 2>/dev/null)" + elif [[ -f /etc/SuSE-release ]]; then + # SuSE system + _comp_expand_glob files '/etc/sysconfig/network/ifcfg-*' || return 0 + _comp_compgen -U files split -- "$(printf '%s\n' "${files[@]}" | + command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" + elif [[ -f /etc/pld-release ]]; then + # PLD Linux + _comp_compgen -U files split -- "$(command ls -B /etc/sysconfig/interfaces | + command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" + else + # Assume Red Hat + _comp_expand_glob files '/etc/sysconfig/network-scripts/ifcfg-*' || return 0 + _comp_compgen -U files split -- "$(printf '%s\n' "${files[@]}" | + command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" + fi +} + +# Local IP addresses. +# If producing IPv6 completions, `_comp_initialize` with `-n :`. +# +# -4: IPv4 addresses only (default) +# -6: IPv6 addresses only +# -a: All addresses +# +# @since 2.12 +_comp_compgen_ip_addresses() +{ + local _n + case ${1-} in + -a) _n='6\{0,1\}' ;; + -6) _n='6' ;; + *) _n= ;; + esac + local PATH=$PATH:/sbin + local addrs + _comp_compgen -v addrs split -- "$({ + ip -c=never addr show || ip addr show || LC_ALL=C ifconfig -a + } 2>/dev/null | + command sed -e 's/[[:space:]]addr:/ /' -ne \ + "s|.*inet${_n}[[:space:]]\{1,\}\([^[:space:]/]*\).*|\1|p")" || + return + + if [[ ! $_n ]]; then + _comp_compgen -U addrs set "${addrs[@]}" + else + _comp_compgen -U addrs ltrim_colon "${addrs[@]}" + fi +} + +# This function completes on available kernel versions +# +# @since 2.12 +_comp_compgen_kernel_versions() +{ + _comp_compgen_split -- "$(command ls /lib/modules)" +} + +# This function completes on all available network interfaces +# -a: restrict to active interfaces only +# -w: restrict to wireless interfaces only +# +# @since 2.12 +_comp_compgen_available_interfaces() +{ + local PATH=$PATH:/sbin + local generated + _comp_compgen -v generated split -- "$({ + if [[ ${1-} == -w ]]; then + iwconfig + elif [[ ${1-} == -a ]]; then + # Note: we prefer ip (iproute2) to ifconfig (inetutils) since long + # interface names will be truncated by ifconfig [1]. + # [1]: https://github.com/scop/bash-completion/issues/1089 + ip -c=never link show up || ip link show up || ifconfig + else + ip -c=never link show || ip link show || ifconfig -a + fi + } 2>/dev/null | _comp_awk \ + '/^[^ \t]/ { if ($1 ~ /^[0-9]+:/) { print $2 } else { print $1 } }')" && + _comp_compgen -U generated set "${generated[@]%:}" +} + +# Echo number of CPUs, falling back to 1 on failure. +# @var[out] REPLY +# @return 0 if it successfully obtained the number of CPUs, or otherwise 1 +# @since 2.12 +_comp_get_ncpus() +{ + local var=NPROCESSORS_ONLN + [[ $OSTYPE == *@(linux|msys|cygwin)* ]] && var=_$var + if REPLY=$(getconf $var 2>/dev/null) && ((REPLY >= 1)); then + return 0 + else + REPLY=1 + return 1 + fi +} + +# Perform tilde (~) completion +# @return False (1) if completion needs further processing, +# True (0) if tilde is followed by a valid username, completions are +# put in COMPREPLY and no further processing is necessary. +# @since 2.12 +_comp_compgen_tilde() +{ + if [[ ${cur-} == \~* && $cur != */* ]]; then + # Try generate ~username completions + if _comp_compgen -c "${cur#\~}" -- -P '~' -u; then + # 2>/dev/null for direct invocation, e.g. in the + # _comp_compgen_tilde unit test + compopt -o filenames 2>/dev/null + return 0 + fi + fi + return 1 +} + +# Expand string starting with tilde (~) +# We want to expand ~foo/... to /home/foo/... to avoid problems when +# word-to-complete starting with a tilde is fed to commands and ending up +# quoted instead of expanded. +# Only the first portion of the variable from the tilde up to the first slash +# (~../) is expanded. The remainder of the variable, containing for example +# a dollar sign variable ($) or asterisk (*) is not expanded. +# Example usage: +# +# $ _comp_expand_tilde "~"; echo "$REPLY" +# +# Example output: +# +# $1 REPLY +# -------- ---------------- +# ~ /home/user +# ~foo/bar /home/foo/bar +# ~foo/$HOME /home/foo/$HOME +# ~foo/a b /home/foo/a b +# ~foo/* /home/foo/* +# +# @param $1 Value to expand +# @var[out] REPLY Expanded result +# @since 2.12 +_comp_expand_tilde() +{ + REPLY=$1 + if [[ $1 == \~* ]]; then + printf -v REPLY '~%q' "${1#\~}" + eval "REPLY=$REPLY" + fi +} + +# This function expands tildes in pathnames +# +# @since 2.12 +_comp_expand() +{ + # Expand ~username type directory specifications. We want to expand + # ~foo/... to /home/foo/... to avoid problems when $cur starting with + # a tilde is fed to commands and ending up quoted instead of expanded. + + case ${cur-} in + ~*/*) + local REPLY + _comp_expand_tilde "$cur" + cur=$REPLY + ;; + ~*) + _comp_compgen -v COMPREPLY tilde && + eval "COMPREPLY[0]=$(printf ~%q "${COMPREPLY[0]#\~}")" && + return 1 + ;; + esac + return 0 +} + +# Process ID related functions. +# for AIX and Solaris we use X/Open syntax, BSD for others. +# +# @since 2.12 +if [[ $OSTYPE == *@(solaris|aix)* ]]; then + # This function completes on process IDs. + _comp_compgen_pids() + { + _comp_compgen_split -- "$(command ps -efo pid | command sed 1d)" + } + + _comp_compgen_pgids() + { + _comp_compgen_split -- "$(command ps -efo pgid | command sed 1d)" + } + _comp_compgen_pnames() + { + _comp_compgen_split -X '' -- "$(command ps -efo comm | + command sed -e 1d -e 's:.*/::' -e 's/^-//' | sort -u)" + } +else + _comp_compgen_pids() + { + _comp_compgen_split -- "$(command ps ax -o pid=)" + } + _comp_compgen_pgids() + { + _comp_compgen_split -- "$(command ps ax -o pgid=)" + } + # @param $1 if -s, don't try to avoid truncated command names + _comp_compgen_pnames() + { + local -a procs=() + if [[ ${1-} == -s ]]; then + _comp_split procs "$(command ps ax -o comm | command sed -e 1d)" + else + # Some versions of ps don't support "command", but do "comm", e.g. + # some busybox ones. Fall back + local -a psout + _comp_split -l psout "$({ + command ps ax -o command= || command ps ax -o comm= + } 2>/dev/null)" + local line i=-1 + for line in "${psout[@]}"; do + if ((i == -1)); then + # First line, see if it has COMMAND column header. For + # example some busybox ps versions do that, i.e. don't + # respect command= + if [[ $line =~ ^(.*[[:space:]])COMMAND([[:space:]]|$) ]]; then + # It does; store its index. + i=${#BASH_REMATCH[1]} + else + # Nope, fall through to "regular axo command=" parsing. + break + fi + else + # + line=${line:i} # take command starting from found index + line=${line%% *} # trim arguments + [[ $line ]] && procs+=("$line") + fi + done + if ((i == -1)); then + # Regular command= parsing + for line in "${psout[@]}"; do + if [[ $line =~ ^[[(](.+)[])]$ ]]; then + procs+=("${BASH_REMATCH[1]}") + else + line=${line%% *} # trim arguments + line=${line##@(*/|-)} # trim leading path and - + [[ $line ]] && procs+=("$line") + fi + done + fi + fi + ((${#procs[@]})) && + _comp_compgen -U procs -- -X "" -W '"${procs[@]}"' + } +fi + +# This function completes on user IDs +# +# @since 2.12 +_comp_compgen_uids() +{ + if type getent &>/dev/null; then + _comp_compgen_split -- "$(getent passwd | cut -d: -f3)" + elif type perl &>/dev/null; then + _comp_compgen_split -- "$(perl -e 'while (($uid) = (getpwent)[2]) { print $uid . "\n" }')" + else + # make do with /etc/passwd + _comp_compgen_split -- "$(cut -d: -f3 /etc/passwd)" + fi +} + +# This function completes on group IDs +# +# @since 2.12 +_comp_compgen_gids() +{ + if type getent &>/dev/null; then + _comp_compgen_split -- "$(getent group | cut -d: -f3)" + elif type perl &>/dev/null; then + _comp_compgen_split -- "$(perl -e 'while (($gid) = (getgrent)[2]) { print $gid . "\n" }')" + else + # make do with /etc/group + _comp_compgen_split -- "$(cut -d: -f3 /etc/group)" + fi +} + +# Glob for matching various backup files. +# +_comp_backup_glob='@(#*#|*@(~|.@(bak|orig|rej|swp|@(dpkg|ucf)-*|rpm@(orig|new|save))))' + +# Complete on xinetd services +# +# @since 2.12 +_comp_compgen_xinetd_services() +{ + local xinetddir=${_comp__test_xinetd_dir:-/etc/xinetd.d} + if [[ -d $xinetddir ]]; then + local -a svcs + if _comp_expand_glob svcs '$xinetddir/!($_comp_backup_glob)'; then + _comp_compgen -U svcs -U xinetddir -- -W '"${svcs[@]#$xinetddir/}"' + fi + fi +} + +# This function completes on services +# +# @since 2.12 +_comp_compgen_services() +{ + local sysvdirs + _comp_sysvdirs || return 1 + + local services + _comp_expand_glob services '${sysvdirs[0]}/!($_comp_backup_glob|functions|README)' + + local _generated=$({ + systemctl list-units --full --all || + systemctl list-unit-files + } 2>/dev/null | + _comp_awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }') + _comp_split -la services "$_generated" + + if [[ -x /sbin/upstart-udev-bridge ]]; then + _comp_split -la services "$(initctl list 2>/dev/null | cut -d' ' -f1)" + fi + + ((${#services[@]})) || return 1 + _comp_compgen -U services -U sysvdirs -- -W '"${services[@]#${sysvdirs[0]}/}"' +} + +# This completes on a list of all available service scripts for the +# 'service' command and/or the SysV init.d directory, followed by +# that script's available commands +# This function is in the main bash_completion file rather than in a separate +# one, because we set it up eagerly as completer for scripts in sysv init dirs +# below. +# +# @since 2.12 +_comp_complete_service() +{ + local cur prev words cword comp_args + _comp_initialize -- "$@" || return + + # don't complete past 2nd token + ((cword > 2)) && return + + if [[ $cword -eq 1 && $prev == ?(*/)service ]]; then + _comp_compgen_services + [[ -e /etc/mandrake-release ]] && _comp_compgen_xinetd_services + else + local sysvdirs + _comp_sysvdirs || return 1 + _comp_compgen_split -l -- "$(command sed -e 'y/|/ /' \ + -ne 's/^.*\(U\|msg_u\)sage.*{\(.*\)}.*$/\2/p' \ + "${sysvdirs[0]}/${prev##*/}" 2>/dev/null) start stop" + fi +} && + complete -F _comp_complete_service service + +_comp__init_set_up_service_completions() +{ + local sysvdirs svc svcdir svcs + _comp_sysvdirs && + for svcdir in "${sysvdirs[@]}"; do + if _comp_expand_glob svcs '"$svcdir"/!($_comp_backup_glob)'; then + for svc in "${svcs[@]}"; do + [[ -x $svc ]] && complete -F _comp_complete_service "$svc" + done + fi + done + unset -f "$FUNCNAME" +} +_comp__init_set_up_service_completions + +# This function completes on kernel modules +# @param $1 kernel version +# +# @since 2.12 +_comp_compgen_kernel_modules() +{ + local _modpath=/lib/modules/$1 + _comp_compgen_split -- "$(command ls -RL "$_modpath" 2>/dev/null | + command sed -ne 's/^\(.*\)\.k\{0,1\}o\(\.[gx]z\)\{0,1\}$/\1/p' \ + -e 's/^\(.*\)\.ko\.zst$/\1/p')" +} + +# This function completes on inserted kernel modules +# @param $1 prefix to filter with, default $cur +# +# @since 2.12 +_comp_compgen_inserted_kernel_modules() +{ + _comp_compgen -c "${1:-$cur}" split -- "$(PATH="$PATH:/sbin" lsmod | + _comp_awk '{if (NR != 1) print $1}')" +} + +# This function completes on user or user:group format; as for chown and cpio. +# +# The : must be added manually; it will only complete usernames initially. +# The legacy user.group format is not supported. +# +# @param $1 If -u, only return users/groups the user has access to in +# context of current completion. +# +# @since 2.12 +_comp_compgen_usergroups() +{ + if [[ $cur == *\\\\* || $cur == *:*:* ]]; then + # Give up early on if something seems horribly wrong. + return + elif [[ $cur == *\\:* ]]; then + # Completing group after 'user\:gr'. + # Reply with a list of groups prefixed with 'user:', readline will + # escape to the colon. + local tmp + if [[ ${1-} == -u ]]; then + _comp_compgen -v tmp -c "${cur#*:}" allowed_groups + else + _comp_compgen -v tmp -c "${cur#*:}" -- -g + fi + if ((${#tmp[@]})); then + local _prefix=${cur%%*([^:])} + _prefix=${_prefix//\\/} + _comp_compgen -Rv tmp -- -P "$_prefix" -W '"${tmp[@]}"' + _comp_compgen -U tmp set "${tmp[@]}" + fi + elif [[ $cur == *:* ]]; then + # Completing group after 'user:gr'. + # Reply with a list of unprefixed groups since readline with split on : + # and only replace the 'gr' part + if [[ ${1-} == -u ]]; then + _comp_compgen -c "${cur#*:}" allowed_groups + else + _comp_compgen -c "${cur#*:}" -- -g + fi + else + # Completing a partial 'usernam'. + # + # Don't suffix with a : because readline will escape it and add a + # slash. It's better to complete into 'chown username ' than 'chown + # username\:'. + if [[ ${1-} == -u ]]; then + _comp_compgen_allowed_users + else + _comp_compgen -- -u + fi + fi +} + +# @since 2.12 +_comp_compgen_allowed_users() +{ + if _comp_as_root; then + _comp_compgen -- -u + else + _comp_compgen_split -- "$(id -un 2>/dev/null || whoami 2>/dev/null)" + fi +} + +# @since 2.12 +_comp_compgen_allowed_groups() +{ + if _comp_as_root; then + _comp_compgen -- -g + else + _comp_compgen_split -- "$(id -Gn 2>/dev/null || groups 2>/dev/null)" + fi +} + +# @since 2.12 +_comp_compgen_selinux_users() +{ + _comp_compgen_split -- "$(semanage user -nl 2>/dev/null | + _comp_awk '{ print $1 }')" +} + +# This function completes on valid shells +# @param $1 chroot to search from +# +# @since 2.12 +_comp_compgen_shells() +{ + local -a shells=() + local _shell _rest + while read -r _shell _rest; do + [[ $_shell == /* ]] && shells+=("$_shell") + done 2>/dev/null <"${1-}"/etc/shells + _comp_compgen -U shells -- -W '"${shells[@]}"' +} + +# This function completes on valid filesystem types +# +# @since 2.12 +_comp_compgen_fstypes() +{ + local _fss + + if [[ -e /proc/filesystems ]]; then + # Linux + _fss="$(cut -d$'\t' -f2 /proc/filesystems) + $(_comp_awk '! /\*/ { print $NF }' /etc/filesystems 2>/dev/null)" + else + # Generic + _fss="$(_comp_awk '/^[ \t]*[^#]/ { print $3 }' /etc/fstab 2>/dev/null) + $(_comp_awk '/^[ \t]*[^#]/ { print $3 }' /etc/mnttab 2>/dev/null) + $(_comp_awk '/^[ \t]*[^#]/ { print $4 }' /etc/vfstab 2>/dev/null) + $(_comp_awk '{ print $1 }' /etc/dfs/fstypes 2>/dev/null) + $(lsvfs 2>/dev/null | _comp_awk '$1 !~ /^(Filesystem|[^a-zA-Z])/ { print $1 }') + $([[ -d /etc/fs ]] && command ls /etc/fs)" + fi + + [[ $_fss ]] && _comp_compgen_split -- "$_fss" +} + +# Get absolute path to a file, with rudimentary canonicalization. +# No symlink resolution or existence checks are done; +# see `_comp_realcommand` for those. +# @param $1 The file +# @var[out] REPLY The path +# @since 2.12 +_comp_abspath() +{ + REPLY=$1 + [[ $REPLY == /* ]] || REPLY=$PWD/$REPLY + REPLY=${REPLY//+(\/)/\/} + while true; do + # Process "." and "..". To avoid reducing "/../../ => /", we convert + # "/*/../" one by one. "/.." at the beginning is ignored. Then, /*/../ + # in the middle is processed. Finally, /*/.. at the end is removed. + case $REPLY in + */./*) REPLY=${REPLY//\/.\//\/} ;; + */.) REPLY=${REPLY%/.} ;; + /..?(/*)) REPLY=${REPLY#/..} ;; + */+([^/])/../*) REPLY=${REPLY/\/+([^\/])\/..\//\/} ;; + */+([^/])/..) REPLY=${REPLY%/+([^/])/..} ;; + *) break ;; + esac + done + [[ $REPLY ]] || REPLY=/ +} + +# Get real command. +# Command is the filename of command in PATH with possible symlinks resolved +# (if resolve tooling available), empty string if command not found. +# @param $1 Command +# @var[out] REPLY Resulting string +# @return True (0) if command found, False (> 0) if not. +# @since 2.12 +_comp_realcommand() +{ + REPLY="" + local file + file=$(type -P -- "$1") || return $? + if type -p realpath >/dev/null; then + REPLY=$(realpath "$file") + elif type -p greadlink >/dev/null; then + REPLY=$(greadlink -f "$file") + elif type -p readlink >/dev/null; then + REPLY=$(readlink -f "$file") + else + _comp_abspath "$file" + fi +} + +# This function returns the position of the first argument, excluding options +# +# Options: +# -a GLOB Pattern of options that take an option argument +# +# @var[out] REPLY Position of the first argument before the current one being +# completed if any, or otherwise an empty string +# @return True (0) if any argument is found, False (> 0) otherwise. +# @since 2.12 +_comp_locate_first_arg() +{ + local has_optarg="" + local OPTIND=1 OPTARG="" OPTERR=0 _opt + while getopts ':a:' _opt "$@"; do + case $_opt in + a) has_optarg=$OPTARG ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 2 + ;; + esac + done + shift "$((OPTIND - 1))" + + local i + REPLY= + for ((i = 1; i < cword; i++)); do + # shellcheck disable=SC2053 + if [[ $has_optarg && ${words[i]} == $has_optarg ]]; then + ((i++)) + elif [[ ${words[i]} != -?* ]]; then + REPLY=$i + return 0 + elif [[ ${words[i]} == -- ]]; then + ((i + 1 < cword)) && REPLY=$((i + 1)) && return 0 + break + fi + done + return 1 +} + +# This function returns the first argument, excluding options +# +# Options: +# -a GLOB Pattern of options that take an option argument +# +# @var[out] REPLY First argument before the current one being completed if any, +# or otherwise an empty string +# @return True (0) if any argument is found, False (> 0) otherwise. +# @since 2.12 +_comp_get_first_arg() +{ + _comp_locate_first_arg "$@" && REPLY=${words[REPLY]} +} + +# This function counts the number of args, excluding options +# +# Options: +# -n CHARS Characters out of $COMP_WORDBREAKS which should +# NOT be considered word breaks. See +# _comp__reassemble_words. +# -a GLOB Options whose following argument should not be counted +# -i GLOB Options that should be counted as args +# +# @var[out] REPLY Return the number of arguments +# @since 2.12 +_comp_count_args() +{ + local has_optarg="" has_exclude="" exclude="" glob_include="" + local OPTIND=1 OPTARG="" OPTERR=0 _opt + while getopts ':a:n:i:' _opt "$@"; do + case $_opt in + a) has_optarg=$OPTARG ;; + n) has_exclude=set exclude+=$OPTARG ;; + i) glob_include=$OPTARG ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 2 + ;; + esac + done + shift "$((OPTIND - 1))" + + if [[ $has_exclude ]]; then + local cword words + _comp__reassemble_words "$exclude<>&" words cword + fi + + local i + REPLY=1 + for ((i = 1; i < cword; i++)); do + # shellcheck disable=SC2053 + if [[ $has_optarg && ${words[i]} == $has_optarg ]]; then + ((i++)) + elif [[ ${words[i]} != -?* || $glob_include && ${words[i]} == $glob_include ]]; then + ((REPLY++)) + elif [[ ${words[i]} == -- ]]; then + ((REPLY += cword - i - 1)) + break + fi + done +} + +# This function completes on PCI IDs +# +# @since 2.12 +_comp_compgen_pci_ids() +{ + _comp_compgen_split -- "$(PATH="$PATH:/sbin" lspci -n | _comp_awk '{print $3}')" +} + +# This function completes on USB IDs +# +# @since 2.12 +_comp_compgen_usb_ids() +{ + _comp_compgen_split -- "$(PATH="$PATH:/sbin" lsusb | _comp_awk '{print $6}')" +} + +# CD device names +# +# @since 2.12 +_comp_compgen_cd_devices() +{ + _comp_compgen -c "${cur:-/dev/}" -- -f -d -X "!*/?([amrs])cd!(c-*)" +} + +# DVD device names +# +# @since 2.12 +_comp_compgen_dvd_devices() +{ + _comp_compgen -c "${cur:-/dev/}" -- -f -d -X "!*/?(r)dvd*" +} + +# TERM environment variable values +# +# @since 2.12 +_comp_compgen_terms() +{ + _comp_compgen_split -- "$({ + command sed -ne 's/^\([^[:space:]#|]\{2,\}\)|.*/\1/p' /etc/termcap + { + toe -a || toe + } | _comp_awk '{ print $1 }' + _comp_expand_glob dirs '/{etc,lib,usr/lib,usr/share}/terminfo/?' && + find "${dirs[@]}" -type f -maxdepth 1 | + _comp_awk -F / '{ print $NF }' + } 2>/dev/null)" +} + +# @since 2.12 +_comp_try_faketty() +{ + if type unbuffer &>/dev/null; then + unbuffer -p "$@" + elif script --version 2>&1 | command grep -qF util-linux; then + # BSD and Solaris "script" do not seem to have required features + script -qaefc "$*" /dev/null + else + "$@" # no can do, fallback + fi +} + +# a little help for FreeBSD ports users +[[ $OSTYPE == *freebsd* ]] && complete -W 'index search fetch fetch-list + extract patch configure build install reinstall deinstall clean + clean-depends kernel buildworld' make + +# This function provides simple user@host completion +# +# @since 2.12 +_comp_complete_user_at_host() +{ + local cur prev words cword comp_args + _comp_initialize -n : -- "$@" || return + + if [[ $cur == *@* ]]; then + _comp_compgen_known_hosts "$cur" + else + _comp_compgen -- -u -S @ + compopt -o nospace + fi +} +shopt -u hostcomplete && complete -F _comp_complete_user_at_host talk ytalk finger + +# NOTE: Using this function as a helper function is deprecated. Use +# `_comp_compgen_known_hosts' instead. +# @since 2.12 +_comp_complete_known_hosts() +{ + local cur prev words cword comp_args + _comp_initialize -n : -- "$@" || return + + # NOTE: Using `_known_hosts' (the old name of `_comp_complete_known_hosts') + # as a helper function and passing options to `_known_hosts' is + # deprecated: Use `_comp_compgen_known_hosts' instead. + local -a options=() + [[ ${1-} == -a || ${2-} == -a ]] && options+=(-a) + [[ ${1-} == -c || ${2-} == -c ]] && options+=(-c) + local IFS=$' \t\n' # Workaround for connected ${v+"$@"} in bash < 4.4 + _comp_compgen_known_hosts ${options[@]+"${options[@]}"} -- "$cur" +} + +# Helper function to locate ssh included files in configs +# This function looks for the "Include" keyword in ssh config files and +# includes them recursively, adding each result to the config variable. +_comp__included_ssh_config_files() +{ + (($# < 1)) && + echo "bash_completion: $FUNCNAME: missing mandatory argument CONFIG" >&2 + local configfile i files f REPLY + configfile=$1 + + # From man ssh_config: + # "Files without absolute paths are assumed to be in ~/.ssh if included + # in a user configuration file or /etc/ssh if included from the system + # configuration file." + # This behavior is not affected by the the including file location - + # if the system configuration file is included from the user's config, + # relative includes are still resolved in the user's ssh config directory. + local relative_include_base + if [[ $configfile == /etc/ssh* ]]; then + relative_include_base="/etc/ssh" + else + relative_include_base="$HOME/.ssh" + fi + + local depth=1 + local -a included + local -a include_files + included=("$configfile") + + # Max recursion depth per openssh's READCONF_MAX_DEPTH: + # https://github.com/openssh/openssh-portable/blob/5ec5504f1d328d5bfa64280cd617c3efec4f78f3/readconf.c#L2240 + local max_depth=16 + while ((${#included[@]} > 0 && depth++ < max_depth)); do + _comp_split include_files "$(command sed -ne 's/^[[:blank:]]*[Ii][Nn][Cc][Ll][Uu][Dd][Ee][[:blank:]]\(.*\)$/\1/p' "${included[@]}")" || return + included=() + for i in "${include_files[@]}"; do + if [[ $i != [~/]* ]]; then + i="${relative_include_base}/${i}" + fi + _comp_expand_tilde "$i" + if _comp_expand_glob files '$REPLY'; then + # In case the expanded variable contains multiple paths + for f in "${files[@]}"; do + if [[ -r $f && ! -d $f ]]; then + config+=("$f") + included+=("$f") + fi + done + fi + done + done +} + +# Helper function for completing _comp_complete_known_hosts. +# This function performs host completion based on ssh's config and known_hosts +# files, as well as hostnames reported by avahi-browse if +# BASH_COMPLETION_KNOWN_HOSTS_WITH_AVAHI is set to a non-empty value. +# Also hosts from HOSTFILE (compgen -A hostname) are added, unless +# BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE is set to an empty value. +# Usage: _comp_compgen_known_hosts [OPTIONS] CWORD +# Options: +# -a Use aliases from ssh config files +# -c Use `:' suffix +# -F configfile Use `configfile' for configuration settings +# -p PREFIX Use PREFIX +# -4 Filter IPv6 addresses from results +# -6 Filter IPv4 addresses from results +# @var[out] COMPREPLY Completions, starting with CWORD, are added +# @return True (0) if one or more completions are generated, or otherwise False +# (1). +# @since 2.12 +_comp_compgen_known_hosts() +{ + local known_hosts + _comp_compgen_known_hosts__impl "$@" || return "$?" + _comp_compgen -U known_hosts set "${known_hosts[@]}" +} +_comp_compgen_known_hosts__impl() +{ + known_hosts=() + + local configfile="" flag prefix="" + local cur suffix="" aliases="" i host ipv4="" ipv6="" + local -a kh tmpkh=() khd=() config=() + + # TODO remove trailing %foo from entries + + local OPTIND=1 + while getopts "ac46F:p:" flag "$@"; do + case $flag in + a) aliases=set ;; + c) suffix=':' ;; + F) + if [[ ! $OPTARG ]]; then + echo "bash_completion: $FUNCNAME: -F: an empty filename is specified" >&2 + return 2 + fi + configfile=$OPTARG + ;; + p) prefix=$OPTARG ;; + 4) ipv4=set ;; + 6) ipv6=set ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 2 + ;; + esac + done + if (($# < OPTIND)); then + echo "bash_completion: $FUNCNAME: missing mandatory argument CWORD" >&2 + return 2 + fi + cur=${!OPTIND} + ((OPTIND += 1)) + if (($# >= OPTIND)); then + echo "bash_completion: $FUNCNAME($*): unprocessed arguments:" \ + "$(while (($# >= OPTIND)); do + printf '%s ' ${!OPTIND} + shift + done)" >&2 + return 2 + fi + + [[ $cur == *@* ]] && prefix=$prefix${cur%@*}@ && cur=${cur#*@} + kh=() + + # ssh config files + if [[ $configfile ]]; then + [[ -r $configfile && ! -d $configfile ]] && config+=("$configfile") + else + for i in /etc/ssh/ssh_config ~/.ssh/config ~/.ssh2/config; do + [[ -r $i && ! -d $i ]] && config+=("$i") + done + fi + + # "Include" keyword in ssh config files + if ((${#config[@]} > 0)); then + for i in "${config[@]}"; do + _comp__included_ssh_config_files "$i" + done + fi + + # Known hosts files from configs + if ((${#config[@]} > 0)); then + # expand paths (if present) to global and user known hosts files + # TODO(?): try to make known hosts files with more than one consecutive + # spaces in their name work (watch out for ~ expansion + # breakage! Alioth#311595) + if _comp_split -l tmpkh "$(_comp_awk 'sub("^[ \t]*([Gg][Ll][Oo][Bb][Aa][Ll]|[Uu][Ss][Ee][Rr])[Kk][Nn][Oo][Ww][Nn][Hh][Oo][Ss][Tt][Ss][Ff][Ii][Ll][Ee][ \t=]+", "") { print $0 }' "${config[@]}" | sort -u)"; then + local tmpkh2 j REPLY + for i in "${tmpkh[@]}"; do + # First deal with quoted entries... + while [[ $i =~ ^([^\"]*)\"([^\"]*)\"(.*)$ ]]; do + i=${BASH_REMATCH[1]}${BASH_REMATCH[3]} + _comp_expand_tilde "${BASH_REMATCH[2]}" # Eval/expand possible `~' or `~user' + [[ -r $REPLY ]] && kh+=("$REPLY") + done + # ...and then the rest. + _comp_split tmpkh2 "$i" || continue + for j in "${tmpkh2[@]}"; do + _comp_expand_tilde "$j" # Eval/expand possible `~' or `~user' + [[ -r $REPLY ]] && kh+=("$REPLY") + done + done + fi + fi + + if [[ ! $configfile ]]; then + # Global and user known_hosts files + for i in /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2 \ + /etc/known_hosts /etc/known_hosts2 ~/.ssh/known_hosts \ + ~/.ssh/known_hosts2; do + [[ -r $i && ! -d $i ]] && kh+=("$i") + done + for i in /etc/ssh2/knownhosts ~/.ssh2/hostkeys; do + [[ -d $i ]] || continue + _comp_expand_glob tmpkh '"$i"/*.pub' && khd+=("${tmpkh[@]}") + done + fi + + # If we have known_hosts files to use + if ((${#kh[@]} + ${#khd[@]} > 0)); then + if ((${#kh[@]} > 0)); then + # https://man.openbsd.org/sshd.8#SSH_KNOWN_HOSTS_FILE_FORMAT + for i in "${kh[@]}"; do + while read -ra tmpkh; do + ((${#tmpkh[@]} == 0)) && continue + # Skip entries starting with | (hashed) and # (comment) + [[ ${tmpkh[0]} == [\|\#]* ]] && continue + # Ignore leading @foo (markers) + local host_list=${tmpkh[0]} + [[ ${tmpkh[0]} == @* ]] && host_list=${tmpkh[1]-} + # Split entry on commas + local -a hosts + if _comp_split -F , hosts "$host_list"; then + for host in "${hosts[@]}"; do + # Skip hosts containing wildcards + [[ $host == *[*?]* ]] && continue + # Remove leading [ + host=${host#[} + # Remove trailing ] + optional :port + host=${host%]?(:+([0-9]))} + # Add host to candidates + [[ $host ]] && known_hosts+=("$host") + done + fi + done <"$i" + done + fi + if ((${#khd[@]} > 0)); then + # Needs to look for files called + # .../.ssh2/key_22_.pub + # dont fork any processes, because in a cluster environment, + # there can be hundreds of hostkeys + for i in "${khd[@]}"; do + if [[ $i == *key_22_*.pub && -r $i ]]; then + host=${i/#*key_22_/} + host=${host/%.pub/} + [[ $host ]] && known_hosts+=("$host") + fi + done + fi + + # apply suffix and prefix + ((${#known_hosts[@]})) && + _comp_compgen -v known_hosts -- -W '"${known_hosts[@]}"' -P "$prefix" -S "$suffix" + fi + + # append any available aliases from ssh config files + if [[ ${#config[@]} -gt 0 && $aliases ]]; then + local -a hosts + if _comp_split hosts "$(command sed -ne 's/^[[:blank:]]*[Hh][Oo][Ss][Tt][[:blank:]=]\{1,\}\(.*\)$/\1/p' "${config[@]}")"; then + _comp_compgen -av known_hosts -- -P "$prefix" \ + -S "$suffix" -W '"${hosts[@]%%[*?%]*}"' -X '@(\!*|)' + fi + fi + + # Add hosts reported by avahi-browse, if desired and it's available. + if [[ ${BASH_COMPLETION_KNOWN_HOSTS_WITH_AVAHI-} ]] && + type avahi-browse &>/dev/null; then + # Some old versions of avahi-browse reportedly didn't have -k + # (even if mentioned in the manpage); those we do not support any more. + local generated=$(avahi-browse -cprak 2>/dev/null | _comp_awk -F ';' \ + '/^=/ && $5 ~ /^_(ssh|workstation)\._tcp$/ { print $7 }' | + sort -u) + _comp_compgen -av known_hosts -- -P "$prefix" -S "$suffix" -W '$generated' + fi + + # Add hosts reported by ruptime. + if type ruptime &>/dev/null; then + local generated=$(ruptime 2>/dev/null | _comp_awk '!/^ruptime:/ { print $1 }') + _comp_compgen -av known_hosts -- -W '$generated' + fi + + # Add results of normal hostname completion, unless + # `BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE' is set to an empty value. + if [[ ${BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE-set} ]]; then + _comp_compgen -av known_hosts -- -A hostname -P "$prefix" -S "$suffix" + fi + + ((${#known_hosts[@]})) || return 1 + + if [[ $ipv4 ]]; then + known_hosts=("${known_hosts[@]/*:*$suffix/}") + fi + if [[ $ipv6 ]]; then + known_hosts=("${known_hosts[@]/+([0-9]).+([0-9]).+([0-9]).+([0-9])$suffix/}") + fi + if [[ $ipv4 || $ipv6 ]]; then + for i in "${!known_hosts[@]}"; do + [[ ${known_hosts[i]} ]] || unset -v 'known_hosts[i]' + done + fi + ((${#known_hosts[@]})) || return 1 + + _comp_compgen -v known_hosts -c "$prefix$cur" ltrim_colon "${known_hosts[@]}" +} +complete -F _comp_complete_known_hosts traceroute traceroute6 \ + fping fping6 telnet rsh rlogin ftp dig drill ssh-installkeys showmount + +# Convert the word index in `words` to the index in `COMP_WORDS`. +# @param $1 Index in the array WORDS. +# @var[in,opt] words Words that contain reassmbled words. +# @var[in,opt] cword Current word index in WORDS. +# WORDS and CWORD, if any, are expected to be created by +# _comp__reassemble_words. +# +_comp__find_original_word() +{ + REPLY=$1 + + # If CWORD or WORDS are undefined, we return the first argument without any + # processing. + [[ -v cword && -v words ]] || return 0 + + local reassembled_offset=$1 i=0 j + for ((j = 0; j < reassembled_offset; j++)); do + local word=${words[j]} + while [[ $word && i -lt ${#COMP_WORDS[@]} && $word == *"${COMP_WORDS[i]}"* ]]; do + word=${word#*"${COMP_WORDS[i++]}"} + done + done + REPLY=$i +} +# A meta-command completion function for commands like sudo(8), which need to +# first complete on a command, then complete according to that command's own +# completion definition. +# +# @since 2.12 +_comp_command_offset() +{ + # rewrite current completion context before invoking + # actual command completion + + # obtain the word index in COMP_WORDS + local REPLY + _comp__find_original_word "$1" + local word_offset=$REPLY + + # make changes to COMP_* local. Note that bash-4.3..5.0 have a + # bug that `local -a arr=("${arr[@]}")` fails. We instead first + # assign the values of `COMP_WORDS` to another array `comp_words`. + local COMP_LINE=$COMP_LINE COMP_POINT=$COMP_POINT COMP_CWORD=$COMP_CWORD + local -a comp_words=("${COMP_WORDS[@]}") + local -a COMP_WORDS=("${comp_words[@]}") + + # find new first word position, then + # rewrite COMP_LINE and adjust COMP_POINT + local i tail + for ((i = 0; i < word_offset; i++)); do + tail=${COMP_LINE#*"${COMP_WORDS[i]}"} + ((COMP_POINT -= ${#COMP_LINE} - ${#tail})) + COMP_LINE=$tail + done + + # shift COMP_WORDS elements and adjust COMP_CWORD + COMP_WORDS=("${COMP_WORDS[@]:word_offset}") + ((COMP_CWORD -= word_offset)) + + COMPREPLY=() + local cur + _comp_get_words cur + + if ((COMP_CWORD == 0)); then + _comp_compgen_commands + else + _comp_dequote "${COMP_WORDS[0]}" || REPLY=${COMP_WORDS[0]} + local cmd=$REPLY compcmd=$REPLY + local cspec=$(complete -p -- "$cmd" 2>/dev/null) + + # If we have no completion for $cmd yet, see if we have for basename + if [[ ! $cspec && $cmd == */* ]]; then + cspec=$(complete -p -- "${cmd##*/}" 2>/dev/null) + [[ $cspec ]] && compcmd=${cmd##*/} + fi + # If still nothing, just load it for the basename + if [[ ! $cspec ]]; then + compcmd=${cmd##*/} + _comp_load -D -- "$compcmd" + cspec=$(complete -p -- "$compcmd" 2>/dev/null) + fi + + local retry_count=0 + while true; do # loop for the retry request by status 124 + local args original_cur=${comp_args[1]-$cur} + if ((${#COMP_WORDS[@]} >= 2)); then + args=("$cmd" "$original_cur" "${COMP_WORDS[-2]}") + else + args=("$cmd" "$original_cur") + fi + + if [[ ! $cspec ]]; then + if ((${#COMPREPLY[@]} == 0)); then + # XXX will probably never happen as long as completion loader loads + # *something* for every command thrown at it ($cspec != empty) + _comp_complete_minimal "${args[@]}" + fi + elif [[ $cspec == *\ -[CF]\ * ]]; then + if [[ $cspec == *' -F '* ]]; then + # complete -F + + # get function name + local func=${cspec#* -F } + func=${func%% *} + $func "${args[@]}" + + # restart completion (once) if function exited with 124 + if (($? == 124 && retry_count++ == 0)); then + # Note: When the completion function returns 124, the + # state of COMPREPLY is discarded. + COMPREPLY=() + + cspec=$(complete -p -- "$compcmd" 2>/dev/null) + + # Note: When completion spec is removed after 124, we + # do not generate any completions including the default + # ones. This is the behavior of the original Bash + # progcomp. + [[ $cspec ]] || break + + continue + fi + else + # complete -C + + # get command name + local completer=${cspec#* -C \'} + + # completer commands are always single-quoted + if ! _comp_dequote "'$completer"; then + _minimal "${args[@]}" + break + fi + completer=${REPLY[0]} + + local -a suggestions + + local IFS=$' \t\n' + local reset_monitor=$(shopt -po monitor) reset_lastpipe=$(shopt -p lastpipe) reset_noglob=$(shopt -po noglob) + set +o monitor + shopt -s lastpipe + set -o noglob + + COMP_KEY="$COMP_KEY" COMP_LINE="$COMP_LINE" \ + COMP_POINT="$COMP_POINT" COMP_TYPE="$COMP_TYPE" \ + $completer "${args[@]}" | mapfile -t suggestions + + $reset_monitor + $reset_lastpipe + $reset_noglob + _comp_unlocal IFS + + local suggestion + local i=0 + COMPREPLY=() + for suggestion in "${suggestions[@]}"; do + COMPREPLY[i]+=${COMPREPLY[i]+$'\n'}$suggestion + + if [[ $suggestion != *\\ ]]; then + ((i++)) + fi + done + fi + + # restore initial compopts + local opt + while [[ $cspec == *" -o "* ]]; do + # FIXME: should we take "+o opt" into account? + cspec=${cspec#*-o } + opt=${cspec%% *} + compopt -o "$opt" + cspec=${cspec#"$opt"} + done + else + cspec=${cspec#complete} + cspec=${cspec%%@("$compcmd"|"'${compcmd//\'/\'\\\'\'}'")} + eval "_comp_compgen -- $cspec" + fi + break + done + fi +} + +# A _comp_command_offset wrapper function for use when the offset is unknown. +# Only intended to be used as a completion function directly associated +# with a command, not to be invoked from within other completion functions. +# +# @since 2.12 +_comp_command() +{ + # We unset the shell variable `words` locally to tell + # `_comp_command_offset` that the index is intended to be that in + # `COMP_WORDS` instead of `words`. + local words + unset -v words + + local offset i + + # find actual offset, as position of the first non-option + offset=1 + for ((i = 1; i <= COMP_CWORD; i++)); do + if [[ ${COMP_WORDS[i]} != -* ]]; then + offset=$i + break + fi + done + _comp_command_offset $offset +} +complete -F _comp_command aoss command "do" else eval exec ltrace nice nohup padsp \ + "then" time tsocks vsound xargs + +# @since 2.12 +_comp_root_command() +{ + local PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin + local _comp_root_command=$1 + _comp_command +} +complete -F _comp_root_command fakeroot gksu gksudo kdesudo really + +# Return true if the completion should be treated as running as root +# +# @since 2.12 +_comp_as_root() +{ + [[ $EUID -eq 0 || ${_comp_root_command-} ]] +} + +# Complete on available commands, subject to `no_empty_cmd_completion`. +# @return True (0) if one or more completions are generated, or otherwise False +# (1). Note that it returns 1 even when the completion generation is canceled +# by `shopt -s no_empty_cmd_completion`. +# +# @since 2.12 +_comp_compgen_commands() +{ + [[ ! ${cur-} ]] && shopt -q no_empty_cmd_completion && return 1 + # -o filenames for e.g. spaces in paths to and in command names + _comp_compgen -- -c -o plusdirs && compopt -o filenames +} + +# @since 2.12 +_comp_complete_longopt() +{ + local cur prev words cword was_split comp_args + _comp_initialize -s -- "$@" || return + + case "${prev,,}" in + --help | --usage | --version) + return + ;; + --!(no-*)dir*) + _comp_compgen -a filedir -d + return + ;; + --!(no-*)@(file|path)*) + _comp_compgen -a filedir + return + ;; + --+([-a-z0-9_])) + local argtype=$(LC_ALL=C $1 --help 2>&1 | command sed -ne \ + "s|.*$prev\[\{0,1\}=[<[]\{0,1\}\([-A-Za-z0-9_]\{1,\}\).*|\1|p") + case ${argtype,,} in + *dir*) + _comp_compgen -a filedir -d + return + ;; + *file* | *path*) + _comp_compgen -a filedir + return + ;; + esac + ;; + esac + + [[ $was_split ]] && return + + if [[ $cur == -* ]]; then + _comp_compgen_split -- "$(LC_ALL=C $1 --help 2>&1 | + while read -r line; do + [[ $line =~ --[A-Za-z0-9]+([-_][A-Za-z0-9]+)*=? ]] && + printf '%s\n' "${BASH_REMATCH[0]}" + done)" + [[ ${COMPREPLY-} == *= ]] && compopt -o nospace + elif [[ $1 == *@(rmdir|chroot) ]]; then + _comp_compgen -a filedir -d + else + [[ $1 == *mkdir ]] && compopt -o nospace + _comp_compgen -a filedir + fi +} +# makeinfo and texi2dvi are defined elsewhere. +complete -F _comp_complete_longopt \ + a2ps awk base64 bash bc bison cat chroot colordiff cp \ + csplit cut date df diff dir du enscript expand fmt fold gperf \ + grep grub head irb ld ldd less ln ls m4 mkdir mkfifo mknod \ + mv netstat nl nm objcopy objdump od paste pr ptx readelf rm rmdir \ + sed seq shar sort split strip sum tac tail tee \ + texindex touch tr uname unexpand uniq units vdir wc who + +# @since 2.12 +declare -Ag _comp_xspecs + +# @since 2.12 +_comp_complete_filedir_xspec() +{ + local cur prev words cword comp_args + _comp_initialize -- "$@" || return + _comp_compgen_filedir_xspec "$1" +} + +# @since 2.12 +_comp_compgen_filedir_xspec() +{ + _comp_compgen_tilde && return + + local REPLY + _comp_quote_compgen "$cur" + local quoted=$REPLY + + local xspec=${_comp_xspecs[${1##*/}]-${_xspecs[${1##*/}]-}} + local -a toks + _comp_compgen -v toks -c "$quoted" -- -d + + # Munge xspec to contain uppercase version too + # https://lists.gnu.org/archive/html/bug-bash/2010-09/msg00036.html + # news://news.gmane.io/4C940E1C.1010304@case.edu + eval xspec="${xspec}" + local matchop=! + if [[ $xspec == !* ]]; then + xspec=${xspec#!} + matchop=@ + fi + xspec="$matchop($xspec|${xspec^^})" + + _comp_compgen -av toks -c "$quoted" -- -f -X "@(|!($xspec))" + + # Try without filter if it failed to produce anything and configured to + [[ ${BASH_COMPLETION_FILEDIR_FALLBACK-} && ${#toks[@]} -lt 1 ]] && + _comp_compgen -av toks -c "$quoted" -- -f + + ((${#toks[@]})) || return 1 + + # Remove . and .. (as well as */. and */..) from suggestions, unless .. or + # */.. was typed explicitly by the user (for users who use tab-completion + # to append a slash after '..') + if [[ $cur != ?(*/).. ]]; then + _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' || return 1 + fi + + compopt -o filenames + _comp_compgen -RU toks -- -W '"${toks[@]}"' +} + +_comp__init_install_xspec() +{ + local xspec=$1 cmd + shift + for cmd in "$@"; do + _comp_xspecs[$cmd]=$xspec + done +} +# bzcmp, bzdiff, bz*grep, bzless, bzmore intentionally not here, see Debian: #455510 +_comp__init_install_xspec '!*.?(t)bz?(2)' bunzip2 bzcat pbunzip2 pbzcat lbunzip2 lbzcat +_comp__init_install_xspec '!*.@(zip|[aegjkswx]ar|exe|pk3|wsz|zargo|xpi|s[tx][cdiw]|sx[gm]|o[dt][tspgfc]|od[bm]|oxt|?(o)xps|epub|cbz|apk|aab|ipa|do[ct][xm]|p[op]t[mx]|xl[st][xm]|pyz|vsix|whl|[Ff][Cc][Ss]td)' unzip zipinfo +_comp__init_install_xspec '*.Z' compress znew +# zcmp, zdiff, z*grep, zless, zmore intentionally not here, see Debian: #455510 +_comp__init_install_xspec '!*.@(Z|[gGd]z|t[ag]z)' gunzip zcat +_comp__init_install_xspec '!*.@(Z|[gGdz]z|t[ag]z)' unpigz +_comp__init_install_xspec '!*.Z' uncompress +# lzcmp, lzdiff intentionally not here, see Debian: #455510 +_comp__init_install_xspec '!*.@(tlz|lzma)' lzcat lzegrep lzfgrep lzgrep lzless lzmore unlzma +_comp__init_install_xspec '!*.@(?(t)xz|tlz|lzma)' unxz xzcat +_comp__init_install_xspec '!*.lrz' lrunzip +_comp__init_install_xspec '!*.@(gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx)' ee +_comp__init_install_xspec '!*.@(gif|jp?(e)g|tif?(f)|png|p[bgp]m|bmp|x[bp]m|rle|rgb|pcx|fits|pm|svg)' qiv +_comp__init_install_xspec '!*.@(gif|jp?(e)g?(2)|j2[ck]|jp[2f]|tif?(f)|png|p[bgpn]m|webp|bmp|x[bp]m|rle|rgb|pcx|fits|pm|?(e)ps)' xv +_comp__init_install_xspec '!*.@(@(?(e)ps|?(E)PS|pdf|PDF)?(.gz|.GZ|.bz2|.BZ2|.Z))' gv ggv kghostview +_comp__init_install_xspec '!*.@(dvi|DVI)?(.@(gz|Z|bz2))' xdvi kdvi +_comp__init_install_xspec '!*.dvi' dvips dviselect dvitype dvipdf advi dvipdfm dvipdfmx +_comp__init_install_xspec '!*.[pf]df' acroread gpdf xpdf +_comp__init_install_xspec '!*.@(?(e)ps|pdf)' kpdf +_comp__init_install_xspec '!*.@(okular|@(?(e|x)ps|?(E|X)PS|[pf]df|[PF]DF|dvi|DVI|cb[rz]|CB[RZ]|djv?(u)|DJV?(U)|dvi|DVI|gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx|GIF|JP?(E)G|MIFF|TIF?(F)|PN[GM]|P[BGP]M|BMP|XPM|ICO|XWD|TGA|PCX|epub|EPUB|odt|ODT|fb?(2)|FB?(2)|mobi|MOBI|g3|G3|chm|CHM|md|markdown)?(.?(gz|GZ|bz2|BZ2|xz|XZ)))' okular +_comp__init_install_xspec '!*.pdf' epdfview pdfunite +_comp__init_install_xspec '!*.@(cb[rz7t]|djv?(u)|?(e)ps|pdf)' zathura +_comp__init_install_xspec '!*.@(?(e)ps|pdf)' ps2pdf ps2pdf12 ps2pdf13 ps2pdf14 ps2pdfwr +_comp__init_install_xspec '!*.texi*' makeinfo texi2html +_comp__init_install_xspec '!*.@(?(la)tex|texi|dtx|ins|ltx|dbj)' tex latex slitex jadetex pdfjadetex pdftex pdflatex texi2dvi xetex xelatex luatex lualatex +_comp__init_install_xspec '!*.mp3' mpg123 mpg321 madplay +_comp__init_install_xspec '!*@(.@(mp?(e)g|MP?(E)G|wm[av]|WM[AV]|avi|AVI|asf|vob|VOB|bin|dat|divx|DIVX|vcd|ps|pes|fli|flv|FLV|fxm|FXM|viv|rm|ram|yuv|mov|MOV|qt|QT|web[am]|WEB[AM]|mp[234]|MP[234]|m?(p)4[av]|M?(P)4[AV]|mkv|MKV|og[agmv]|OG[AGMV]|t[ps]|T[PS]|m2t?(s)|M2T?(S)|mts|MTS|wav|WAV|flac|FLAC|asx|ASX|mng|MNG|srt|m[eo]d|M[EO]D|s[3t]m|S[3T]M|it|IT|xm|XM)|+([0-9]).@(vdr|VDR))?(.@(crdownload|part))' xine aaxine cacaxine fbxine +_comp__init_install_xspec '!*@(.@(mp?(e)g|MP?(E)G|wm[av]|WM[AV]|avi|AVI|asf|vob|VOB|bin|dat|divx|DIVX|vcd|ps|pes|fli|flv|FLV|fxm|FXM|viv|rm|ram|yuv|mov|MOV|qt|QT|web[am]|WEB[AM]|mp[234]|MP[234]|m?(p)4[av]|M?(P)4[AV]|mkv|MKV|og[agmv]|OG[AGMV]|opus|OPUS|t[ps]|T[PS]|m2t?(s)|M2T?(S)|mts|MTS|wav|WAV|flac|FLAC|asx|ASX|mng|MNG|srt|m[eo]d|M[EO]D|s[3t]m|S[3T]M|it|IT|xm|XM|iso|ISO)|+([0-9]).@(vdr|VDR))?(.@(crdownload|part))' kaffeine dragon totem +_comp__init_install_xspec '!*.@(avi|asf|wmv)' aviplay +_comp__init_install_xspec '!*.@(rm?(j)|ra?(m)|smi?(l))' realplay +_comp__init_install_xspec '!*.@(mpg|mpeg|avi|mov|qt)' xanim +_comp__init_install_xspec '!*.@(og[ag]|m3u|flac|spx)' ogg123 +_comp__init_install_xspec '!*.@(mp3|ogg|pls|m3u)' gqmpeg freeamp +_comp__init_install_xspec '!*.fig' xfig +_comp__init_install_xspec '!*.@(mid?(i)|cmf)' playmidi +_comp__init_install_xspec '!*.@(mid?(i)|rmi|rcp|[gr]36|g18|mod|xm|it|x3m|s[3t]m|kar)' timidity +_comp__init_install_xspec '!*.@(669|abc|am[fs]|d[bs]m|dmf|far|it|mdl|m[eo]d|mid?(i)|mt[2m]|oct|okt?(a)|p[st]m|s[3t]m|ult|umx|wav|xm)' modplugplay modplug123 +_comp__init_install_xspec '*.@([ao]|so|so.!(conf|*/*)|[rs]pm|gif|jp?(e)g|mp3|mp?(e)g|avi|asf|ogg|class)' vi vim gvim rvim view rview rgvim rgview gview emacs xemacs sxemacs kate kwrite +_comp__init_install_xspec '!*.@(zip|z|gz|tgz)' bzme +# konqueror not here on purpose, it's more than a web/html browser +_comp__init_install_xspec '!*.@(?([xX]|[sS])[hH][tT][mM]?([lL]))' netscape mozilla lynx galeon dillo elinks amaya epiphany +_comp__init_install_xspec '!*.@(sxw|stw|sxg|sgl|doc?([mx])|dot?([mx])|rtf|txt|htm|html|?(f)odt|ott|odm|pdf)' oowriter lowriter +_comp__init_install_xspec '!*.@(sxi|sti|pps?(x)|ppt?([mx])|pot?([mx])|?(f)odp|otp)' ooimpress loimpress +_comp__init_install_xspec '!*.@(sxc|stc|xls?([bmx])|xlw|xlt?([mx])|[ct]sv|?(f)ods|ots)' oocalc localc +_comp__init_install_xspec '!*.@(sxd|std|sda|sdd|?(f)odg|otg)' oodraw lodraw +_comp__init_install_xspec '!*.@(sxm|smf|mml|odf)' oomath lomath +_comp__init_install_xspec '!*.odb' oobase lobase +_comp__init_install_xspec '!*.[rs]pm' rpm2cpio +_comp__init_install_xspec '!*.aux' bibtex +_comp__init_install_xspec '!*.po' poedit gtranslator kbabel lokalize +_comp__init_install_xspec '!*.@([Pp][Rr][Gg]|[Cc][Ll][Pp])' harbour gharbour hbpp +_comp__init_install_xspec '!*.[Hh][Rr][Bb]' hbrun +_comp__init_install_xspec '!*.ly' lilypond ly2dvi +_comp__init_install_xspec '!*.@(dif?(f)|?(d)patch)?(.@([gx]z|bz2|lzma))' cdiff +_comp__init_install_xspec '!@(*.@(ks|jks|jceks|p12|pfx|bks|ubr|gkr|cer|crt|cert|p7b|pkipath|pem|p10|csr|crl)|cacerts)' portecle +_comp__init_install_xspec '!*.@(mp[234c]|og[ag]|@(fl|a)ac|m4[abp]|spx|tta|w?(a)v|wma|aif?(f)|asf|ape)' kid3 kid3-qt +unset -f _comp__init_install_xspec + +# Minimal completion to use as fallback in _comp_complete_load. +# TODO:API: rename per conventions +_comp_complete_minimal() +{ + local cur prev words cword comp_args + _comp_initialize -- "$@" || return + compopt -o bashdefault -o default +} +# Complete the empty string to allow completion of '>', '>>', and '<' on < 4.3 +# https://lists.gnu.org/archive/html/bug-bash/2012-01/msg00045.html +complete -F _comp_complete_minimal '' + +# Initialize the variable "_comp__base_directory" +# @var[out] _comp__base_directory +_comp__init_base_directory() +{ + local REPLY + _comp_abspath "${BASH_SOURCE[0]-./bash_completion}" + _comp__base_directory=${REPLY%/*} + [[ $_comp__base_directory ]] || _comp__base_directory=/ + unset -f "$FUNCNAME" +} +_comp__init_base_directory + +# @since 2.12 +_comp_load() +{ + local flag_fallback_default="" IFS=$' \t\n' + local OPTIND=1 OPTARG="" OPTERR=0 opt + while getopts ':D' opt "$@"; do + case $opt in + D) flag_fallback_default=set ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 2 + ;; + esac + done + shift "$((OPTIND - 1))" + + local cmd=$1 cmdname=${1##*/} dir compfile + local -a paths + [[ $cmdname ]] || return 1 + + local backslash= + if [[ $cmd == \\* ]]; then + cmd=${cmd:1} + # If we already have a completion for the "real" command, use it + $(complete -p -- "$cmd" 2>/dev/null || echo false) "\\$cmd" && return 0 + backslash=\\ + fi + + # Resolve absolute path to $cmd + local REPLY pathcmd origcmd=$cmd + if pathcmd=$(type -P -- "$cmd"); then + _comp_abspath "$pathcmd" + cmd=$REPLY + fi + + local -a dirs=() + + # Lookup order: + # 1) From BASH_COMPLETION_USER_DIR (e.g. ~/.local/share/bash-completion): + # User installed completions. + if [[ ${BASH_COMPLETION_USER_DIR-} ]]; then + _comp_split -F : paths "$BASH_COMPLETION_USER_DIR" && + dirs+=("${paths[@]/%//completions}") + else + dirs=("${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions") + fi + + # 2) From the location of bash_completion: Completions relative to the main + # script. This is primarily for run-in-place-from-git-clone setups, where + # we want to prefer in-tree completions over ones possibly coming with a + # system installed bash-completion. (Due to usual install layouts, this + # often hits the correct completions in system installations, too.) + dirs+=("$_comp__base_directory/completions") + + # 3) From bin directories extracted from the specified path to the command, + # the real path to the command, and $PATH + paths=() + [[ $cmd == /* ]] && paths+=("${cmd%/*}") + _comp_realcommand "$cmd" && paths+=("${REPLY%/*}") + _comp_split -aF : paths "$PATH" + for dir in "${paths[@]%/}"; do + [[ $dir == ?*/@(bin|sbin) ]] && + dirs+=("${dir%/*}/share/bash-completion/completions") + done + + # 4) From XDG_DATA_DIRS or system dirs (e.g. /usr/share, /usr/local/share): + # Completions in the system data dirs. + _comp_split -F : paths "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" && + dirs+=("${paths[@]/%//bash-completion/completions}") + + # Set up default $IFS in case loaded completions depend on it, + # as well as for $compspec invocation below. + local IFS=$' \t\n' + + # Look up and source + shift + local i prefix compspec + for prefix in "" _; do # Regular from all dirs first, then fallbacks + for i in ${!dirs[*]}; do + dir=${dirs[i]} + if [[ ! -d $dir ]]; then + unset -v 'dirs[i]' + continue + fi + for compfile in "$prefix$cmdname" "$prefix$cmdname.bash"; do + compfile="$dir/$compfile" + # Avoid trying to source dirs as long as we support bash < 4.3 + # to avoid an fd leak; https://bugzilla.redhat.com/903540 + if [[ -d $compfile ]]; then + # Do not warn with . or .. (especially the former is common) + [[ $compfile == */.?(.) ]] || + echo "bash_completion: $compfile: is a directory" >&2 + elif [[ -e $compfile ]] && . "$compfile" "$cmd" "$@"; then + # At least $cmd is expected to have a completion set when + # we return successfully; see if it already does + if compspec=$(complete -p -- "$cmd" 2>/dev/null); then + # $cmd is the case in which we do backslash processing + [[ $backslash ]] && eval "$compspec \"\$backslash\$cmd\"" + # If invoked without path, that one should be set, too + # ...but let's not overwrite an existing one, if any + [[ $origcmd != */* ]] && + ! complete -p -- "$origcmd" &>/dev/null && + eval "$compspec \"\$origcmd\"" + return 0 + fi + # If not, see if we got one for $cmdname + if [[ $cmdname != "$cmd" ]] && compspec=$(complete -p -- "$cmdname" 2>/dev/null); then + # Use that for $cmd too, if we have a full path to it + [[ $cmd == /* ]] && eval "$compspec \"\$cmd\"" + return 0 + fi + # Nothing expected was set, continue lookup + fi + done + done + done + + # Look up simple "xspec" completions + [[ -v _comp_xspecs[$cmdname] || -v _xspecs[$cmdname] ]] && + complete -F _comp_complete_filedir_xspec "$cmdname" "$backslash$cmdname" && return 0 + + if [[ $flag_fallback_default ]]; then + complete -F _comp_complete_minimal -- "$origcmd" && return 0 + fi + + return 1 +} + +# set up dynamic completion loading +# @since 2.12 +_comp_complete_load() +{ + # $1=_EmptycmD_ already for empty cmds in bash 4.3, set to it for earlier + local cmd=${1:-_EmptycmD_} + + # Pass -D to define *something*, or otherwise there will be no completion + # at all. + _comp_load -D -- "$cmd" && return 124 +} && + complete -D -F _comp_complete_load + +# Function for loading and calling functions from dynamically loaded +# completion files that may not have been sourced yet. +# @param $1 completion file to load function from in case it is missing +# @param $2 the xfunc name. When it does not start with `_', +# `_comp_xfunc_${1//[^a-zA-Z0-9_]/_}_$2' is used for the actual name of the +# shell function. +# @param $3... if any, specifies the arguments that are passed to the xfunc. +# @since 2.12 +_comp_xfunc() +{ + local xfunc_name=$2 + [[ $xfunc_name == _* ]] || + xfunc_name=_comp_xfunc_${1//[^a-zA-Z0-9_]/_}_$xfunc_name + declare -F -- "$xfunc_name" &>/dev/null || _comp_load -- "$1" + "$xfunc_name" "${@:3}" +} + +# Call a POSIX-compatible awk. Solaris awk is not POSIX-compliant, but Solaris +# provides a POSIX-compatible version through /usr/xpg4/bin/awk. We switch the +# implementation to /usr/xpg4/bin/awk in Solaris if any. +# @since 2.12 +if [[ $OSTYPE == *solaris* && -x /usr/xpg4/bin/awk ]]; then + _comp_awk() + { + /usr/xpg4/bin/awk "$@" + } +else + _comp_awk() + { + command awk "$@" + } +fi + +# List custom/extra completion files to source on the startup +## @param $1 path Path to "bash_completion" +## @var[out] _comp__init_startup_configs +_comp__init_collect_startup_configs() +{ + local base_path=${1:-${BASH_SOURCE[1]}} + _comp__init_startup_configs=() + + # source compat completion directory definitions + local -a compat_dirs=() + local compat_dir + if [[ ${BASH_COMPLETION_COMPAT_DIR-} ]]; then + compat_dirs+=("$BASH_COMPLETION_COMPAT_DIR") + else + compat_dirs+=(/etc/bash_completion.d) + # Similarly as for the "completions" dir, look up from relative to + # bash_completion, primarily for installed-with-prefix and + # run-in-place-from-git-clone setups. Notably we do it after the + # system location here, in order to prefer in-tree variables and + # functions. + if [[ $_comp__base_directory == */share/bash-completion ]]; then + compat_dir=${_comp__base_directory%/share/bash-completion}/etc/bash_completion.d + else + compat_dir=$_comp__base_directory/bash_completion.d + fi + [[ ${compat_dirs[0]} == "$compat_dir" ]] || + compat_dirs+=("$compat_dir") + fi + for compat_dir in "${compat_dirs[@]}"; do + [[ -d $compat_dir && -r $compat_dir && -x $compat_dir ]] || continue + local compat_files + _comp_expand_glob compat_files '"$compat_dir"/*' + local compat_file + for compat_file in "${compat_files[@]}"; do + [[ ${compat_file##*/} != @($_comp_backup_glob|Makefile*|${BASH_COMPLETION_COMPAT_IGNORE-}) && + -f $compat_file && -r $compat_file ]] && + _comp__init_startup_configs+=("$compat_file") + done + done + + # source user completion file + # + # Remark: We explicitly check that $user_completion is not '/dev/null' + # since /dev/null may be a regular file in broken systems and can contain + # arbitrary garbages of suppressed command outputs. + local user_file=${BASH_COMPLETION_USER_FILE:-~/.bash_completion} + [[ $user_file != "$base_path" && $user_file != /dev/null && -r $user_file && -f $user_file ]] && + _comp__init_startup_configs+=("$user_file") + + unset -f "$FUNCNAME" +} +_comp__init_collect_startup_configs "$BASH_SOURCE" +# shellcheck disable=SC2154 +for _comp_init_startup_config in "${_comp__init_startup_configs[@]}"; do + . "$_comp_init_startup_config" +done +unset -v _comp__init_startup_configs _comp_init_startup_config +unset -f have +unset -v have + +set $_comp__init_original_set_v +unset -v _comp__init_original_set_v + +# ex: filetype=sh