2

I've seen a bunch of similar questions to this one but none exactly the same.

I have a directory of files which I want to pass in as a single, double-quoted argument to a command, using wildcard expansion.

Files

a.ext
b.ext
c.ext

Desired Use

command --flag "a.ext b.ext c.ext"

Currently, I'm using command --flag $(echo \"$(echo ./*.ext)\"), but that doesn't work and also seems unnecessarily complex. Is there a simple way to do this?

3
  • What do you mean by "using wildcard expansion"? Can you give an example of using it, and what it should do? BTW, passing filenames in a single space-delimited string is generally a bad idea, because filenames can contain spaces. Aug 27 '20 at 4:34
  • The command I gave is the example, specifically ./*.ext. In this case I'm ok with a space delimited string since I am generating all of the filenames myself. Aug 27 '20 at 4:49
  • Are you trying to turn a wildcard pattern into a space-separated list of matching filenames? If so, is it a fixed pattern (i.e. always ./*.ext), or in a variable or parameter or something? Also, note that ./*.ext would expand to something like ./a.ext ./b.ext ./c.ext (i.e. since ./ is specified as part of the pattern, it'll also appear in the result). Aug 27 '20 at 4:58
2

If you want to join the filenames resulting of the expansion of a glob with SPC characters, you can store them in the positional parameters and use "$*" to do the joining:

set -- ./*.ext
IFS=' ' # SPC is the first character of $IFS by default, we're setting it
        # here in case that code is called in a context where it has been
        # modified before.
files_joined_with_SPC="$*"
cmd --flag "$files_joined_with_SPC"

Or:

cmd --flag "$*"

directly of course (though it's easier to lose track of the fact that it depends on the current value of $IFS).

The above is standard POSIX sh syntax. In zsh/ksh93/bash/mksh/yash, you can also use an array in place of the positional parameters:

files=(./*.ext)
IFS=' '; files_joined_with_SPC="${files[*]}"

With zsh, you can also use:

files=(./*.ext)
cmd --flag "${(j[ ])files}"

Or using an anonymous function:

(){ cmd --flag "${(j[ ])argv}"; } ./*.ext

Where we explicitly request the joining with SPC without having to rely on a global parameter like $IFS.

Now, note that SPC is as valid a character as any in a file path. Any character but NUL is valid in a file path (actually any byte value but 0 on most systems, those bytes don't even have to form valid characters), but NUL cannot be passed in an argument to a command that is executed.

You don't say what that command is (by the way command is the name of a standard shell builtin command, I prefer to use cmd for a placeholder), but if that cmd --flag list is meant to accept any list of file name, SPC delimited, they must have a way to let users specify a SPC in the file name.

That could be cmd --flag 'with\ space.ext other.ext' or cmd --flag 'with%20space.ext other.ext, cmd --flag 'with\040space.ext other.ext', etc.

In which case, when building that argument to cmd --flag, you may have to escape that SPC (and possibly \ or % characters) in file names before joining them with SPC.

In ksh93/zsh/bash/yash, that could be done with:

escaped_files=("${files[@]//\\/\\\\}") # \ escaped as \\
escaped_files=("{files[@]// /\\ }")    # SPC escaped as \SPC
# more characters may need to be escaped such as other whitespace
# or quoting characters, depending on the exact syntax expected by
# cmd for the --flag option.

IFS=' '; escaped_files_joined_with_SPC="$*"
cmd -- flag "$escaped_files_joined_with_SPC"

(though beware of locales that use a character encoding such as GB18030, BIG5... where some characters contain the encoding of backslash (byte 0x5c). Your shell may not escape those 0x5c byte but cmd --flag might still consider them as backslash if it's not decoding the arguments as per the locale's charset).

More reliable/convenient ways for commands to get a list of file names is to just take them as separate arguments (so not as arguments to an option), or as cmd --flag 'file 1.ext' --flag 'file 2.ext'....

2

To get all the filenames in one quoted string:

command --flag "$(echo ./*.ext)"

As you know, this only works for simple filenames. This means no whitespace in the file names. Depending on the value of xpg_echo, it may also mean no backslashes (hat tip: kusalananda).

8
  • Depending on whether the xpg_echo shell option is set or not, names containing backslashes may also be modified by the act of using echo to output them.
    – Kusalananda
    Aug 27 '20 at 6:26
  • @Kusalananda Very good point. Answer updated to mention xpg_echo.
    – John1024
    Aug 27 '20 at 6:55
  • And as usual with echo, you need to make sure the pattern doesn't match files like -n, -e, -Enen..., and as usual with command substitution, newline characters at the end of filenames would also be a problem. Aug 27 '20 at 6:59
  • 1
    @pLumo. "$(printf '%s ' *)" would address both the echo and command substitution issues, but as that would end in a SPC character, there's the question of whether command --flag would consider "file " as one "file" file, or the "file" and "" files separated by a SPC. Aug 27 '20 at 7:19
  • 1
    This actually solved my problem, so I'm using it, but I accepted the other answer as it goes much more in-depth into the general solution. Aug 27 '20 at 15:35
1

setup a for loop of the glob expanded files. Let the shell variable named var be the container for the filenames. In every iteration we update the shell variable var

var=
for f in *.ext; do var=${var-}${var:+ }$f; done
your_command --flag "$var"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.