Copy specific files from subdirectories into one directory - bash

I have a directory:
❯ find ./images -name *150x150.jpg
./images/2060511653921052666.images/thumb-150x150.jpg
./images/1777759401031970571.images/thumb-150x150.jpg
./images/1901716489977597520.images/thumb-150x150.jpg
./images/2008758225324557620.images/thumb-150x150.jpg
./images/1988762968386208381.images/thumb-150x150.jpg
./images/1802341648716075239.images/thumb-150x150.jpg
./images/2051017760380879322.images/thumb-150x150.jpg
./images/1974813836146304123.images/thumb-150x150.jpg
./images/2003120002653201215.images/thumb-150x150.jpg
./images/1911925394312129508.images/thumb-150x150.jpg
(...)
I would like to copy all those files (thumb-150x150.jpg) into one directory.
❯ find ./images -name *150x150.jpg -exec cp {} ./another-directory \;
But of course every file will be overwritten by the next one.
So how could I copy them to either:
1) 1.jpg, 2.jpg, 3.jpg... etc
or
2) use the subdirectory id (./images/2060511653921052666.images/thumb-150x150.jpg) as the target filename (2060511653921052666.jpg in this example) ?

you can use loop:
i=1
find ./images -name *150x150.jpg | while read line; do
cp $line /anotherdir/$i.jpg
i=$[i+1]
done

A simple bash script, using $RANDOM to generate a random number for each image copied. A random number is embedded in the new name of each file.
Note that $RANDOM generates a random number between 1 and 32767. So the same random number could be produced more than once. This is only likely if you had tens of thousand images to copy.
It is fairly easy to improve the randomness of each number generated is necessary.
# !/bin/bash
cd images
for d in *
do
[ -d $d ] && cd $d
for image in *150x150.jpg
do
cp -pv $image /another-dir/thumb${RANDOM}.jpg
done
[ -d $d ] && cd ..
done

If you have GNU parallel it's possible:
find ./images -name *150x150.jpg | \
parallel 'mv {} ./another-directory/`stat -c%i {}`_{/}'
the stat -c%i {} get's the inode for each file
I tested the command bellow on my system:
find -iname "*.mp3" | parallel 'cp {} ~/tmp/{/.}_`stat -c%i {}`.mp3'
{/} .......... gets just the filename with no full path "basename"
{.} .......... removes extension
Before runing the actual command you can put a echo command in front of cp or mv in order to test your command.
source: https://stackoverflow.com/a/16074963/2571881

Related

find, xargs: execute chain of commands for each file

