This link: Wildcards inside quotes in my case not solve the problem.
The file "a.sh" contain:

file="path with wildcard*"

The file "b.sh" contain:

. a.sh
[ -f "$file" ] && echo ok



not expand wildcard, instead


expand wildcard, but with error: "binary operator expected"

How do I solve this puzzle? It is not possible to take the wildcard out of the double quotes.

I want achieve a single match from wildcard. If there are more matches, the condition must return false, in any case not must return an error.

  • binary operator expected indicates your unquoted $file brought two words. [ -f … ] expects exactly one path. If it gets two (so with -f there are three words inside [ ]), [ assumes the middle argument is a binary operator (which somehow compares two arguments: its neighbors). If it got even more, the error would be too many arguments. The problem is not that * doesn't expand; the problem is that $file expands to too many entities. Maybe because there are two matching files; maybe because of word splitting. path with wildcard* is three words, the last one can expand to more. Jun 17 '20 at 4:34
  • 1
    It's unclear what your intention with the test is. Is it to test the existence of any regular file matching the pattern? Note that the -f test only works on single files. What if the pattern matches a range of filetypes (directories, regular files, named pipes, etc.), should the test fail (because there was at least one file that was not a regular file), or should it succeed (because there was at least one regular file)?
    – Kusalananda
    Jun 17 '20 at 6:16
  • 3
    Does this answer your question? Test if there are files matching a pattern in order to execute a script
    – pfnuesel
    Jun 17 '20 at 10:17
  • 1
    Note that . a.sh looks for a.sh in $PATH, not in the current directory (though in the case of bash when not in sh mode, it will fall back to using the a.sh in the current directory if it can't be found in $PATH). Change to . ./a.sh if you want to source the a.sh file in the current directory. Jun 17 '20 at 16:23

If you want to store in an array the list of files resulting from the expansion of a glob pattern stored in variable, in bash, that would be:

pattern='path with wildcard*'

IFS= # disable splitting

shopt -s nullglob # make globs that don't match any file expand to nothing
                  # instead of the unexpanded pattern.

files=($pattern) # here using split+glob (unquoted expansion in list context)
                 # with the splitting disabled above.

Or to assign the $files array directly without using a $pattern scalar variable:

shopt -s nullglob # make globs that don't match any file expand to nothing
                  # instead of the unexpanded pattern.

files=('path with wildcard'*)

Then you can test if that list is empty with:

if [ "${#files[@]}" -gt 0 ]; then
  echo it did find some files

If you want to check, that among those files, at least one is a regular file (after symlink resolution), you could do:

has_regular_files() {
  local file
  for file do
    [ -f "$file" ] && return

if has_regular_files "${files[@]}"; then
  echo there was at least one regular file in the list

To check that it matches only one file and that that file is a regular file:

if [ "${#files[@]}" -eq 1 ] && [ -f "${files[0]}" ]; then
  echo one and only one file matches and it is regular.

To check that it matches at least one regular file and that all the matched files are regular:

only_regular_files() {
  [ "$#" -gt 0 ] || return
  local file
  for file do
    [ -f "$file" ] || return

if only_regular_files "${files[@]}"; then
  echo at least one regular file and all are regular.

With the zsh shell, you can use glob qualifiers to match by file type:

if ()(($#)) $~pattern(N-.); then
  print at least one regular file in the expansion of the pattern.
  • contrary to bash, zsh doesn't do implicit split+glob upon unquoted parameter expansions. Here we're asking for globbing (but not splitting) with $~pattern.
  • We append the (N-.) glob qualifier, N for nullglob so that if the glob matches no file, it expands to nothing . to test for regular files only (to the exclusion of any other type of file), - so that that test be done after symlink resolution so it would also match on files that are symlinks to regular files (like your [ -f "$file" ] would).
  • the expansion of that glob is passed as arguments to an anonymous function, () {body} args where the {body} is (($#)).
  • ((expr)) is a ksh-style arithmetic expression evaluation operator, that returns true if the expression evaluates to a number other than 0. Here, the expression is $#, that is a special parameter than expands to the number of positional parameters, in this case the number of arguments of that anonymous function, so the number of files that result from that glob expansion.
[ -f $file ] && echo ok

This would expand the variable, wordsplit, and expand all matching files. You might end up with [ -f filethis filethat ], and filethis isn't a binary operator, so you get the error.

Even if there are no matches, and $file expands to nothing, you get [ -f ] which is a test to see if -f is a non-empty string. It is, so the result is true.

Expand the filenames to an array or to the positional parameters and count the matches:

file="path with wildcard*"
set -- $file
echo "matched $# files"

You have to change IFS there if you have whitespace in the pattern, because the split happens before filename expansion, so the above would have two constant words and one glob pattern. Also an unmatched pattern would remain as-is, so we need to check for that.

You could hide this in a function:

only_one() {
    local IFS=
    set -- $1
    if [ $# = 1 ] && [ -f "$1" ]; then return 0; fi
    return 1

and then run

if only_one "$file"; then 
  • My solution is more speed and comes to the same solution. Jun 17 '20 at 11:27
  • @MarioPalumbo, less typing, surely. I doubt there's a significant difference in runtime, and if that even matters, I'd suggest using something else than the shell, it's always slow. :) Note that passing through read can cause issues if someone creates filenames with newlines or backslashes in their names. Dealing with that kind of stuff is pretty much the biggest issue in shell programming, as far as I see, and arrays are often the best way to deal with that.
    – ilkkachu
    Jun 17 '20 at 11:44
  • Note that using [ -f "$1" ] means that it restricts it to regular files (tested after symlink resolution). That would return false if the glob matches one file but that file is not regular (like a director file, a fifo file, a socket file, a device file...). Jun 17 '20 at 12:19
  • If the glob doesn't match any file, set -- $file bash will still store one element in the positional parameter array unless you set the failglob or nullglob option. Jun 17 '20 at 12:26
  • @StéphaneChazelas i have tested this command with a symlink. It works. Jun 17 '20 at 12:27

I have solved with:
The file "a.sh" contain:

file=('path with wildcard'*)

The file "b.sh" contain:

. a.sh
[ -f "$file" ] && echo ok

Result: Ok

  • I want achieve a single match from wildcard. If there are more matches, is right that the answer's condition returns false. Jun 17 '20 at 11:19
  • Thanks Stephane Chazelas, what is a good method for expand wildcards and put in a variable? Jun 17 '20 at 12:34
  • 1
    See my answer... Jun 17 '20 at 12:42
  • @StéphaneChazelas Check now, i have modified my reply. Jun 17 '20 at 13:30
  • That's much better. You're now checking that the first file whose name starts with path with wildcard in the current directory is a regular file (after symlink resolution). That's because (confusingly) in bash like in ksh, $file is actually short for ${file[0]} (the element of indice 0, that's different from every other shell). Note that after mkdir 'path with wildcard1'; ln -s /dev/null 'path with wildcard2'; echo text > 'path with wildcard3', that would not output OK because the first matching file is not a regular file in that case. That would change if you swapped 1 and 3. Jun 17 '20 at 14:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.