I have a folder named /home/user/temps
which has 487 folders.
In each folder I have a file called thumb.png.
I want to copy all files named thumb.png to a separate folder and rename them based on the folder they came from.
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communityI have a folder named /home/user/temps
which has 487 folders.
In each folder I have a file called thumb.png.
I want to copy all files named thumb.png to a separate folder and rename them based on the folder they came from.
Here you go:
for file in /home/user/temps/*/thumb.png; do new_file=${file/temps/new_folder}; cp "$file" "${new_file/\/thumb/}"; done;
edit:
the canonical wisdom, by the way, is that using find
for this is a bad idea -- simply using shell expansion is much more reliable. Also, this assumes bash
, but I figure that's a safe assumption :)
edit 2:
for clarity, I'll break it down:
# shell-expansion to loop specified files
for file in /home/user/temps/*/thumb.png; do
# replace 'temps' with 'new_folder' in the path
# '/home/temps/abc/thumb.png' becomes '/home/new_folder/abc/thumb.png'
new_file=${file/temps/new_folder};
# drop '/thumb' from the path
# '/home/new_folder/abc/thumb.png' becomes '/home/new_folder/abc.png'
cp "$file" "${new_file/\/thumb/}";
done;
details on the ${var/Pattern/Replacement}
construct can be found here.
the quotes in the cp
line are important to handle spaces and newlines etc. in filenames.
new_file/\/thumb
?
Apr 20, 2011 at 17:07
${VARIABLE/PATTTERN/REPLACEMENT}
produces the value of VARIABLE
but with the pattern replaces by the replacement text. Here the pattern is /thumb
(the /
needs to be escaped so that it doesn't look like ${new_file//PATTERN/REPLACEMENT}
, which makes it a global replacement instead of a first-occurrence replacement) and the replacement text is empty.
Apr 20, 2011 at 20:43
/home/user/temps
, which is not clear from the question. If they aren't, you need find or **
.
Apr 21, 2011 at 7:00
This solution "flattens" the files in sub- and subsubdirectories so they all sit in one big directory. The new file name reflects the original path. For example temps/dir/subdir/thumb.png
will become newdir/temps_dir_subdir_thumb.png
.
find temps/ -name "thumb.png" | while IFS= read -r f
do
cp -v "$f" "newdir/${f//\//_}"
done
can also be done in one line
find temps/ -name "thumb.png" | while IFS= read -r f; do cp -v "$f" "newdir/${f//\//_}"; done
note the semicolons (;
).
example output
$ find temps/ -name "thumb.png" | while IFS= read -r f; do cp -v "$f" "newdir/${f//\//_}"; done
`temps/thumb.png' -> `newdir/temps_thumb.png'
`temps/dir3/thumb.png' -> `newdir/temps_dir3_thumb.png'
`temps/dir3/dir31/thumb.png' -> `newdir/temps_dir3_dir31_thumb.png'
`temps/dir3/dir32/thumb.png' -> `newdir/temps_dir3_dir32_thumb.png'
`temps/dir1/thumb.png' -> `newdir/temps_dir1_thumb.png'
`temps/dir2/thumb.png' -> `newdir/temps_dir2_thumb.png'
`temps/dir2/dir21/thumb.png' -> `newdir/temps_dir2_dir21_thumb.png'
You must execute the command from the parent directory of temps
. Also newdir
must exists and must be a sibling directory of temps
. These can deviate but then you must be really carefull how to write the command.
If you are unsure then prepend cp
with echo
to see what is going to be executed.
find temps/ -name "thumb.png" | while IFS= read -r f; do echo cp -v "$f" "newdir/${f//\//_}"; done
If you are really sure and don't the need the one line per copy anymore then drop the -v
from cp
.
explanation
This works using parameter expansion: ${f//\//_}
. It takes the content of the variable f
(which contains the filename with path) and replaces every occurence of /
with _
.
Note that this is a dumb text search and replace. In some rare cases two distinct file might end up to the same name. If that happens one of the files will overwrite the other.
For example two files temps/dir/thumb.png
and temps/dir_thumb.png
. Both files will be renamed to temps_dir_thumb.png
. So one file will be lost. Which file will be lost is dependent on the order of how find
found them on disk.
Also note: obligatory pedantic warning: if your filenames contain newlines this command will break horribly.
You can find, copy, and rename files in oneliner command with -exec sh:
find /home/user/temps -name thumb.png \
-exec sh -c 'cp "{}" "$(basename "$(dirname "{}")")_$(basename "{}")"' \;
(The extra "
are meant to deal with copying files with spaces).
Option 2 - with xargs
(it can print each command before it is executed):
find /home/user/temps -name thumb.png \
| xargs -I {} --verbose sh -c 'cp "{}" "$(basename "$(dirname "{}")")_$(basename "{}")"'
sh -c cp "temps/thumb.png" "$(basename "$(dirname "temps/thumb.png")")_$(basename "temps/thumb.png")"
sh -c cp "temps/dir one/thumb.png" "$(basename "$(dirname "temps/dir one/thumb.png")")_$(basename "temps/dir one/thumb.png")"
sh -c cp "temps/dir two/thumb.png" "$(basename "$(dirname "temps/dir two/thumb.png")")_$(basename "temps/dir two/thumb.png")"
Short helper code:
#!/bin/bash
#
# echo cp "$1" ../tmp/"${1//\//_}"
#
mv "$1" ../tmp/"${1//\//_}"
let's name it 'deslash.sh' and make it executable. Call it with:
find -type f -name thumb.png -exec ./deslash.sh {} ";"
It will fail, if a collision exists
a/b/thumb.png # and
a_b/thumb.png
but that's unavoidable.
Try this
mkdir /home/user/thumbs
targDir=/home/user/thumbs
cd /home/user/temps
find . -type d |
while IFS="" read -r dir ; do
if [[ -f "${dir}"/thumb.png ]] ; then
echo mv -i "${dir}/thumb.png" "${targDir}/${dir}_thumb.png"
fi
done
Edit
I have added quoting in case any of your dir names have white-space chars embedded in them.
Also I have changed this so it will only print out the commands to be executed.
Examine the output of the script to be sure all files/path names look proper.
When you're sure there are no issues with the commands that will be executed, remove the echo
.
mv -i ./A A/thumb.png ../tmp/.png
- all files end in ../tmp/.png (../tmp is my target dir).
Apr 20, 2011 at 17:14
find -type d
and see the output. Does that get all the directories you expect? It should, if not, please add some output to show the problem. Good Luck.
read dir
performs word splitting, and takes, from a directory a\nb
a and b as two instances, which no masking can solve.
Apr 20, 2011 at 21:42
To copy you need the command cp, and to rename for linux is the same than moving the file, so you have to do it with mv command. In Linux you always have to specify the whole path, from the source, if you are in another folder, and to the destination folder, of course. I'd be something like this, for copy:
cp /source_path/file /destination_path/
or to rename or move
mv /source_path/old_file /destination_path/new_name_file
find