One robust way in bash is to expand into an array, and output the first element only:
pattern="*.txt"
files=( $pattern )
echo "${files[0]}" # printf is safer!
(You can even just echo $files
, a missing index is treated as [0].)
This safely handles space/tab/newline and other metacharacters when expanding the filenames. Note that locale settings in effect can alter what "first" is.
You can also do this interactively with a bash completion function:
_echo() {
local cur=${COMP_WORDS[COMP_CWORD]} # string to expand
if compgen -G "$cur*" > /dev/null; then
local files=( ${cur:+$cur*} ) # don't expand empty input as *
[ ${#files} -ge 1 ] && COMPREPLY=( "${files[0]}" )
fi
}
complete -o bashdefault -F _echo echo
This binds the _echo
function to complete arguments to the echo
command (overriding normal completion). An extra "*" is appended in the code above, you can just hit tab on a partial filename and hopefully the right thing will happen.
The code is slightly convoluted, rather than set or assume nullglob
(shopt -s nullglob
) we check compgen -G
can expand the glob to some matches, then we expand safely into an array, and finally set COMPREPLY so that quoting is robust.
You can partly do this (programmatically expand a glob) with bash's compgen -G
, but it's not robust as it outputs unquoted to stdout.
As usual, completion is rather fraught, this breaks completion of other things, including environment variables (see the _bash_def_completion()
function here for the details of emulating the default behaviour).
You could also just use compgen
outside of a completion function:
files=( $(compgen -W "$pattern") )
One point to note is that "~" is not a glob, it's handled by bash in a separate stage of expansion, as are $variables and other expansions. compgen -G
just does filename globbing, but compgen -W
gives you all of bash's default expansion, though possibly too many expansions (including ``
and $()
). Unlike -G
, the -W
is safely quoted (I can't explain the disparity). Since the purpose of -W
is that it expands tokens, this means it will expand "a" to "a" even if no such file exists, so it's perhaps not ideal.
This is easier to understand, but may have unwanted side-effects:
_echo() {
local cur=${COMP_WORDS[COMP_CWORD]}
local files=( $(compgen -W "$cur") )
printf -v COMPREPLY %q "${files[0]}"
}
Then:
touch $'curious \n filename'
echo curious*
tab
Note the use of printf %q
to safely quote the values.
One final option is to use 0-delimited output with GNU utilities (see the bash FAQ):
pattern="*.txt"
while IFS= read -r -d $'\0' filename; do
printf '%q' "$filename";
break;
done < <(find . -maxdepth 1 -name "$pattern" -printf "%f\0" | sort -z )
This option gives you a little more control over the sorting order (the order when expanding a glob will be subject to your locale/LC_COLLATE
and may or may not fold case), but is otherwise a rather large hammer for such a small problem ;-)