Take the 2-minute tour ×
Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

I'm not really sure how to phrase this, and maybe that's why I can't find any thing, 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.

share|improve this question
2  
Then what is wrong with using the {} of the find command? –  wzzrd Jan 11 '11 at 22:43
    
Sorry, it was given as an example of something similar. However, it's not what I asked. –  Aaron Copley Jan 12 '11 at 14:44

7 Answers 7

up vote 2 down vote accepted
ls /dir |while read dirname; do cp -v /dir/"$dirname"/file "$dirname"-file ; done
share|improve this answer
    
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

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.

share|improve this answer
    
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
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.

share|improve this answer
    
Or zcp which is the same as zmv -C. –  Dennis Williamson Jan 12 '11 at 5:09
    
That alias is not installed by default. You need to choose to create the symlink somewhere in $fpath yourself. –  Phil P Jan 12 '11 at 8:53

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$ 
share|improve this answer
    
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

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
share|improve this answer
    
The only problem is that it moves the files rather than copying them. –  Dennis Williamson Jan 12 '11 at 5: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

It's not pretty:

find -type f -exec sh -c 'cp "{}" "$(basename "$(dirname "{}")")-$(basename "{}")"' \;
share|improve this answer
    
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
    
Got it, thanks for the explanation. I see now! –  Aaron Copley Jan 12 '11 at 15:18

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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