0

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

Because

"$file"

not expand wildcard, instead

$file

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.

Edit:
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.

αғsнιη
  • 41,407
Mario Palumbo
  • 233
  • 1
  • 14
  • 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. – Kamil Maciorowski Jun 17 '20 at 04: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 06:16
  • 3
  • No............. – Mario Palumbo Jun 17 '20 at 11:22
  • 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. – Stéphane Chazelas Jun 17 '20 at 16:23

3 Answers3

4

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
fi

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
  done
  false
}

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

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.
fi

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
  done
}

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

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.
fi
  • 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.
2
[ -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*"
IFS=
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 
    ...
fi
ilkkachu
  • 138,973
  • My solution is more speed and comes to the same solution. – Mario Palumbo 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...). – Stéphane Chazelas 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. – Stéphane Chazelas Jun 17 '20 at 12:26
  • @StéphaneChazelas i have tested this command with a symlink. It works. – Mario Palumbo Jun 17 '20 at 12:27
  • @ilkkachu Based on (thank you) you reply, i have modified the answer, look and says me if like it. – Mario Palumbo Jun 17 '20 at 12:28
  • @MarioPalumbo, it works if the only match is a symlink pointing to a regular file, but not if the match is a directory, or a symlink to a directory, or one of the more special files. Also, it returns falsy if there are two matches, one regular file and one not. Probably a corner case, but the point is that it's hard to exclude files based in type in the expansion itself (at least in Bash). – ilkkachu Jun 17 '20 at 13:18
-2

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

Mario Palumbo
  • 233
  • 1
  • 14
  • I want achieve a single match from wildcard. If there are more matches, is right that the answer's condition returns false. – Mario Palumbo Jun 17 '20 at 11:19
  • Thanks Stephane Chazelas, what is a good method for expand wildcards and put in a variable? – Mario Palumbo Jun 17 '20 at 12:34
  • 1
    See my answer... – Stéphane Chazelas Jun 17 '20 at 12:42
  • @StéphaneChazelas Check now, i have modified my reply. – Mario Palumbo 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. – Stéphane Chazelas Jun 17 '20 at 14:24
  • I not care about not expansion in case of file not found, is good for me and in my case, when a file is found, i am sure that there aren't delimitetor within wildcard, IN MY CASE. Thank you very much @StéphaneChazelas – Mario Palumbo Jun 17 '20 at 14:39