Unix & Linux Asked by Weston Ganger on December 17, 2021
I want to have tab completion when using curly braces in a command in bash.
For example:
cp ~/html/{foo bar.txt whatever} /var/www/html/
I want the tab completion for the files stated in the curly braces
I provide diagnostics tool which enables the OP to directly observe the behavior of line completion after tab, and for comparison, the behavior of parsing after return. After observation and analysis, we may conclude that while writing a line completion function to complete curly brackets function might be possible - it would be pointless due to bash's limitation of suppressing any further line completion functionality after the curly brackets.
The following bash script enables the diagnostics:
#!/bin/bash
confirm_args(){
logger -t "confirm_args" -- "#=${#}"
for index in `seq 0 ${#}` ; do
eval item=$$index
logger -t "confirm_args" -- "[$index] ${item}"
done
}
_foo_completion(){
logger -t "_foo_completion" -- "COMP_CWORD=${COMP_CWORD}"
logger -t "_foo_completion" -- "#COMP_WORDS=${#COMP_WORDS[@]}"
for index in `seq 0 $((${#COMP_WORDS[@]}-1))` ; do
logger -t "_foo_completion" -- "[$index] ${COMP_WORDS[$index]}"
done
}
declare -f _foo_completion
complete -F _foo_completion "foo"
alias foo=confirm_args
I have made it available as a gist foo-completion.sh
.
Copy and paste it into a file /tmp/foo-completion.sh
.
Create two dummy files
touch /tmp/a.{1,2}
In order for the diagnostics to function correctly, logger
must write to the system log. Then the OP will be able to view the diagnostic output by following the system log file. If the OP is running systemd
it can viewed by opening a new window and entering
sudo journalctl -f
Activate the line completion and diagnostic output:
source /tmp/foo-completion.sh
Note that the _foo_completion
does NOT perform any completion, thus no candidates are return to the user. All it does is show what data is passed to the line completion function.
Test some inputs and observe the outputs:
INPUT 1
foo a.*[SPACE][TAB]
OUTPUT
Jul 23 12:10:34 ub18 _foo_completion[17672]: COMP_CWORD=2
Jul 23 12:10:34 ub18 _foo_completion[17673]: #COMP_WORDS=3
Jul 23 12:10:34 ub18 _foo_completion[17675]: [0] foo
Jul 23 12:10:34 ub18 _foo_completion[17676]: [1] /tmp/a.*
Jul 23 12:10:34 ub18 _foo_completion[17677]: [2]
INPUT 2
foo a.*[SPACE][RETURN]
OUTPUT
Jul 23 12:19:43 ub18 confirm_args[18487]: #=2
Jul 23 12:19:43 ub18 confirm_args[18489]: [0] bash
Jul 23 12:19:43 ub18 confirm_args[18490]: [1] /tmp/a.1
Jul 23 12:19:43 ub18 confirm_args[18491]: [2] /tmp/a.2
INPUT 3
foo a.{1,2}[SPACE][TAB]
OUTPUT
NOTE - no output!
INPUT 4
foo a.{1,2}[SPACE][RETURN]
OUTPUT
Jul 23 12:28:42 ub18 confirm_args[19098]: #=2
Jul 23 12:28:42 ub18 confirm_args[19100]: [0] bash
Jul 23 12:28:42 ub18 confirm_args[19101]: [1] /tmp/a.1
Jul 23 12:28:42 ub18 confirm_args[19102]: [2] /tmp/a.2
In "INPUT 1" the *
is passed to the line completion function as a literal character '*'. However, in "INPUT 3" the line completion function is not even called. Apparently the curly brackets cause bash to suppress any further calls to the line completion function.
On the other hand, in the post [RETURN] parsing cases INPUT 2 and INPUT 4, bash expands both cases a.*
and a.{1,2}
to the same result.
One more test case:
INPUT 5
foo a.{[TAB]
OUTPUT
Jul 23 12:56:52 ub18 _foo_completion[21059]: COMP_CWORD=1
Jul 23 12:56:52 ub18 _foo_completion[21060]: #COMP_WORDS=2
Jul 23 12:56:52 ub18 _foo_completion[21062]: [0] foo
Jul 23 12:56:52 ub18 _foo_completion[21063]: [1] /tmp/a.{
In "INPUT 5", with no space before the tab, bash DOES call line completion. So a line completion could theoretically return a single candidate
/tmp/a.{1,2}
which should result in auto-completion (although I haven't tested).
The problem with that would be that further files could not be added with line completion, because the curly brackets suppress line completion.
Testing with a preexisting line completion function, e.g.,
INPUT CHECK
ls /tmp/a.{[TAB][TAB][TAB][TAB][TAB][TAB]
OUTPUT
(nothing)
seems to support the thesis that it's not worth supporting curlies in a line completion function until/unless bash doesn't suppress line completion after curlies.
EDIT: After playing around with the [CTRL]{{
method described in this answer, I found it complete and was (sometimes) able to be followed by the default completion function to add more files. However, there was no way to continue with the original _foo_completion
function after that, which may or may not be a limitation depending on the usage case.
Answered by Craig Hicks on December 17, 2021
In a Ubuntu (16.04) terminal you can do Ctrl+{ and then { and it autocompletes the curly brackets with the files in the directory. It uses commas instead of spaces to separate the files. For example, a directory containing these files:
file.cpp file.h file.i file.py file_wrap.cxx
... would auto complete to this:
$ cp file{.{cpp,h,i,py},_wrap.cxx}
Answered by user3325563 on December 17, 2021
From man bash
:
COMP_WORDBREAKS
The set of characters that the readline
library treats as word separators when per‐
forming word completion. If COMP_WORDBREAKS
is unset, it loses its special properties,
even if it is subsequently reset.
I thought that was interesting, so I did:
printf %q\n "${COMP_WORDBREAKS}"
...to have a look at its default value. This got me:
$' tn"'@><=;|&(:'
So I figured, why not...?
$ COMP_WORDBREAKS="{}$COMP_WORDBREAKS"
$ {/<TAB><TAB>
...where the <TAB>
part represents my actually pressing the <TAB>
key, and I saw:
$ {/
bin/ esp/ lib/ opt/ run/ sys/ usr/
boot/ etc/ lib64/ proc/ sbin/ testuser/ var/
dev/ home/ mnt/ root/ srv/ tmp/
So that was neat. I then did:
$ COMP_WORDBREAKS=${COMP_WORDBREAKS#??}
$ {/<TAB><TAB>
...and nothing happened.
Answered by mikeserv on December 17, 2021
There are two ways to interpret the question: you want the completion while you are typing the names before the closing }
(in effect performing completion of files in another directory); or, you want the completion to expand and substitute the (valid) names after the closing }
. { }
expansion expands all the options, unlike globbing which expands existing file names, though using "," rather than space.
With bash, the expected way to use {}
to create a list of words is:
cp ~/html/{foo,bar.txt,whatever} ...
There are two options: associating a readline key binding with a shell function, or programmable completion.
The readline approach gives you a free hand to completely rewrite the current command, but the problem is that it doesn't tokenise the current command line, so you have a non-trivial parsing problem.
Programmable completion does tokenise the command line, but you can only modify/replace the current word, which means you can't (easily) preserve the path/{
prefix during editing.
The only related native bash capability is the readline
function shell-expand-line
for bash purports to do "all of the shell word expansions", this is bound by default to M-C-e
(Esc C-e
). One might reasonably expect this expansion to be all of the seven types of expansion indicated in the man page (bash-4.3):
Expansion is performed on the command line after it has been split into words. There are seven kinds of expansion performed: brace expansion, tilde expansion, parameter and variable expansion, command substitu‐ tion, arithmetic expansion, word splitting, and pathname expansion.
You can experiment with by typing (without hitting Return)
echo {1..2} ~root $0 $LANG `echo foo` $((1+2) "a b" /etc/p[aeiou]*
And then MetaCtrlE (or ESC + CtrlE if you are using a Meta-impaired keyboard).
Neither the first nor last (brace and path) expansions work for me, so the documentation and implementation don't quite match IMHO.
What follows is a work-around, rather than complete solution using tab for expansion. You need to make sure to use the correct syntax for brace expansion though, specifically with a comma to separate the items, not a space.
function _expand() {
[[ -z "${READLINE_LINE}" ]] && return
eval local aa=( ${READLINE_LINE} ) # not-quoted, eval needed
[[ ${#aa} -eq 0 ]] && return # parse problem
printf -v READLINE_LINE "%s " "${aa[@]}"
READLINE_POINT=${#READLINE_LINE} # eol, predictable at least
}
bind -x '"C-_":_expand'
This binds Ctrl_ to a bash function which uses eval
to populate an array, causing all the normal expansions (as above, and not including history) to occur, and then rebuilds the command line.
Start typing, hit Ctrl_ when you want something expanded, and Return as normal when ready.
This is more powerful than tab, but less precise in that it expands the complete input line. There are a few cases where this won't expand as hoped, specifically where various shell metacharacters confuse the simple eval
logic.
Rather than over-engineer a bash solution, Gille's suggestion of zsh
is worth a look.
Answered by mr.spuratic on December 17, 2021
Braces (which, by definition, are "curly") in this context refer to a list of fragments of text, separated by commas. While it's most often used for file names, it gets expanded before file name expansions are handled. Therefore, there is no definitive expansion capability, because bash
cannot know if you're actually typing a file name or not.
Answered by Joe Sewell on December 17, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP