Search code examples
command-line-argumentsglobrakucommand-line-tool

How do I use raku -e and -n with multiple file glob


I'd like to do the following in raku on windows

raku -n -e ".say if /mydatabegin/;" *.file

Failed to open file C:\..\*.file: Invalid argument

The glob isn't interpreted as a glob. I assume that's because windows requires your programs to do the globbing yourself? So is there a pre-processing directive or function or even a switch I might have missed or redirect or something that allows the glob to be expanded while keeping the simplicity of the -n (or -p) and -e switches?

Obviously, I can change it to a full program by removing the -n (or -p), just using -e to specify a main, and loop on the glob results. But I really like -n.

PS. I'm literally just learning raku, and was surprised this didn't work out of the box. So examples of full programs with easy syntax work also. But I really like -n..

Edit: re @chenyf

raku -e ".say for $*ARGFILES" *.file

Same error. Related:

raku -e ".say for $*ARGFILES.lines" *.file

Same error.

raku -e "use IO::Glob; .say for glob('*.file')"

Worked as expected! Expanding:

raku -e "use IO::Glob; .say for glob('*.file').lines"

No such method 'lines' for invocant of type 'IO::Glob'

Getting closer - perhaps expanding on this is a good enough workaround. But returning to one line glory attempts:

raku -e "use IO::Glob; .say for glob($*ARGFILES)" test.file

Cannot resolve caller glob(IO::ArgFiles:D); none of these signatures match.

Ok - let's retreat back to the safety of strings:

raku -e "use IO::Glob; .say for glob($*ARGFILES.Str)" test.file

Yes! SO..:

raku -e "use IO::Glob; .say for glob($*ARGFILES.Str).lines" test.file

No such method 'lines' for invocant of type 'IO::Glob'

I clearly need to read more of the manual. But let's retreat a little and see if my use case works:

raku -e "use IO::Glob; .say for glob($*ARGFILES.Str)" *.file

Failed to open file C:\..\*.file: Invalid argument

The same error I started off with. Could this just be a raku on windows error?

Edit:

raku -MIO::Glob -e "my @files = (map { glob($_).dir }, @*ARGS).flat; for @files -> $file { say $_ for $file.lines }" *file *file2 *5

I have three sets of files. I can almost live with this solution - except for some reason the lines are being printed with "s

Any ideas on shortening, and getting rid of the quotes?

EDIT Working around the auto-globbing of the $*ARGFILES variable:

raku -MIO::Glob -n -e "BEGIN { @*ARGS = (map { glob($_) }, @*ARGS).flat }; .say" *.file *.file2

This has the advantage of still looking like the original one liner; it uses -n! It just had to do the globbing that seems to be a bug when $*ARGFILES is created.

raku -MIO::Glob -e "BEGIN { @*ARGS = (map { glob($_) }, @*ARGS).flat }; .say for $*ARGFILES.lines" *.file *.file2

Converting to $*ARGFILES.lines above shows that $*ARGFILES gets its values from @*ARGS dynamically.

EDIT

lastly, it turns out the glob function doesn't work with directories, at least on windows (the documentation has an example that simply doesn't work).

#Example from https://github.com/zostay/raku-IO-Glob
for glob("src/core/*.pm") -> $file { say ~$file }

#mine that doesn't work
raku -MIO::Glob -e "for glob('..\*.file') -> $file { say ~$file }"

#mine that does work.
raku -MIO::Glob -e "for glob('*.file').dir('..') -> $file { say ~$file }"

#And therefore the final modification of the script above:
raku -MIO::Glob -e "BEGIN { @*ARGS = (map { glob(.IO.basename).dir(.IO.dirname) }, @*ARGS).flat };  .say for $*ARGFILES.lines" ..\*.file

Solution

  • The general answer - Its not a bug. Windows programs have to deal with their own globbing if they want it. Making it work in the raku executable makes sense to me; it removes platform specific surprises, and makes one-liners easier.

    But others didn't see it that way, and there is an easy enough solution - create your own module so that the code can remain consistent and be called relatively simply.

    Here's a module for starters. There is room to add things like

    • a switch for making a successful match mandatory
    • a switch to indicate that a failed glob should stay in the @*ARGS variable
    • a switch to only glob after it.
    • a routine to apply the globbing instead of automatically doing it. This would allow you to remove the non-file switches
    • gather each glob into its own list (perhaps with a switch).

    The module:

    unit module CLGlob:ver<1.0>:auth<pureabsolute>;
    
    use IO::Glob;
    
    @*ARGS = map { .Str }, (map { glob(.IO.basename).dir(.IO.dirname) }, @*ARGS ).flat;
    

    Note: the expression in the second map can be simplified when the glob function works for windows with directories.

    Note: I convert each element to .Str to make @*ARGS consistent. The $*ARGFILES worked without doing that so some processing can be saved if you'll never look at the @*ARGS again.

    Finally, the end result:

    raku -MCLGlob -ne ".say" ..\*.file ..\*.file2
    

    Yay.