1

I have variable "one" which contains following

avi,mkw,dvd,cd

im trying to dynamicly create directories that would look like this

type-avi
type-mkw
type-dvd
type-cd

I have tried to achieve wanted result with following code

mkdir type-{"$one"}

but instead of creating 4 directories , it created one directory called

type-{avi,mkw,dvd,cd}

I suppose this is wrong method.. if so , how can i create dynamicly directories with "suffixes" stored in variabe?

Darlyn
  • 4,715
  • 12
  • 40
  • 90
  • 1
    See https://stackoverflow.com/questions/4956584/sequences-expansion-and-variable-in-bash for why this didn't work. You get to loop manually for this. – Etan Reisner Nov 16 '15 at 16:35
  • indeed i could loop and just use the cut command to get the value , but how can i determine the length if the variable based on "," ? in my example avi,mkw,dvd,cd i would need to return 4 – Darlyn Nov 16 '15 at 16:45
  • `mkdir type-{$one}` works in `ksh` (ver 93+). Note, NO dbl-quotes on `$one` (One of the few times you **don't** want to quote a variable, so it seems ;-) ) . Good luck. – shellter Nov 16 '15 at 16:46
  • @shellter Does that work if the elements of `$one` have spaces in their names or other globs? – Etan Reisner Nov 16 '15 at 16:49
  • Hm... actually that's an interesting trade-off here. Brace expansion means you can correctly glob individual entries at expansion time but an array means you need to move that to globbing to creation time. – Etan Reisner Nov 16 '15 at 16:54
  • @EtanReisner : `one="avi,mkw,dvd,cd,test space"; mkdir type-{$one}` did create the `type-test space` directory. (I'm surprised it did). What sort of glob are you thinking of? Wouldn't that attempt to expand to existing files in the current dir? Good luck to all. – shellter Nov 16 '15 at 17:19
  • @shellter Good to know for spaces (and somewhat surprising, I wonder what the evaluation order is there exactly). Yes, for `mkdir` here a glob doesn't make sense but it would for `type-{$one}/somedir` assuming there were many `type-foo*` directories to include for example. The question was mostly does the glob survive and expand correctly *after* the brace expansion. – Etan Reisner Nov 16 '15 at 18:03

2 Answers2

5

Use an array instead of your string variable for this.

IFS=, read -a onearr <<<"$one"
mkdir "${onearr[@]/#/type-}"

Or if you don't need the $one string in the first place just create the array manually.

onearr=(avi mkw dvd cd)
mkdir "${onearr[@]/#/type-}"

If you aren't worried about spaces or anything in the values in $one and can trust your input to be "safe" and not exploitative and can't use read then you could use this to create the array instead (but it is just flat out a worse soluton).

onearr=($(tr , ' ' <<<"$one"))
Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • if i had variable which would be one="avi,mkw,dvd,cd" how could i create array from it ? ...im not sure if this question is for new topic or not :) – Darlyn Nov 16 '15 at 17:00
  • The first snippet here does exactly that. Notice that is uses `$one`. – Etan Reisner Nov 16 '15 at 17:01
  • is there any way how to do it without read? Im doing this exercises in order to prepare for school and we are not allowed to use read – Darlyn Nov 16 '15 at 17:02
  • thanks! :) one ast quetion why is the order mkdir "${onearr[@]/#/type-}" reversed? and what does # in it mea? – Darlyn Nov 16 '15 at 17:36
  • See [Shell Parameter Expansion](http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion) in the bash manual. – Etan Reisner Nov 16 '15 at 18:04
  • `IFS=, a=($one)` splits the variable by comma, making each extension a value in an array. You can then step through them with `for d in "${a[@]}"; do mkdir type-$d; done` – ghoti Nov 16 '15 at 19:43
  • @ghoti I know. That replaces a single call to `mkdir` with `N` calls to `mkdir` and "leaks" the new value of `IFS` to the environment though. – Etan Reisner Nov 16 '15 at 19:47
  • Hmm, I was thinking of setting IFS for the array assignment, but I realize you'd need `IFS=, declare -a a=($one)` for that. But I take your point on the single call to `mkdir` however. Of course, one could also just `eval mkdir type-{$one}`. :-) – ghoti Nov 16 '15 at 20:00
  • @ghoti That declare version doesn't work either. `$one` is still split by the current shell and not `declare`. You need `IFS=, declare -a a=(\$one)` for this and I'm not sure that doesn't open up other problems (and doesn't really improve anything over using `read` that I can think of so only makes sense in the context of that artificial restriction). I'll reserve comment about my thoughts on `eval`. =) – Etan Reisner Nov 16 '15 at 20:06
  • Huh, your `declare` option also doesn't work. Looks like I am completely out to lunch, sorry for the misdirection. +1 for getting this answer right in the first place. – ghoti Nov 16 '15 at 20:12
  • @ghoti It worked for me (bash 3.2.25, 4.1.2 and 4.2.46 on CentOS 5, 6 and 7 respectively). But ideone (bash 4.3.33) agrees with you http://ideone.com/uOGXOp – Etan Reisner Nov 16 '15 at 20:17
