3

I'm not really sure how to phrase this, and maybe that's why I can't find anything, but I want to reuse the values enumerated by a wildcard in a command. Is this possible?

Scenario:

$ ls /dir 1 2 3 Contents of /dir are directories 1, 2, and 3.

$ cp /dir/*/file .

Results in file being copied from /dir/1 /dir/2 and /dir/3 to here.

What I would like to do is copy the files to a new destination name based on the wildcard expansion.

$ cp /dir/*/file ???-file would result in /dir/*/file being copied to 1-file, 2-file, and 3-file. What I can't figured out is the ??? portion to tell Bash I want to use the wildcard-expanded values.

Using the wildcard in the target nets a cp error:

cp: target `*-file' is not a directory.

Is there something else in Bash that can be used here?

The find command has {} to use with -exec which is similar to what I am looking for above.

Aaron Copley
  • 12,525
  • 5
  • 47
  • 68

7 Answers7

3

My first thought is you are probably making this problem too complicated. However, it is solvable.

You can do something like this with a crazy bash loop:

for i in /dir/*/file
do
  j=${i%/file}
  k=${j#/dir/}

  cp $i %k-file
done

that uses a couple of bash string operators to extract the number of the source directory.

I guarantee there are waaay better ways to do this, that's just what came to mind.

Phil Hollenback
  • 14,947
  • 4
  • 35
  • 52
  • I can't assume that the source directory is just a number. Sorry, that was only an example. But, the code is clean, easy to understand, and appears to work. +1 anyway. – Aaron Copley Jan 12 '11 at 14:53
  • Note that my answer works for any source directory, not just numbered dirs. However I think the answer you selected as the answer is more elegant than mine. – Phil Hollenback Jan 12 '11 at 16:25
2
zsh% autoload zmv       # should be done for you, noting it just in case
zsh% zmv -C '(*)/file' '$1-file'

zmv is a shell function, often used via alias mmv='noglob zmv -W' for an even easier invocation, but in this case you want the regular usage, plus -C to copy instead of move.

Phil P
  • 3,080
  • 1
  • 16
  • 19
2
ls /dir |while read dirname; do cp -v /dir/"$dirname"/file "$dirname"-file ; done
nitins
  • 2,579
  • 15
  • 44
  • 68
  • Very simple and works. It seems there isn't something in BASH like I imagined so this is going to be close enough. Thanks! – Aaron Copley Jan 12 '11 at 14:55
  • 2
    @Aaron: There are several ways this can fail. One of which is if there are spaces in the directory names. You can come closer to making this work reliably by doing `for dirname in */; do` – Dennis Williamson Jan 12 '11 at 15:14
  • I definitely won't have to worry about spaces. Thanks for the suggestion though. I'll play around with that too. – Aaron Copley Jan 12 '11 at 15:17
0

As Phil has said, you'll need to iterate over the elements of the expanded wildcard; extracting the bit you care about (in your example, the name of the directory under dir/) and then acting on that extracted value.

I've got a different quick and hacky way to do this below. The obvious bug is that this is going to break if there are spaces in filenames, but this is not the only bug.

andromeda:tmp polleyj$ find dir/ ?-file
dir/
dir//1
dir//1/a
dir//1/b
dir//1/c
dir//2
dir//2/d
dir//2/e
dir//2/f
dir//3
dir//3/g
dir//3/h
dir//3/i
1-file
2-file
3-file
andromeda:tmp polleyj$ for f in dir/*; do
> i=$(basename $f)
> mv dir/${i}/* ${i}-file/
> done
andromeda:tmp polleyj$ find dir/ ?-file
dir/
dir//1
dir//2
dir//3
1-file
1-file/a
1-file/b
1-file/c
2-file
2-file/d
2-file/e
2-file/f
3-file
3-file/g
3-file/h
3-file/i
andromeda:tmp polleyj$ 
James Polley
  • 2,089
  • 15
  • 13
  • I think we can all agree no matter what way you try to solve this it's pretty ridiculous. :) – Phil Hollenback Jan 11 '11 at 23:38
  • I think there are probably less ridiculous ways to solve it - a quick python script would probably do a better job of the string manipulation, and handle filenames-with-spaces better... but really, this is a quick and dirty task; unless you're doing the same thing over and over there's no need to get pretty. – James Polley Jan 11 '11 at 23:48
0

On most distros (using util-linux's rename):

rename /file -file /dir/*/file

On Debian and Ubuntu (blame Debian):

rename.ul /file -file /dir/*/file

Or using Perl's rename (on Debian and Ubuntu, I don't know where to find it in other distros):

rename 's,(\d+)/file,$1-file,' */file
Juliano
  • 5,512
  • 28
  • 28
  • The only problem is that it moves the files rather than copying them. – Dennis Williamson Jan 12 '11 at 05:06
  • Right, and this only works for this command. I was hoping there would be something in the shell that could be used with other commands. It's hard to explain why I frequently need this so I tried to simplify the example. – Aaron Copley Jan 12 '11 at 14:51
0

It's not pretty:

find -type f -exec sh -c 'cp "{}" "$(basename "$(dirname "{}")")-$(basename "{}")"' \;
Dennis Williamson
  • 62,149
  • 16
  • 116
  • 151
  • Just out of curiosity, what's the 'sh -c' doing? – Aaron Copley Jan 12 '11 at 14:57
  • @Aaron: It is running the `cp` command in a new subshell. It's kind of like creating a shell script on the fly. The reason for it here is to make the multiple `{}` work and keep the `basename` and `dirname` commands (in the `$()` command substitution) from being evaluated too soon. – Dennis Williamson Jan 12 '11 at 15:11
0

If I got your point, mcp is doing what you want to.

Hint: mcp belongs to the mmv package.

Your situation:

$ tree
.
├── dir
│   ├── 1
│   │   └── foo
│   ├── 2
│   │   └── bar
│   └── 3
│       └── baz
└── dst

5 directories, 3 files

The solution:

$ mcp -v 'dir/*/*' 'dst/#1-#2'
dir/1/foo -> dst/1-foo : done
dir/2/bar -> dst/2-bar : done
dir/3/baz -> dst/3-baz : done

$ tree
.
├── dir
│   ├── 1
│   │   └── foo
│   ├── 2
│   │   └── bar
│   └── 3
│       └── baz
└── dst
    ├── 1-foo
    ├── 2-bar
    └── 3-baz

5 directories, 6 files

I think it should be mostly self-explanatory

What happens here is that mcp takes a from and a to argument. The from argument can contain shell wildcard patterns (*,?) that match a set of files. These matches are backreferenced in the to pattern in an ordered way as #1, #2, #3 and so forth.

Michuelnik
  • 3,410
  • 3
  • 19
  • 24