The current tab-completion while \"read -e\" is active in bash seems to be only matching filenames:
read -e
[[TabTab]]
abc.txt bcd.txt cde.txt
Although it seems like a reasonable request, I don't believe that is possible.
The existing implementation of the read
builtin sets the readline completion environment to a fairly basic configuration before calling readline to handle -e
input.
You can see the code in builtins/read.def, in the edit_line function: it sets rl_attempted_completion_function to NULL
for the duration of the call to readline
. readline
has several completion overrides, so it's not 100% obvious that this resets the entire completion environment, but as far as I know this is the function which is used to implement programmable completion as per the complete
command.
With some work, you could probably modify the definition of the read
command to allow a specific completion function instead of or in addition to the readline standard filename completion function. That would require a non-trivial understanding of bash internals, but it would be a reasonable project if you wanted to gain familiarity with those internals.
As a simpler but less efficient alternative, you could write your own little utility which just accepts one line of keyboard input with readline
and echoes it to stdout. Then invoke read
redirecting its stdin to your utility:
read -r < <(my_reader string1 string2 string3)
(That assumes that my_reader
uses its command-line arguments to construct the potential completion list for the readline
library. You'd probably want the option to present a prompt as well.)
The readline documentation includes an example of an application which does simple custom completion; once you translate it from the K&R function prototype syntax, it might be pretty easy to adapt to your needs.
Edit: After I looked at that example again, I thought it had a lot of unnecessary details, so I wrote the following example with fewer unnecessary details. I might upload it to github, but for now it's here even though it's nearly 100 lines:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <readline/readline.h>
static void version(const char* progname) {
fprintf(stderr, "%s 0.1\n", progname);
}
static void usage(const char* progname) {
fprintf(stderr, "Usage: %s [-fhv] [-p PROMPT] [-n PROGNAME] [COMPLETION...]\n", progname);
fprintf(stderr,
"Reads one line using readline, and prints it to stdout.\n"
"Returns success if a line was read.\n"
" -p PROMPT Output PROMPT before requesting input.\n"
" -n PROGNAME Set application name to PROGNAME for readline config file\n"
" (Default: %s).\n"
" -f Use filename completion as well as specified completions.\n"
" -h Print this help text and exit.\n"
" -v Print version number and exit.\n"
" COMPLETION word to add to the list of possible completions.\n",
progname);
}
/* Readline really likes globals, so none of its hooks take a context parameter. */
static char** completions = NULL;
static char* generate_next_completion(const char* text, int state) {
static int index = 0;
if (state == 0) index = 0; /* reset index if we're starting */
size_t textlen = strlen(text);
while (completions[index++])
if (strncmp(completions[index - 1], text, textlen) == 0)
return strdup(completions[index - 1]);
return NULL;
}
/* We use this if we will fall back to filename completion */
static char** generate_completions(const char* text, int start, int end) {
return rl_completion_matches(text, generate_next_completion);
}
int main (int argc, char **argv) {
const char* prompt = "";
const char* progname = strrchr(argv[0], '/');
progname = progname ? progname + 1 : argv[0];
rl_readline_name = progname;
bool use_file_completion = false;
for (;;) {
int opt = getopt(argc, argv, "+fp:n:hv");
switch (opt) {
case -1: break;
case 'f': use_file_completion = true; continue;
case 'p': prompt = optarg; continue;
case 'n': rl_readline_name = optarg; continue;
case 'h': usage(progname); return 0;
case 'v': version(progname); return 0;
default: usage(progname); return 2;
}
break;
}
/* The default is stdout, which would interfere with capturing output. */
rl_outstream = stderr;
completions = argv + optind;
rl_completion_entry_function = rl_filename_completion_function;
if (*completions) {
if (use_file_completion)
rl_attempted_completion_function = generate_completions;
else
rl_completion_entry_function = generate_next_completion;
} else {
/* No specified strings */
if (!use_file_completion)
rl_inhibit_completion = true;
}
char* line = readline(prompt);
if (line) {
puts(line);
free(line);
return 0;
} else {
fputc('\n', rl_outstream);
return 1;
}
}