0
  • I want to create 12 directories named after the months. So I tried using locale to get the month names, piping to tr to separate them with commas, then put braces around and passed to mkdir. But the whole is treated as one string. Is there a way to avoid this?
16:07: temp ⧲ mkdir {$(locale mon | tr \; ,)}

16:18: temp ⧲ ls -lh total 4.0K drwxr-xr-x. 2 john john 4.0K Dec 11 16:18 {January,February,March,April,May,June,July,August,September,October,November,December}

16:18: temp ⧲

teraspora
  • 111

4 Answers4

5

Use ";" as the input field separator to split the output of locale man:

IFS=';'
mkdir -- $(locale mon)
Stephen Kitt
  • 434,908
B215826
  • 406
3

You can't use brace expansion like that, use xargs instead:

locale mon | tr ';' '\n' | xargs mkdir
user000001
  • 3,635
2

Usually, you don't use brace expansion with command substitution (or parameter expansion) like that.

Not in Bash, because Bash quite inconveniently expands brace expansion first; and not in zsh, since while zsh expands command substitutions and parameter expansions before brace expansions, it requires literal braces and commas for brace expansion.

You could do it ksh, though, as it doesn't seem to care where the commas and braces come from. This works just as you attempted:

ksh$ mkdir {$(locale mon | tr \; ,)}

To compare:

 # this brace expands
ksh$ x=abc,def; echo {$x}
abc def

increasingly silly, but also expands

ksh$ x="foo{abc" y=",def}"; echo $x$y fooabc foodef

it doesn't work like that in zsh

zsh% x=abc,def; echo {$x} {abc,def}

this works sensibly though, the comma is just part of the data

zsh% x=abc,def y=ghi; echo {$x,$y}xyz abc,defxyz ghixyz

this also makes sense

zsh% a=1 b=4; echo {$a..$b} 1 2 3 4

but Bash just fails with it

bash$ a=1 b=4; echo {$a..$b} {1..4}


Though if you really wanted, you could of course do it with eval, even in Bash:

eval "mkdir {$(locale mon | tr \; ,)}"

Though that would come with all the usual caveats about processing untrusted input, so you'd be far, far better off just using e.g. the solutions in the other answers.

ilkkachu
  • 138,973
1

{x,y} is brace expansion (initially from csh in the 70s), not parameter expansion.

In the fish shell, list expansion behaves like csh parameter expansion.

For instance:

> echo //(string split -- ';' (locale mon))//
//January// //February// //March// //April// //May// //June// //July// //August// //September// //October// //November// //December//

Same as:

% echo //{January,February,...}//
//January// //February// //...//

in csh (or shells that have copied that feature from csh).

In fish, command substitution is with (cmd) instead of `cmd` in Bourne/csh or $(cmd) in Korn like shells.

It's also the case in the rc shell in this case:

; echo //^``(';
'){locale mon}^//
//January// //February// //March// //April// //May// //June// //July// //August// //September// //October// //November// //December//

In rc, command substitution is with `cmd or `{more complex cmd}, and there's a ``(seps){cmd} form to specify the list of separators instead of $ifs. You'll notice here we use both ; and newline as we don't want the trailing newline output by locale mon to be included in the last element. fish splits on newlines and its string split outputs each element one per line¹

Where it differs from fish's expansion or csh-style brace expansion is when two lists with a number of elements other than 1 are concatenated together:

$ rc -c 'echo `{seq 3}^`{seq 3}'
11 22 33
$ fish -c 'echo (seq 3)(seq 3)'
11 12 13 21 22 23 31 32 33
$ fish -c 'echo (seq 2)(seq 3)'
11 12 13 21 22 23
$ rc -c 'echo `{seq 2}^`{seq 3}'
rc: line 0: bad concatenation

zsh behaves like rc or actually more like fish or csh-style brace expansion for its parameter expansions when the rcexpandparam option is on. And that style of expansion can be enabled on a per-expansion basis using the $^param syntax.

It's also possible there to have command substitutions within parameter expansions:

$ echo //${(s[;])^"$(locale mon)"}//
//January// //February// //March// //April// //May// //June// //July// //August// //September// //October// //November// //December//

There, the $(cmd) command substitution strips trailing newline characters like in most other shells and the s[;] parameter expansions flag here applied to the quoted substitution splits on ; instead of relying on $IFS.

zsh also has builtin support to get the list of localised month names in its zsh/langinfo module:

$ zmodload zsh/langinfo
$ echo //${(v)^langinfo[(I)MON_<1-12>]}//
//January// //February// //March// //April// //May// //June// //July// //August// //September// //October// //November// //December//

¹ having elements contain newline is possible but very cumbersome there.