How to copy contents of a directory recursively without maintaining the directory hierarchy? - bash

I am using Bash and I want to copy the contents of all subdirectories in the directories into a single flat-directory.
Here is an example. The input directory is:
xyz
--x1
--y1
--test1.txt
--test2.txt
--image1.jpg
--x2
--test3.txt
And I want the resultant directory to be:
abc
--test1.txt
--test2.txt
--test3.txt
--image1.jpg
Are there a combination of flags with cp command that can be used to achieve the above? The name of the files are always unique.

You can use mkdir and find:
mkdir -p abc
find ./xyz -type f -print0 | xargs -0 -I {} cp {} ./abc

Related

Bash - replace all files, of a certain type, recursively, with a single file, retaining the original file name

Suppose I have a directory that I need to traverse recursively, finding each file of a certain type (*.jpeg), and replace each matching occurrence with one unique file (placeholder.jpeg) BUT I want to keep the original filename.
I am replacing all jpeg images in a large directory structure with one small placeholder image, but I need to keep the original file names intact.
So a recursive find-file-by-type-and-replace, but keeping the original file name.
Here is what almost works:
#! /bin/bash
shopt -s globstar
for f in /tmp/**/*.jpeg
do
cp /tmp/placeholder.jpeg $f
done
It's not clear to me that your placeholder file is always outside the directory structure where you're doing the replacement.
Various find versions have a -samefile option to detect a particular file, so you could avoid an invocation of cp that's going to emit its "... are the same file" warning:
find /tmp \
-name '*.jpeg' ! -samefile /tmp/placeholder.jpeg \
-exec cp /tmp/placeholder.jpeg {} \;
You could test by replacing -exec cp ... with -exec echo cp ... to see the expected copies without doing them.
Try this: find /tmp/ -name \*.jpeg -print0 | xargs -0I'{}' cp placeholder.jpeg {}

How to copy all files with the same name into another directory using cp command

I have directory named "Documents". In this directory I have 5 files:
User1.txt
User2.txt
User3.txt
User4.txt
User5.txt
Users-info.zip
index.html
I want to copy only those files in whose names there is a word "user" to another directory. How I can do this with cp command?
cp User* /path/to/dir try this, will be enough.
If you wish unusual way:
find . -type f -name 'User*' -print0 | xargs -0 cp -t /path/to/dir/for/copies/
For your case it is:
cp User[1-9].txt /dst_dir
We copy only files with User in the beginning, than some digit and finally .txt.

Searching for a Specific String in multiple files and Copying all the files recursively

I am having 35K + odd files in multiple directories and subdirectories. There are 1000 odd files (.c .h and other file names) with a unique string "FOO" in the FILE CONTENT. I am trying to copy those files alone (with the directory structure preserved) to a different directory 'MEOW'. Can some one look in to my bash execution and correct my mistake
for Eachfile in `find . -iname "*FOO*" `
do
cp $Eachfile MEOW
done
getting the following error
./baash.sh: line 2: syntax error near unexpected token `$'do\r''
'/baash.sh: line 2: `do
To find all files under the current directory with the string "FOO" in them and copy them to directory MEOW, use:
grep --null -lr FOO . | xargs -0 cp -t MEOW
Unlike find, grep looks in the contents of a file. The -l option to grep tells it to list file names only. The -r option tells it to look recursively in subdirectories. Because file names can have spaces and other odd characters in them, we give the --null option to grep so that, in the list it produces, the file names are safely separated by null characters. xargs -0 reads from that list of null-separated file names and provides them as argument to the command cp -t MEOW which copies them to the target directory MEOW.
In case you only want to search for the string FOO in .c and .h file then
find ./ -name "*\.c" -o -name "*\.h" -exec grep -l FOO {} \; | xargs cp -t MEOW/
For me its working even without --null option to xrags, if doesn't for you.. then append -0 in xargs part as follow:
xargs -0 cp -t MEOW/
for file in $(find -name '*.c' -o -name '*.h' -exec grep -l 'FOO' {} \;); do
dname=$(dirname "$file")
dest="MEOW/$dname"
mkdir -p $dest
cp "$file" "$dest"
done

Unzip Folders to Parent Directory Keeping Zipped Folder Name

