Reputation: 69
In the directories ~/temp/a/foo/
and ~/temp/b/foo foo/
I have some files named bar1
, bar2
, bar bar1
, bar bar2
, etc.
I am trying to write a line of Bash that copies all these files in a directory containing "foo" as last part of the name to the folder above the respective "foo" folder.
As long as there are no spaces in the file names, this is an easy task, but the two following commands fail when dealing with the foo foo
directory:
for dir in `find . -type d -name '*foo'` ; do cp $dir/* "$(echo $dir|sed 's_foo__g')" ; done
(The cp
command fails to see the last foo
of "foo foo"
as part of the same directory name.)
for dir in `find . -type d -name '*foo'` ; do cp "$dir/*" "$(echo $dir|sed 's_foo__g')" ; done
("$dir/*"
is not expanded.)
Attempts like replacing $dir/*
with "$(echo $dir/*)"
have been even less successful.
Is there an easy way to expand $dir/*
so that cp
understands?
Upvotes: 5
Views: 2277
Reputation: 295825
Not only is a for
loop wrong -- sed
is also not the right tool for this job.
while IFS= read -r -d '' dir; do
cp "$dir" "${dir/foo/}"
done < <(find . -type d -name '*foo' -print0)
Using -print0
on the find
(and IFS= read -r -d ''
) ensures that filenames with newlines won't mess you up.
Using the < <(...)
construct ensures that if the inside of your loop sets variables, changes directory state, or does similar things, those changes will survive (the right-hand side of a pipeline is in a subshell in bash, so piping into a while loop would mean that any changes to the shell's state made inside that while loop would be discarded on its exit otherwise).
Using ${dir/foo/}
is vastly more efficient than invoking sed
, as it does the string replacement internal to bash.
Upvotes: 3
Reputation: 124804
The problem here is not with cp
, but with for
, because by default it splits the output of your subshell by words, not by directory names.
A lazy workaround is to use while
instead and process the list of directories line by line like this:
find . -type d -name '*foo' | while read dir; do cp "$dir"/* "$(echo $dir | sed 's_foo__g')" ; done
This should fix your problem with spaces, but this is by no means a foolproof solution.
See Charles Duffy's answer for a more accurate solution.
Upvotes: 2
Reputation: 26204
Rename ~/temp/b/foo foo/
to something without spaces, e.g. ~/temp/b/foo_foo/
and do what you were trying to do again. After that you can rename it back to the original, with space, if you really have to. BTW. myself I never use file names containing spaces, simply to avoid complications like the one you are facing now.
Upvotes: -1