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!
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 ~> ▇
\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 with\0
escaped as \\\\\\0
, but this is due to the multiple layers of indirection - each shell's handling of escape sequences and string parsing.