I am sorry if the question title is not informative enough. Please feel free to suggest a better variant.
I want to perform the following task:
In a directory I have a number of files that are photos in JPEG format. I want to extract from EXIF the dates when those photos were taken, create a new directory for each date, and move a file to the relevant directory.
(EXIF date and time have the format YYYY:MM:DD hh:mm:ss, and I want the directory names to be formatted as YYYY-MM-DD, that's why I use sed)
I kind of know how to perform each of those tasks separately, but failed to put them together. I spent some time investigating how to execute commands using find with -exec or xargs but still failed to understand how to properly chain everything.
Finally I was able to fulfil my task using two commands:
find . -name '*.jpg' -exec sh -c "identify -format %[exif:DateTimeOriginal] {}
| sed 's/ [0-9:]*//; s/:/-/g' | xargs mkdir -p" \;
find . -name '*.jpg' -exec sh -c "identify -format %[exif:DateTimeOriginal] {}
| sed 's/ [0-9:]*//; s/:/-/g; s/$/\//' | xargs mv {}" \;
But I do not like the duplication, and I do not like -exec sh -c. Is there the right way to do this in one line and without using -exec sh -c?
Rather than focusing on one-liners, a better solution would be to put the logic into a script which makes it easy to execute and test. Put this in a file called movetodate.sh:
#!/usr/bin/env bash
# This script takes one or more image file paths
set -e
set -o pipefail
for path in "$#"; do
date=$(identify -format %[exif:DateTimeOriginal] | sed 's/ [0-9:]*//; s/:/-/g')
dest=$(dirname "$path")/$date
mkdir -p "$dest"
mv "$path" "$dest"
done
Then, to invoke it:
find . -name '*.jpg' -exec ./movetodate.sh {} +
It is easily done with exiftool:
exiftool "-Directory<DateTimeOriginal" -d %Y-%m-%d *.jpg
For example, the command transforms a layout like this:
.
├── a.jpg (2013:10:17 10:01:00)
└── b.jpg (2012:08:07 16:11:15)
to this:
.
├── 2012-08-07
│   └── b.jpg
└── 2013-10-17
└── a.jpg
If you still want to use identify, the commands can be rewritten as follows:
script=$(cat <<'SCRIPT'
d=$(
d=$(identify -format "%[exif:DateTimeOriginal]" "$0" 2>/dev/null) || exit $?
d=${d:0:10}
printf '%s/%s' "$(dirname "$0")" "${d//:/-}"
) || exit $?
mkdir -p "$d" && mv -v "$0" "$d"
SCRIPT
)
find "$dir" -name '*.jpg' -exec bash -c "$script" {} \;
Note the use of $0 variable within the script. We pass the {} placeholder to the script as the first argument.
The script can easily be transformed to accept multiple arguments (paths) with the help of a for file in "$#" loop. In this case the \; character should be replaced with +. However, if you have a large number of files exceeding the $(getconf ARG_MAX) limit, you will need either xargs, or processing the files one by one as shown in the script above. The same considerations are applied to the exiftool command.
With parallel you do not need the script but would instead do:
doit() {
path="$1"
date=$(identify -format %[exif:DateTimeOriginal] | sed 's/ [0-9:]*//; s/:/-/g')
dest=$(dirname "$path")/$date
mkdir -p "$dest"
mv "$path" "$dest"
}
export -f doit
find . -name '*.jpg' | parallel doit

Bash find all zip files within subfolders, bind files and their path into loop and perform some tasks

I need to write script on Bash, which will find all zip files within subfolders, bind files and their path into some file, and then loop through this list and perform some tasks with all zip files (e.g. extract, check files within zip and then delete extracted files).
Some thoughts:
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo $DIR ##current dir
ls $DIR\*.zip
Then bind result to file (ziplist.txt for example)
Then read this file in loop per string:
if [[ some result ]] ; then
while IFS= read -r ; do
done <$DIR/ziplist.txt
How this can be done in best way? Sorry, I've have limited experience with bash.
This should do the trick :
for filename in $(find . -name '*.zip'); do
# Your operations here
done
If you want to keep using a while you can do:
while IFS= read -r ; do
done < <(find . -name '*.zip')
You can use find in many ways to do this.
"Your way" would be to create a temporary file and loop through this:
find /your/path -name '*.zip' > /tmp/zips
If you don't necessarily need the collection of files but just want to perform a task to them as you find them, you can use find's exec:
find /your/path -name '*.zip' -exec /path/to/your/worker/script.sh {} \;
which will execute your script.sh for every zip file it finds, with the full zip file path as the argument.
Hope this helps.

md5 all files in a directory tree

I have a a directory with a structure like so:
.
├── Test.txt
├── Test1
│   ├── Test1.txt
│   ├── Test1_copy.txt
│   └── Test1a
│   ├── Test1a.txt
│   └── Test1a_copy.txt
└── Test2
├── Test2.txt
├── Test2_copy.txt
└── Test2a
├── Test2a.txt
└── Test2a_copy.txt
I would like to create a bash script that makes a md5 checksum of every file in this directory. I want to be able to type the script name in the CLI and then the path to the directory I want to hash and have it work. I'm sure there are many ways to accomplish this. Currently I have:
#!/bin/bash
for file in "$1" ; do
md5 >> "${1}__checksums.md5"
done
This just hangs and it not working. Perhaps I should use find?
One caveat - the directories I want to hash will have files with different extensions and may not always have this exact same tree structure. I want something that will work in these different situations, as well.
Using md5deep
md5deep -r path/to/dir > sums.md5
Using find and md5sum
find relative/path/to/dir -type f -exec md5sum {} + > sums.md5
Be aware, that when you run check on your MD5 sums with md5sum -c sums.md5, you need to run it from the same directory from which you generated sums.md5 file. This is because find outputs paths that are relative to your current location, which are then put into sums.md5 file.
If this is a problem you can make relative/path/to/dir absolute (e.g. by puting $PWD/ in front of your path). This way you can run check on sums.md5 from any location. Disadvantage is, that now sums.md5 contains absolute paths, which makes it bigger.
Fully featured function using find and md5sum
You can put this function to your .bashrc file (located in your $HOME directory):
function md5sums {
if [ "$#" -lt 1 ]; then
echo -e "At least one parameter is expected\n" \
"Usage: md5sums [OPTIONS] dir"
else
local OUTPUT="checksums.md5"
local CHECK=false
local MD5SUM_OPTIONS=""
while [[ $# > 1 ]]; do
local key="$1"
case $key in
-c|--check)
CHECK=true
;;
-o|--output)
OUTPUT=$2
shift
;;
*)
MD5SUM_OPTIONS="$MD5SUM_OPTIONS $1"
;;
esac
shift
done
local DIR=$1
if [ -d "$DIR" ]; then # if $DIR directory exists
cd $DIR # change to $DIR directory
if [ "$CHECK" = true ]; then # if -c or --check option specified
md5sum --check $MD5SUM_OPTIONS $OUTPUT # check MD5 sums in $OUTPUT file
else # else
find . -type f ! -name "$OUTPUT" -exec md5sum $MD5SUM_OPTIONS {} + > $OUTPUT # Calculate MD5 sums for files in current directory and subdirectories excluding $OUTPUT file and save result in $OUTPUT file
fi
cd - > /dev/null # change to previous directory
else
cd $DIR # if $DIR doesn't exists, change to it to generate localized error message
fi
fi
}
After you run source ~/.bashrc, you can use md5sums like normal command:
md5sums path/to/dir
will generate checksums.md5 file in path/to/dir directory, containing MD5 sums of all files in this directory and subdirectories. Use:
md5sums -c path/to/dir
to check sums from path/to/dir/checksums.md5 file.
Note that path/to/dir can be relative or absolute, md5sums will work fine either way. Resulting checksums.md5 file always contains paths relative to path/to/dir.
You can use different file name then default checksums.md5 by supplying -o or --output option. All options, other then -c, --check, -o and --output are passed to md5sum.
First half of md5sums function definition is responsible for parsing options. See this answer for more information about it. Second half contains explanatory comments.
How about:
find /path/you/need -type f -exec md5sum {} \; > checksums.md5
Update#1: Improved the command based on #twalberg's recommendation to handle white spaces in file names.
Update#2: Improved based on #jil's suggestion, to remove unnecessary xargs call and use -exec option of find instead.
Update#3: #Blake a naive implementation of your script would look something like this:
#!/bin/bash
# Usage: checksumchecker.sh <path>
find "$1" -type f -exec md5sum {} \; > "$1"__checksums.md5
Updated Answer
If you like the answer below, or any of the others, you can make a function that does the command for you. So, to test it, type the following into Terminal to declare a function:
function sumthem(){ find "$1" -type f -print0 | parallel -0 -X md5 > checksums.md5; }
Then you can just use:
sumthem /Users/somebody/somewhere
If that works how you like, you can add that line to the end of your "bash profile" and the function will be declared and available whenever you are logged in. Your "bash profile" is probably in $HOME/.profile
Original Answer
Why not get all your CPU cores working in parallel for you?
find . -type f -print0 | parallel -0 -X md5sum
This finds all the files (-type f) in the current directory (.) and prints them with a null byte at the end. These are then passed passed into GNU Parallel, which is told that the filenames end with a null byte (-0) and that it should do as many files as possible at a time (-X) to save creating a new process for each file and it should md5sum the files.
This approach will pay the largest bonus, in terms off speed, with big images like Photoshop files.
#!/bin/bash
shopt -s globstar
md5sum "$1"/** > "${1}__checksums.md5"
Explanation: shopt -s globstar(manual) enables ** recursive glob wildcard. It will mean that "$1"/** will expand to list of all the files recursively under the directory given as parameter $1. Then the script simply calls md5sum with this file list as parameter and > "${1}__checksums.md5" redirects the output to the file.
md5deep -r $your_directory | awk {'print $1'} | sort | md5sum | awk {'print $1'}
Use find command to list all files in directory tree,
then use xargs to provide input to md5sum command
find dirname -type f | xargs md5sum > checksums.md5
In case you prefer to have separate checksum files in every directory, rather than a single file, you can
find all subdirectories
keep only those which actually contain files (not only other subdirs)
cd to each of them and create a checksums.md5 file inside that directory
Here is a an example script which does that:
#!/bin/bash
# Do separate md5 files in each subdirectory
md5_filename=checksums.md5
dir="$1"
[ -z "$dir" ] && dir="."
# Check OS to select md5 command
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
is_linux=1
md5cmd="md5sum"
elif [[ "$OSTYPE" == "darwin"* ]]; then
md5cmd="md5 -r"
else
echo "Error: unknown OS '$OSTYPE'. Don't know correct md5 command."
exit 1
fi
# go to base directory after saving where we started
start_dir="$PWD"
cd "$dir"
# if we're in a symlink cd to the real path
if [ ! "$dir" = "$(pwd -P)" ]; then
dir="$(pwd -P)"
cd "$dir"
fi
if [ "$PWD" = "/" ]; then
die "Refusing to do it on system root '$PWD'"
fi
# Find all folders to process
declare -a subdirs=()
declare -a wanted=()
# find all non-hidden subdirectories (not if the name begins with "." like ".Trashes", ".Spotlight-V100", etc.)
while IFS= read -r; do subdirs+=("$PWD/$REPLY"); done < <(find . -type d -not -name ".*" | LC_ALL=C sort)
# count files and if there are any, add dir to "wanted" array
echo "Counting files and sizes to process ..."
for d in "$dir" "${subdirs[#]}"; do # include "$dir" itself, not only it's subdirs
files_here=0
while IFS= read -r ; do
(( files_here += 1 ))
done < <(find "$d" -maxdepth 1 -type f -not -name "*.md5")
(( files_here )) && wanted+=("$d")
done
echo "Found ${#wanted[#]} folders to process:"
printf " * %s\n" "${wanted[#]}"
if [ "${#wanted[*]}" = 0 ]; then
echo "Nothing to do. Exiting."
exit 0
fi
for d in "${wanted[#]}"; do
cd "$d"
find . -maxdepth 1 -type f -not -name "$md5_filename" -print0 \
| LC_ALL=C sort -z \
| while IFS= read -rd '' f; do
$md5cmd "$f" | tee -a "$md5_filename"
done
cd "$dir"
done
cd "$start_dir"
(This is actually a very simplified version of this "md5dirs" script on Github. The original is quite specific and more complex, making it less illustrative as an example, and more difficult to adapt to other different needs.)
I wanted something similar to calculate the SHA256 of an entire directory, so I wrote this "checksum" script:
#!/bin/sh
cd $1
find . -type f | LC_ALL=C sort |
(
while read name; do
sha256sum "$name"
done;
) | sha256sum
Example usage:
patrick#pop-os:~$ checksum tmp
d36bebfa415da8e08cbfae8d9e74f6606e86d9af9505c1993f5b949e2befeef0 -
In an earlier version I was feeding the file names to "xargs", but that wasn't working when file names had spaces.

Split a folder into multiple subfolders in terminal/bash script

I have several folders, each with between 15,000 and 40,000 photos. I want each of these to be split into sub folders - each with 2,000 files in them.
What is a quick way to do this that will create each folder I need on the go and move all the files?
Currently I can only find how to move the first x items in a folder into a pre-existing directory. In order to use this on a folder with 20,000 items... I would need to create 10 folders manually, and run the command 10 times.
ls -1 | sort -n | head -2000| xargs -i mv "{}" /folder/
I tried putting it in a for-loop, but am having trouble getting it to make folders properly with mkdir. Even after I get around that, I need the program to only create folders for every 20th file (start of a new group). It wants to make a new folder for each file.
So... how can I easily move a large number of files into folders of an arbitrary number of files in each one?
Any help would be very... well... helpful!
Try something like this:
for i in `seq 1 20`; do mkdir -p "folder$i"; find . -type f -maxdepth 1 | head -n 2000 | xargs -i mv "{}" "folder$i"; done
Full script version:
#!/bin/bash
dir_size=2000
dir_name="folder"
n=$((`find . -maxdepth 1 -type f | wc -l`/$dir_size+1))
for i in `seq 1 $n`;
do
mkdir -p "$dir_name$i";
find . -maxdepth 1 -type f | head -n $dir_size | xargs -i mv "{}" "$dir_name$i"
done
For dummies:
create a new file: vim split_files.sh
update the dir_size and dir_name values to match your desires
note that the dir_name will have a number appended
navigate into the desired folder: cd my_folder
run the script: sh ../split_files.sh
This solution worked for me on MacOS:
i=0; for f in *; do d=dir_$(printf %03d $((i/100+1))); mkdir -p $d; mv "$f" $d; let i++; done
It creates subfolders of 100 elements each.
This solution can handle names with whitespace and wildcards and can be easily extended to support less straightforward tree structures. It will look for files in all direct subdirectories of the working directory and sort them into new subdirectories of those. New directories will be named 0, 1, etc.:
#!/bin/bash
maxfilesperdir=20
# loop through all top level directories:
while IFS= read -r -d $'\0' topleveldir
do
# enter top level subdirectory:
cd "$topleveldir"
declare -i filecount=0 # number of moved files per dir
declare -i dircount=0 # number of subdirs created per top level dir
# loop through all files in that directory and below
while IFS= read -r -d $'\0' filename
do
# whenever file counter is 0, make a new dir:
if [ "$filecount" -eq 0 ]
then
mkdir "$dircount"
fi
# move the file into the current dir:
mv "$filename" "${dircount}/"
filecount+=1
# whenever our file counter reaches its maximum, reset it, and
# increase dir counter:
if [ "$filecount" -ge "$maxfilesperdir" ]
then
dircount+=1
filecount=0
fi
done < <(find -type f -print0)
# go back to top level:
cd ..
done < <(find -mindepth 1 -maxdepth 1 -type d -print0)
The find -print0/read combination with process substitution has been stolen from another question.
It should be noted that simple globbing can handle all kinds of strange directory and file names as well. It is however not easily extensible for multiple levels of directories.
The code below assumes that the filenames do not contain linefeeds, spaces, tabs, single quotes, double quotes, or backslashes, and that filenames do not start with a dash. It also assumes that IFS has not been changed, because it uses while read instead of while IFS= read, and because variables are not quoted. Add setopt shwordsplit in Zsh.
i=1;while read l;do mkdir $i;mv $l $((i++));done< <(ls|xargs -n2000)
The code below assumes that filenames do not contain linefeeds and that they do not start with a dash. -n2000 takes 2000 arguments at a time and {#} is the sequence number of the job. Replace {#} with '{=$_=sprintf("%04d",$job->seq())=}' to pad numbers to four digits.
ls|parallel -n2000 mkdir {#}\;mv {} {#}
The command below assumes that filenames do not contain linefeeds. It uses the implementation of rename by Aristotle Pagaltzis which is the rename formula in Homebrew, where -p is needed to create directories, where --stdin is needed to get paths from STDIN, and where $N is the number of the file. In other implementations you can use $. or ++$::i instead of $N.
ls|rename --stdin -p 's,^,1+int(($N-1)/2000)."/",e'
I would go with something like this:
#!/bin/bash
# outnum generates the name of the output directory
outnum=1
# n is the number of files we have moved
n=0
# Go through all JPG files in the current directory
for f in *.jpg; do
# Create new output directory if first of new batch of 2000
if [ $n -eq 0 ]; then
outdir=folder$outnum
mkdir $outdir
((outnum++))
fi
# Move the file to the new subdirectory
mv "$f" "$outdir"
# Count how many we have moved to there
((n++))
# Start a new output directory if we have sent 2000
[ $n -eq 2000 ] && n=0
done
The answer above is very useful, but there is a very import point in Mac(10.13.6) terminal. Because xargs "-i" argument is not available, I have change the command from above to below.
ls -1 | sort -n | head -2000| xargs -I '{}' mv {} /folder/
Then, I use the below shell script(reference tmp's answer)
#!/bin/bash
dir_size=500
dir_name="folder"
n=$((`find . -maxdepth 1 -type f | wc -l`/$dir_size+1))
for i in `seq 1 $n`;
do
mkdir -p "$dir_name$i";
find . -maxdepth 1 -type f | head -n $dir_size | xargs -I '{}' mv {} "$dir_name$i"
done
This is a tweak of Mark Setchell's
Usage:
bash splitfiles.bash $PWD/directoryoffiles splitsize
It doesn't require the script to be located in the same dir as the files for splitting, it will operate on all files, not just the .jpg and allows you to specify the split size as an argument.
#!/bin/bash
# outnum generates the name of the output directory
outnum=1
# n is the number of files we have moved
n=0
if [ "$#" -ne 2 ]; then
echo Wrong number of args
echo Usage: bash splitfiles.bash $PWD/directoryoffiles splitsize
exit 1
fi
# Go through all files in the specified directory
for f in $1/*; do
# Create new output directory if first of new batch
if [ $n -eq 0 ]; then
outdir=$1/$outnum
mkdir $outdir
((outnum++))
fi
# Move the file to the new subdirectory
mv "$f" "$outdir"
# Count how many we have moved to there
((n++))
# Start a new output directory if current new dir is full
[ $n -eq $2 ] && n=0
done
Can be directly run in the terminal
i=0;
for f in *;
do
d=picture_$(printf %03d $((i/2000+1)));
mkdir -p $d;
mv "$f" $d;
let i++;
done
This script will move all files within the current directory into picture_001, picture_002... and so on. Each newly created folder will contain 2000 files
2000 is the chunked number
%03d is the suffix digit you can adjust (currently 001,002,003)
picture_ is the folder prefix
This script will chunk all files into its directory (create subdirectory)
You'll certainly have to write a script for that.
Hints of things to include in your script:
First count the number of files within your source directory
NBFiles=$(find . -type f -name *.jpg | wc -l)
Divide this count by 2000 and add 1, to determine number of directories to create
NBDIR=$(( $NBFILES / 2000 + 1 ))
Finally loop through your files and move them accross the subdirs.
You'll have to use two imbricated loops : one to pick and create the destination directory, the other to move 2000 files in this subdir, then create next subdir and move the next 2000 files to the new one, etc...

Recursively move files of certain type and keep their directory structure

I have a directory which contains multiple sub-directories with mov and jpg files.
/dir/
/subdir-a/ # contains a-1.jpg, a-2.jpg, a-1.mov
/subdir-b/ # contains b-1.mov
/subdir-c/ # contains c-1.jpg
/subdir-d/ # contains d-1.mov
... # more directories with the same pattern
I need to find a way using command-line tools (on Mac OSX, ideally) to move all the mov files to a new location. However, one requirement is to keep directory structure i.e.:
/dir/
/subdir-a/ # contains a-1.mov
/subdir-b/ # contains b-1.mov
# NOTE: subdir-c isn't copied because it doesn't have mov files
/subdir-d/ # contains d-1.mov
...
I am familiar with find, grep, and xargs but wasn't sure how to solve this issue. Thank you very much beforehand!
It depends slightly on your O/S and, more particularly, on the facilities in your version of tar and whether you have the command cpio. It also depends a bit on whether you have newlines (in particular) in your file names; most people don't.
Option #1
cd /old-dir
find . -name '*.mov' -print | cpio -pvdumB /new-dir
Option #2
find . -name '*.mov' -print | tar -c -f - -T - |
(cd /new-dir; tar -xf -)
The cpio command has a pass-through (copy) mode which does exactly what you want given a list of file names, one per line, on its standard input.
Some versions of the tar command have an option to read the list of file names, one per line, from standard input; on MacOS X, that option is -T - (where the lone - means 'standard input'). For the first tar command, the option -f - means (in the context of writing an archive with -c, write to standard output); in the second tar command, the -x option means that the -f - means 'read from standard input'.
There may be other options; look at the manual page or help output of tar rather carefully.
This process copies the files rather than moving them. The second half of the operation would be:
find . -name '*.mov' -exec rm -f {} +
ASSERT: No files have newline characters in them. Spaces, however, are AOK.
# TEST FIRST: CREATION OF FOLDERS
find . -type f -iname \*.mov -printf '%h\n' | sort | uniq | xargs -n 1 -d '\n' -I '{}' echo mkdir -vp "/TARGET_FOLDER_ROOT/{}"
# EXECUTE CREATION OF EMPTY TARGET FOLDERS
find . -type f -iname \*.mov -printf '%h\n' | sort | uniq | xargs -n 1 -d '\n' -I '{}' mkdir -vp "/TARGET_FOLDER_ROOT/{}"
# TEST FIRST: REVIEW FILES TO BE MOVED
find . -type f -iname \*.mov -exec echo mv {} /TARGET_FOLDER_ROOT/{} \;
# EXECUTE MOVE FILES
find . -type f -iname \*.mov -exec mv {} /TARGET_FOLDER_ROOT/{} \;
Being large files, if they are on the same file system you don't want to copy them, but just to replicate their directory structure while moving.
You can use this function:
# moves a file (or folder) preserving its folder structure (relative to source path)
# usage: move_keep_path source destination
move_keep_path () {
# create directories up to one level up
mkdir -p "`dirname "$2"`"
mv "$1" "$2"
}
Or, adding support to merging existing directories:
# moves a file (or folder) preserving its folder structure (relative to source path)
# usage: move_keep_path source destination
move_keep_path () {
# create directories up to one level up
mkdir -p "`dirname "$2"`"
if [[ -d "$1" && -d "$2" ]]; then
# merge existing folder
find "$1" -depth 1 | while read file; do
# call recursively for all files inside
mv_merge "$file" "$2/`basename "$file"`"
done
# remove after merge
rmdir "$1"
else
# either file or non-existing folder
mv "$1" "$2"
fi
}
It is easier to just copy the files like:
cp --parents some/folder/*/*.mov new_folder/
from the parent directory of "dir execute this:
find ./dir -name "*.mov" | xargs tar cif mov.tar
Then cd to the directory you want to move the files to and execute this:
tar xvf /path/to/parent/directory/of"dir"/mov.tar
This should work if you want to move all mov files to a directory called new location -
find ./dir -iname '*.mov' -exec mv '{}' ./newlocation \;
However, if you wish to move the mov files along with their sub-dirs then you can do something like this -
Step 1: Copy entire structure of /dir to a new location using cp
cp -iprv dir/ newdir
Step 2: Find jpg files from newdir and delete them.
find ./newdir -iname "*.jpg" -delete
Test:
[jaypal:~/Temp] ls -R a
a.mov aa b.mov
a/aa:
aaa c.mov d.mov
a/aa/aaa:
e.mov f.mov
[jaypal:~/Temp] mkdir d
[jaypal:~/Temp] find ./a -iname '*.mov' -exec mv '{}' ./d \;
[jaypal:~/Temp] ls -R d
a.mov b.mov c.mov d.mov e.mov f.mov
I amended the function of #djjeck, because it didn't work as I needed. The function below moves a source file to a destination directory also creating the needed levels of hierarchy in the source file path (see the example below):
# moves a file, creates needed levels of hierarchy in destination
# usage: move_with_hierarchy source_file destination top_level_directory
move_with_hierarchy () {
path_tail=$(dirname $(realpath --relative-to="$3" "$1"))
cd "$2"
mkdir -p $path_tail
cd - > /dev/null
mv "$1" "${2}/${path_tail}"
}
example:
$ ls /home/sergei/tmp/dir1/dir2/bla.txt
/home/sergei/tmp/dir1/dir2/bla.txt
$ rm -rf tmp2
$ mkdir tmp2
$ move_with_hierarchy /home/sergei/tmp/dir1/dir2/bla.txt /home/sergei/tmp2 /home/sergei/tmp
$ tree ~/tmp2
/home/sergei/tmp2
└── dir1
└── dir2
└── bla.txt
2 directories, 1 file

Resources