2

In zsh, with glob expansion we can use the P flag to prepend each match with some text:

$ ls -1
bar
baz
foo
$ print -- *(P:--file:)
--file bar --file baz --file foo

Is there something analogous for array parameters? If I write

items=(foo bar baz)

is there some flag I can use to get behavior like this?

$ print -- ${(...)items}
--file foo --file bar --file baz
3

The best answer depends on what you need the modified array for.

  1. If you’re generating command-line arguments (as in the question), and you’re using a tool that accepts flags like --file=foo as an alternative to --file foo, the most idiomatic approach is to use the ${^name} form of expansion:

    $ items=(foo "two words" baz)
    $ print -lr -- --file=${^items}
    --file=foo
    --file=two words
    --file=baz
    

    I’m using print -lr to demonstrate that the expansion produces an array with three elements. If you call a command like this, the command will see three arguments.

  2. If you’re generating command-line arguments, you can put the text to prepend (--file in this example) in a dummy array and combine that with the arguments array using the ${name:^^arrayname} form of expansion:

    $ flag=(--file)
    $ items=(foo "two words" baz)
    $ print -lr -- ${flag:^^items}
    --file
    foo
    --file
    two words
    --file
    baz
    

    This is an array with six elements. Either this format or the one in (1)—or both—should be acceptable to just about any command-line tool for passing flags with associated values.

  3. You can modify the approach in (1) to use a space instead of an equals sign:

    $ items=(foo "two words" baz)
    $ print -lr -- --file\ ${^items}
    --file foo
    --file two words
    --file baz
    

    (Note that you have to escape the space with a backslash; using double quotes like "--file ${^items}" means something else.) This is an array with three elements but with an embedded space in each element. Few, if any, command line tools will accept flags in this format, but it could be useful for other kinds of text manipulation.

  4. You can also use the ${name/pattern/repl} form of expansion. The pattern should start with # (to indicate that it needs to match at the beginning of each array element) but it should otherwise be empty (so that every array element will match and so that no text is actually replaced).

    $ items=(foo "two words" baz)
    $ print -lr -- ${items/#/--file }
    --file foo
    --file two words
    --file baz
    

    This has the same caveats as (3), and it’s also less idiomatic than that approach.

Note that all four of these approaches will do the right thing (i.e., they won’t suddenly split strings on spaces) when the array elements contain spaces.

  • 1
    @StéphaneChazelas That’s good to know, thank you. And you made me realize that the “embedded space” problem I mentioned is irrelevant as long as the command accepts --flag=value as an alternative to --flag value. – bdesham Mar 9 at 1:15
  • @StéphaneChazelas I’ve rewritten the answer based partially on your feedback. – bdesham Mar 23 at 20:25
2

You can use the P glob modifier! Everything can be a glob if you insist. Note that I'm not saying that I'd really want to do this. I can't think of a situation where it's the best way. But it is technically possible.

% items=(foo bar baz)
% print -lr -- /(P:--file:e\''reply=($items[@])'\')
--file
bar
--file
baz
--file
foo

It works by making a glob that matches exactly one file name (/ will do nicely), using the glob qualifier e to replace the one match by an arbitrary list, and applying the P qualifier to the result.

This only works in contexts where globbing is performed, but that's usually a case where an array is allowed.

  • This is a fascinating abuse of glob modifiers 😄 Thanks for demonstrating it! – bdesham Mar 23 at 20:27
1

You can do this portably (or, at least, portably to shells that support this array syntax) with printf:

$ items=(foo bar baz)
$ printf -- "--file %s " "${items[@]}"
--file foo --file bar --file baz $ 

Unlike the print command, however, that doesn't add a trailing newline (which is why you have the $ on the same line as the output, above). If that's a deal breaker, you can do:

$ printf -- "--file %s " "${items[@]}"; echo ""
--file foo --file bar --file baz 
  • There is no need for echo "". A simpler echo is good enough. – Isaac Mar 14 at 1:43

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.