When I migrated to fish from bash, I found glob patterns like "tmp[0-9][0-9].pdf" aren't supported. Then, recently fish has stopped to support the ? wildcard:
$ fish --version
fish, version 4.0.0
$ ls tmp00.pdf
tmp00.pdf
$ ls tmp??.pdf
ls: tmp??.pdf: No such file or directory
$
https://fishshell.com/docs/4.0/fish_for_bash_users.html says, "Fish only supports the * and ** glob (and the deprecated ? glob) as syntax."
So, has somebody written a fish function that can simulate the good-old glob? which might be used like
for file in (fmatch 'img..\.jpg') # to get img00.jpg, img01.jpg, . . .
# do something on $file
With some help from ChatGPT, I've been able to write something that filters the output from '*' with string match -r -- $regex -- $file, but the handling of paths is incredibly hard to me:
# bash
for file in ../../*/img??.jpg # How to implement this?
and also the constant need of escaping . in regex is tedious, especially when your path includes ../.
I tried fd -g <glob pattern> but this glob doesn't seem to allow for paths.
I need the good old Glob. Perhaps I should admit the defeat :-) and do this
function glob --description 'Good old glob'
if test (count $argv) -ne 1
echo "Usage: glob <glob_pattern>"
return 1
end
bash -c 'printf "%s\n" $*' _ $argv[1] # call bash!
end
Edit: Initially, I wrote
bash -c "ls $argv[1]"
But a better version was proposed by @CharlesDuffy, which I've replaced mine with, in the above fish function. [Please refer to @CharlesDuffy's discussion below for why printf and what problems exist in $*. But, given that the above solution still has some problems, it's probably better to install this glob command instead of writing a fish function like the above.]
I'm already using the glob.fish function defined as above. So far so good. glob tmp[0-9][0-9].pdf works!
2 Answers 2
I happened to be looking for a solution to this exact problem today...
Standing on the shoulders of everyone in the discussion above (special thanks to Ryo and Charles Duffy), I came up with this improved but somewhat "uglified" and more complex version of OP’s function...
function bash_glob --description 'Expand bash-style glob string into an array'
[ (count $argv) -eq 0 ] && echo 'Usage: bash_glob <pattern>' && return 1
bash -c '
shopt -s nullglob;
IFS=;
eval "printf \"%s\\\\\0円\" $*"
' _ $argv | string split0
end
Example:
me@my-computer ~> bash_glob '.config/[e-n]??[cm]*/README.{md,txt}'
.config/emacs/README.md
.config/nvim/README.md
me@my-computer ~> ▇
Main Changes:
0円as an array separator: Avoids wrong processing of paths containing whitespaces.IFS=as a security measure: Prevents word splitting and potential code injection in Bash.eval: Ensures Bash fully expands$*, and that the glob patterns are processed entirely within Bash.shopt -s nullglob: Prevents unexpanded glob patterns from appearing as extra arguments - this way, only matched files are included in the result.
Only with these changes was I able to get the results I needed, since I did have to handle filenames with spaces, glob patterns that wouldn’t always expand, and other edge cases. This approach avoids most issues, at least.
And I'm not too happy with0円 escaped as \\\\\0円, but this is due to the multiple layers of indirection - each shell's handling of escape sequences and string parsing.
2 Comments
bash -c 'IFS=; printf "%s0円" $*' _ $argv | string split0 -- that way someone trying to pull off an injection attack can't put a command substitution or such into a glob expression and have it be evaluated.I myself use the solution provided by @rsenna (Thank you!) et al, but I post this just to suggest an alternative, which people might be interested in.
This project
https://github.com/p-ranav/glob
provides a glob library. As an example, it also provides a very simple stand-alone command for globbing. Out of the box, it doesn't work like glob <pattern> but it's a very short simple C++ program that can be easily modified.
So, here I report I just modified the source code in a trivial way, compiled everything using the provided make script, and successfully obtained a command line tool that can be an alternative to the fish-shell function shown in other messages.
If I'm very much bored (which wouldn't happen in the near future), I might turn this glob command into a Homebrew package . . .
printfinstead oflswas very deliberate. (Alternate source, written in light of objections to that FAQ). Admittedly, a better solution would useprintf '%s0円'instead ofprintf '%s\n', but I don't know how to make the fish end of that work off the top of my head (specifically, I don't know how to tell fish to expect NUL delimiters when reading a subprocess's output into an array).$*unquoted in bash concatenates all your arguments into a single string, then word-splits that string and expands each element in it as a separate glob -- so if your original argv has items with spaces, those spaces are instead treated as separating individual glob expressions.echo $*you can't tell if a space in the output is intended to separate two distinct items, or is a case where a glob matched a filename with a space as part of its literal content (where that entire filename needs to be understood by the caller as a single object).echo $*will sometimes (but not always!) replace backslash-escape sequences with characters they'd refer to -- this is whatecho -eis explicitly used to request, butecho -eis explicitly disallowed by POSIX from doing anything but printing-eon output; when bash is configured to be more standard-compliant than default and is in XPG mode, it acts as if-ewere enabled by default.