Before actual question few words about what I'm doing to grasp the idea.
I would want to add another cool feature to my Authentic Theme for Webmin.
I have made Command Shell module look/feel just if it was ordinary shell. However, as it's only a port, it has few limitations. One of them is missing shell autocomplete, when you hit Tab
key (or other based on the system: Esc+Esc
and/or Ctrl+I
).
My point is to make it work natively. I'm going to use an
XMLHttpRequest
call to the server and pass the part of entered command to the actual shell. The call will be triggered on the event of the Tab
key.
For example, when you're in Authentic Theme dropdown shell (you could see it on the video screencast above), and when you type, let's say, xa
and hit Tab
, the event will be triggered and make a request. Then the server receives a string xa
and we're ready to begin.
1. What is the best way to execute such command in Perl?
2. How to properly escape such command to make sure that it won't be exploitable;
3. How to trigger programmatically Tab
key (to run autocomplete) in the system()
call;
4. How to grep output.
On the respond, using xa
as an example, I expect to get xargs
on the results.
I'm aware about possibly of ambiguity of the passed command, for example, when using sys
, and when autocomplete wouldn't work on the single Tab
key. I think it's better to exclude this from the scope of current question.
If you know the type of completion you want to generate, you can generate completions using the compgen
bash builtin.
Most usefully, the -c
option will complete command names and -o default
will complete filenames through readline. See the options for complete
, most of which you can also use with compgen
.
Note that system()
will pass the command to /bin/sh -c
, which may not be the same as bash
on your system. So you can do something like:
system('bash', '-c', 'compgen -c -- "$1"', 'bash', 'xa')
# xargs
# xattr
# etc…
or
system('bash', '-c', 'compgen -o default -- "$1"', 'bash', 'file')
# file.txt
# file.pdf
# etc…
If you want to literally the same thing that bash would do given a particular string (and not guess the completion type yourself), you will need to actually invoke the tab-press (or equivalent), which you can do with an expect
script. This is also the only way to properly invoke any custom completion functions you may have defined. See for example the way that bash-completion handles its test suite.
Below is my attempt at an expect
script to print out the completions that bash would suggest for arbitrary command input:
#!/usr/bin/env expect
log_user 0
set prompt {/@}
set cmd [lindex $argv 0]
# start bash with no startup files for clean env
spawn env INPUTRC=/dev/null PS1=$prompt bash --norc
expect $prompt
# set some readline variables for consistent completion output
send "bind 'set show-all-if-ambiguous on'\r"
expect $prompt
send "bind 'set bell-style none'\r"
expect $prompt
send "bind 'set completion-query-items -1'\r"
expect $prompt
send "bind 'set page-completions off'\r"
expect $prompt
send "bind 'set completion-display-width 0'\r"
expect $prompt
# run the completion
send "$cmd\t $prompt"
expect {
# multiple matches, printed on separate lines, followed by prompt
-re "^$cmd\r\n(.*)\r\n$prompt$cmd" { puts $expect_out(1,string) }
# single match, completed in-place
-re "^($cmd\[^ \]*) $prompt" { puts $expect_out(1,string) }
# single match, completed in-place, nospace
-re "^($cmd\[^ \]+) $prompt" { puts $expect_out(1,string) }
# no match
-re "^$cmd $prompt" { exit }
}
Calling ./script.exp string
will print out the actual completions that bash will produce for string
, one per line. If there are no completions suggested, nothing is printed.