0

A way to do this without reading into the shell, in a traditional tools pipeline approach:

echo "$one" |
tr ',' '\n' | 
sed "s/^/mkdir 'type-/; s/$/'/" |
sh -x

Your original attempt was very close. To make it work, you can use the shell eval command:

eval mkdir type-{$one}

or

echo mkdir type{"$one"} | bash

In either case, the effect causes bash to re-evaluate the line.

I personally would not recommend this approach for these reasons:

  1. eval can be a security risk and is little used, maintainers will have to do a double-take.
  2. Brace Expansion is a bash-type shell extension and while I love bash, I write all shell scripts to run with the POSIX /bin/sh.
  3. These will not handle unusual characters in filenames, such as spaces.

The eval causes the shell to re-evaluate the string after the variable substition has been performed. To gain more understanding on these topics, see "Brace Expansion" and also the eval command, both on the bash man page.

RobertL
  • 365
  • 1
  • 9
  • The final snippet is incorrect. You missed `echo`. And none of these will work with spaces in the contents of the components of `$one`. Not to mention that these all open up the potential for arbitrary code execution if you can't trust the value of `$one`. – Etan Reisner Nov 16 '15 at 20:08
  • @EtanReisner Typo, thanks, fixed. Problems with the last two examples? More reasons not to use them. Answer updated, thanks! – RobertL Nov 16 '15 at 20:27
  • @EtanReisner In the first example, what happens when there are "spaces in the contents of the components of $one"? Did you try it? – RobertL Nov 16 '15 at 20:29
  • Non-newline spaces in the first example are fine. Newlines break though. And it still allows for arbitrary code execution. Imagine `one` was `foo'; touch /tmp/broken; : '`? – Etan Reisner Nov 16 '15 at 20:32
  • @EtanReisner _Non-newline_ spaces? Sounds like back pedaling to me. Why not say "newlines"? I understand what you mean by "arbitrary code execution." The likelihood of newlines and arbitrary code execution varies widely by the context within which the code is used. – RobertL Nov 16 '15 at 20:46
  • I failed to notice the single-quote wrapping when I wrote my original comment, mea culpa. If you want to call a valid sub-case of my original comment "back-pedalling" that's up to you. The newline case is valid. These options are all unsafe for arbitrary input and costlier then doing this in a safer way using `read` and `IFS`. And you don't know the full context for this code or who *else* will read it or where *else* the OP will use it. So while it may be "safe" enough here it has problems from an *objective* standpoint that I chose to mention. – Etan Reisner Nov 16 '15 at 20:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/95288/discussion-between-robertl-and-etan-reisner). – RobertL Nov 16 '15 at 23:47