Move only files recursively from multiple directories into one directory with mv - bash

I currently have ~40k RAW images that are in a nested directory structure. (Some folders have as many as 100 subfolders filled with files.) I would like to move them all into one master directory, with no subfolders. How could this be accomplished using mv? I know the -r switch will copy recursively, but this copies folders as well, and I do not wish to have subdirectories in the master folder.

If your photos are in /path/to/photos/ and its subdirectories, and you want to move then in /path/to/master/, and you want to select them by extension .jpg, .JPG, .png, .PNG, etc.:
find /path/to/photos \( -iname '*.jpg' -o -iname '*.png' \) -type f -exec mv -nv -t '/path/to/master' -- {} +
If you don't want to filter by extension, and just move everything (i.e., all the files):
find /path/to/photos -type f -exec mv -nv -t '/path/to/master' -- {} +
The -n option so as to not overwrite existing files (optional if you don't care) and -v option so that mv shows what it's doing (very optional).
The -t option to mv is to specify the target directory, so that we can stack all the files to be moved at the end of the command (see the + delimiter of -exec). If your mv doesn't support -t:
find /path/to/photos \( -iname '*.jpg' -o -iname '*.png' \) -type f -exec mv -nv -- {} '/path/to/master' \;
but this will be less efficient, as one instance of mv will be created for each file.
Btw, this moves the files, it doesn't copy them.
Remarks.
The directory /path/to/master must already exist (it will not be created by this command).
Make sure the directory /path/to/master is not in /path/to/photos. It would make the thing awkward!

Make use of -execdir option of find:
find /path/of/images -type f -execdir mv '{}' /master-dir \;
As per man find:
-execdir utility [argument ...] ;
The -execdir primary is identical to the -exec primary with the exception that
utility will be executed from the directory that holds the current
file. The filename substituted for the string ``{}'' is not qualified.
Since -execdir makes find execute given command from each directory therefore only base filename is moved without any parent path of the file.

find <base location of files> -type -f -name \*\.raw -exec mv {} master \;

If your hierachy is only one level deep, here is another way using the automated tools of StringSolver:
mv -a firstfolder/firstfile.raw firstfile.raw
The -a options immediately applies the similar transformation to all similar files at a nesting level 1 (i.e. for all other subfolders).
If you do not trust the system, you can use other options such as -e to explain the transformation or -t to test it on all files.
DISCLAIMER: I am a co-author of this work for academic purposes, and working on a bash script renderer. But the system is already available for testing purposes.

Related

Move all files in subdirectories of a specific name up one directory level

I've run a batch process to resize and optimize all images within a directory and subdirectories. Unfortunately, Photoshop CC doesn't have an option (that I'm aware of) to overwrite the existing images and instead creates a 'JPEG' directory anywhere it processes images and writes the processed images there. The number of images is relatively large (~2000 images) and they're divided into hundreds of subdirectories, so manually moving the images to overwrites the existing ones is out of the question.
How can I move all of the processed images in the 'JPEG' directories up a level to overwrite the original images? The closes I think I've come is this:
find . -type d -name "JPEG" -exec cd {} \; && find . -type f -name "*.jpg" -exec mv -v {} .. \;
...but that drops all the images a level up from my current directory.
Can some one sort me out?
Your cd only affects a subprocess which changes to the directory and dies, while its parent process remains unaffected.
Anyway, switching to a directory is rarely necessary or useful; just use the path directly in the mv command.
find . -type d -name "JPEG" -exec mv -v {}/*.jpg {}/.. \;
(Not in a place where I can test right now, but I guess the wildcard needs a shell, too:
find . -type d -name "JPEG" -exec sh -c 'mv -v {}/*.jpg {}/..' \;
Untested, obviously.)
backup your image directory before using the following script.
CURRENT_DIR=$(pwd)
for x in $(find -type d -name 'JPEG')
do
cd $x && mv -v *.jpg .. # you can use `cp` instead of `mv`, then remove JPEG directory.
cd $CURRENT_DIR
done

How to copy files recursively, rename them but keep the same extension in Bash?

I have a folder with tens of thousands of different file types. Id like to copy them all to a new folder (Copy1) but also rename them all to $RANDOM but keep the extension intact. I realize I can write a line specifying which extension to find and how to name it, but there is got to be a way to do it dynamically, because there are at least 100 file types and may be more in the future.
I have the following so far:
find ./ -name '*.*' -type f -exec bash -c 'cp "$1" "${1/\/123_//_$RANDOM}"' -- {} \;
but that puts the random number after the extension, and also it puts the all in the same folder. I cant figure out how to do the following 2 things:
1 - Keep all paths intact, but in a new root folder (Copy1)
2 - How to have name be $RANDOM.extension, instead of .extension.$RANDOM
PS - by $RANDOM i mean actual randomly generated number. I am interested in keeping folder structure, so we are dealing with a few hundred files at most per directory, but all directories/files need to be renamed to $RANDOM. Another way to look at what I need to do. Copy all contents or Folder1 with all subdirectories and files to Folder2 (where Fodler2 is a $RANDOM name), then rename all folders and files to random names but keep all extensions.
EDIT: Ok i figured out how to rename and keep extension. But I have a problem where its dumping all of the files into the root directory where script is run from. How do I keep them in their respective folders? Command Im using is:
find ./ -name '*.*' -type f -exec bash -c 'mv "$1" $RANDOM.${1##*.}' -- {} \;
Thanks!
Ok i figured out how to rename and keep extension. But I have a
problem where its dumping all of the files into the root directory
where script is run from. How do I keep them in their respective
folders? Command Im using is:
find ./ -name '*.*' -type f -exec bash -c 'mv "$1" $RANDOM.${1##*.}' -- {} \;
Change your command to:
PATH=/bin:/usr/bin find . -name '*.*' -type f -execdir bash -c 'mv "$1" $RANDOM.${1##*.}' -- {} \;
Or alternatively using uuids instead of random numbers:
PATH=/bin:/usr/bin find . -name '*.*' -type f -execdir bash -c 'mv "$1" $(uuidgen).${1##*.}' -- {} \;
Here's what I came up with :
i=1
random="whatever"
find . -name "*.*" -type f | while read f
do
newbase=${f/*./$random$i.} //added counter to filename
cp $f /Path/Name/"$newbase"
((i++))
done
I had to add a counter to random (i), otherwise, if the extensions are similar, your files would overwrite themselves when copied.
In your new folder, your files should look like this :
whatever1.txt
whatever2.txt
etc etc
I hope this is what you were looking for.
Here is the command that worked for me.
find . -name '*.pdf' -type f -exec bash -c 'echo "{}" && cp "$1" ./$RANDOM.${1##*.}' -- {} \;

issue with changing file extensions with bash

find /path/to/files -type f -not -name "M*'.jpg" -exec mv "{}" "{}".mxg \;
I fear I made two mistakes.
Files are stored in a directory structure. Goal is to keep the filenames and change the file extension from .jpg to .mxg. But only for files that have 'M' as the first character of there filename.
The above line has this result:
all files have .mxg added. So the .jpg isn't and all files are changed.
This should do it:
find /path/to/files -type f -name 'M*.jpg' -exec bash -c 'echo mv "$1" "${1/jpg/mxg}"' -- {} \;
A somewhat cleaner solution is if you have the rename command. However, there are different implementations out there, so read your man page first to check you have the same as mine. The version I have in Debian is Larry Wall's implementation in perl. You can recognize this by the example rename 's/\.bak$//' *.bak near the top, or the AUTHOR section near the bottom. With this implementation you can rename your files like this:
find /path/to/files -type f -name 'M*.jpg' -exec rename 's/jpg$/mxg/' {} \;

Copy changed files, create a changeset and maintain directory structure

I want to copy just the files i've created/edited today into a separate directory "changeset" whilst maintaining their directory structure
I came up with the following script
cd ./myproject/
find ./* -mtime -1 -daystart -exec cp {} ../changeset/{} \;
The drawbacks of the above is that directories aren't created and the copy throws an error.
I've manually gone into ../changeset/ and create the folder structure until the command runs without errors.. but thats a little tedious.
Is there a simple solution to this?
find * -mtime -1 -daystart -print0 | cpio -pd0 ../changeset
cpio is an old, oddball archival program that is occasionally the best tool for the job. With -p it copies files named on stdin to another directory. With -d it creates directories as needed.
I've found another solution which isn't as elegant as John's but which isn't reliant on cpio, which i dont have.
cd ./myproject/
# Create all directories
find ./* -type d -exec mkdir ../changeset/{} \;
# Copy files
find ./* -mtime -1 -daystart -exec cp {} ../changeset/{} \;
# Delete empty directories, run this several times because after moving a child the parent directory needs to be removed
find ../changeset/ -type d -empty -exec rmdir {} \;

shell script for listing all images in current and subfolder and copy them into one folder?

I have some folder hierarchy, in some of the folders there are images, I need a shell script which can list all images and copy them into one specified folder, where listing them is not important, I just want to copy all images into a folder?
I know I can
ls -R *.png
but how do I copy them all to one folder?
Thanks!
Update: As glenn jackman has pointed out, this would be slightly more efficient to use over the answer I provided:
file . -type f -name \*.png | xargs cp -t destination
For the explanation, see glenn's comments that follow this answer.
One way is to use find:
find . -type f -name "*.png" -exec cp {} ~/path/to/your/destination/folder \;
Explanation:
find is used to find files / directories
. start finding from the current working directory (alternatively, you can specify a path)
-type f: only consider files (as opposed to directories)
-name "*.png": only consider those with png extension
-exec: for each such result found, do something (see below)
cp {} ~/path/to/your/destination/folder \;: this is the do something part: copy each such result found (substituted into the {}) to the destination specified.
To copy multiple file patterns in single go we can use -regex instead -name
find . -type f -regex '.*\(jpg\|jpeg\|png\|gif\|mp4\|avi\|svg\|mp3\|vob\)' -exec cp {} /path/to/your/destination/folder \;

Resources