shellglobfish

fish shell: Glob function?


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!


Solution

  • 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:

    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.