I have a file structure as follows:
archives/
zips/
zipfolder1.zip
zipfolder2.zip
zipfolder3.zip
...
zipfolderN.zip
I have a script that unzips the folders to the parent directory "archives", but it is unzipping the contents of the folders to the "archives" directory. I need the zipped folders to remain as folders under the "archives" directory. The resultant file structure should look like this:
archives/
zips/
zipfolder1.zip
zipfolder2.zip
...
zipfolder1/
contents...
zipfolder2/
contents...
...
I am currently using the following:
find /home/username/archives/zips/*.zip -type f | xargs -i unzip -d ../ -q '{}'
How can I modify this line to keep the original folder names? Is it as simple as using ../*?
You could use basename to extract the zip into the desired directory:
find /home/username/archives/zips/*.zip -type f -exec sh -c 'unzip -q -d ../"$(basename "{}" .zip)" "{}"' \;

shell entering each folder and zip content

So I have some folder
|-Folder1
||-SubFolder1
||-SubFolder2
|-Folder2
||-SubFolder3
||-SubFolder4
Each subfolder contains several jpg I want to zip to the root folder...
I'm a little bit stuck on "How to enter each folder"
Here is my code:
find ./ -type f -name '*.jpg' | while IFS= read i
do
foldName=${PWD##*/}
zip ../../foldName *
done
The better would be to store FolderName+SubFolderName and give it to the zip command as name...
Zipping JPEGs (for Compression) is Usually Wasted Effort
First of all, attempting to compress already-compressed formats like JPEG files is usually a waste of time, and can sometimes result in archives that are larger than the original files. However, it is sometimes useful to do so for the convenience of having a bunch of files in a single package.
Just something to keep in mind. YMMV.
Use Find's -execdir Flag
What you need is the find utility's -execdir flag. The GNU find man page says:
-execdir command {} +
Like -exec, but the specified command is run from the subdirec‐
tory containing the matched file, which is not normally the
directory in which you started find.
For example, given the following test corpus:
cd /tmp
mkdir -p foo/bar/baz
touch foo/bar/1.jpg
touch foo/bar/baz/2.jpg
you can zip the entire set of files with find while excluding the path information with a single invocation. For example:
find /tmp/foo -name \*jpg -execdir zip /tmp/my.zip {} +
Use Zip's --junk-paths Flag
The zip utility on many systems supports a --junk-paths flag. The man page for zip says:
--junk-paths
Store just the name of a saved file (junk the path), and do not
store directory names.
So, if your find utility doesn't support -execdir, but you do have a zip that supports junking paths, you could do this instead:
find /tmp/foo -name \*jpg -print0 | xargs -0 zip --junk-paths /tmp/my.zip
You can use dirname to get the directory name of a file/directory it is located in.
You can also simplify the find command to search only for directories by using -type d. Then you should use basename to get only the name of the subdirs:
find ./*/* -type d | while read line; do
zip --junk-paths "$(basename $line)" $line/*.jpg
done
Explanation
find ./*/* -type d
will print out all directories located in ./*/* which will result in all subdirs of directories located in the current dir
while read line reads each line from the stream and stores it in the variable "line". Thus $line will be the relative path to the subdir, e.g. "Folder1/Subdir2"
"$(basename $line)" returns the only the name of the subdir, e.g. "Subdir2"
Update: add --junk-paths to the zip command if you do not want the directy paths to be stored in the zip filde
So a little check, I finally got something working:
find ./*/* -type d | while read line; do
#printf '%s\n' "$line"
zip ./"$line" "$line"/*.jpg
done
But this create un archive containing:
Subfolder.zip
Folder
|-Subfolder
||-File1.jpg
||-File2.jpg
||-File3.jpg
Instead I fold like it to be:
Subfolder.zip
|-File1.jpg
|-File2.jpg
|-File3.jpg
So I tried using basename and dirname in differnet combination...Always got some error...
And just to learn how to: what if I would like the new archive to be created in the same root directory as "Folder"?
Ok finally got it!
find ./* -name \*.zip -type f -print0 | xargs -0 rm -rf
find ./*/* -type d | while read line; do
#printf '%s\n' "$line"
zip --junk-paths ./"$line" "$line"/*.jpg
done
find . -name \*.zip -type f -mindepth 2 -exec mv -- '{}' . \;
In first row I simply remove all .zip files,
Then I zip all and in the final row I move all zip to the root directory!
Thanks everbody for your help!

Resources