Completing sudo after --user= doesn't suggest commands
Describe the bug
When trying to complete sudo --user=foobar <HIT TAB HERE>, it suggests the files in the current directory instead of commands
To reproduce
- Type
sudo --user=<A USER ON YOUR SYSTEM> - Try to complete
- See problem
Expected behavior
Trying to complete sudo --user=foo should suggest command names, not files in the current directory
Versions (please complete the following information)
- [ ] Operating system name/distribution and version: Fedora 35 Silverblue
- [ ] bash version,
echo "$BASH_VERSION": 5.1.8(1)-release - [ ] bash-completion version,
(IFS=.; echo "${BASH_COMPLETION_VERSINFO[*]}"): 2.11
Additional context
I discovered this when trying to write a completion for pkexec. There's another bug in sudo's completion that mishandles sudo --user foobar, so this context doesn't apply to sudo. My completion doesn't have this bug, so I can get this additional behavior: pkexec --user=foobar <TAB> completes the directory, but pkexec --user foobar <TAB> completes commands. I can confirm that, at least with my completion, _command_offset is being executed. So I suspect soething in _command_offset can't deal with --arg=data arguments?
Debug trace
This is caused by the mismatching of the offset in words and that in COMP_WORDS. While _command_offset assumes its first argument being the offset in COMP_WORDS, _sudo passes the offset in words. Similar mismatching is also found in other completions.
- 7 completions pass the offset in
words-
screen976ba4482 (2011-04) -
strace8048917(2011-04) Converted fromCOMP_WORDS. I think this is a mistake in refactoring. -
_ioniced54fcf100 (2011-10) -
watch049b60ec6 (2011-10) -
gdbb1f780355 (2014-04) Contributed by others -
sudoca3b1f664 (2018-06) Converted fromCOMP_WORDS. This also seems like an oversight in fixing another problem. -
find3e849c25e (2020-04)
-
- 4 completions pass the offset in
COMP_WORDS-
ccacheed1703497 (2011-11) -
valgrind834379ef6 (2011-11) -
timeout63b499593 (2013-10) -
xvfb-run5c0e98832 (2019-06)
-
We can determine which offset _command_offset receives and convert the above to one side. In case we decide that _command_offset should receive the offset in words, we can convert the offset in words to that in COMP_WORDS in _command_offset as
diff --git a/bash_completion b/bash_completion
index e5220cac..5172be30 100644
--- a/bash_completion
+++ b/bash_completion
@@ -2220,6 +2220,18 @@ _command_offset()
# rewrite current completion context before invoking
# actual command completion
+ # convert the new first-word position $1 to the index in COMP_WORDS.
+ if [[ -v cword && -v words ]]; then
+ 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[@]} ]]; do
+ word=${word#*"${COMP_WORDS[i++]}"}
+ done
+ done
+ set -- "$i"
+ fi
+
# find new first word position, then
# rewrite COMP_LINE and adjust COMP_POINT
local word_offset=$1 i j
I tried to find which one of words vs COMP_WORDS is preferred in the change history, but I couldn't find it there. I find some conversions from COMP_WORDS to words, but I'm not sure if that was the intentional change. Also, the difference doesn't seem to be due to the time of the introduction.