Command for moving subfolders with files, with keeping the original structure - linux

I have a parent/ folder with a couple of subfolders in it. Structure:
/parent/
/subfolder_1/
- file_1.txt
- file_2.txt
/subfolder_2/
- file_3.txt
- file_4.txt
Now, I need to recursively move the contents of parent/ folder to the empty parent_tmp/ directory. Thing is, I need to keep the original folder structure in parent/.
Expected outcome after moving:
/parent/
/subfolder_1/
(empty)
/subfolder_2/
(empty)
/parent_tmp/
/subfolder_1/
- file_1.txt
- file_2.txt
/subfolder_2/
- file_3.txt
- file_4.txt
Normally, I would simply do
mv parent/* parent_tmp
but this will, of course, move the subfolders permanently.
Is there a way to adjust the mv command to keep the original structure of the source directory?
Note:
I realize that I can e.g. copy parent/ to parent_tmp, and then remove the files in parent/ subfolders. This is plan B to me.

You can use find from parent of parent and parent_tmp directoroies:
find parent -type f -exec bash -c 'mkdir -p "parent_tmp/${1%/*}" &&
mv "$1" "parent_tmp/${1%/*}"' - {} \;

You could copy the files
cp -r parent/* parent_tmp/
or create hard links (should be a lot faster for big files)
cp -l -r parent/* parent_tmp/
and then delete the original files
find parent -type f -delete
while keeping the directory structure.

Zip the content of the parent folder and Unzip it in the target folder.

Quick and Dirty:
I don't think you'll find a tool or option in the mv command to do what you want, but you should be able to achieve the desired goal by using find:
cd parent && while read file ; do dirname="$(dirname "$file")" ; mkdir -p ../parent_tmp/"$dirname"/; mv "$file" "../parent_tmp/"${file#}"" ; done < <( find . -type f ) && cd -
Function
If you use this a lot then you can add the above to your ~/.basrc like so (append to the end of the file):
alias mvkp=moveandkeep
moveandkeep() {
cd "$1"
while read file ;
do dirname="$(dirname "$file")" ;
mkdir -p "$2"/"${dirname#}";
mv "$file" ""$2"/"${file#}"";
done < <(find . -type f)
cd -
}
Now you could simply do the following: (Full path to directories required)
mvkp /home/user/parent /home/user/parent_tmp

Related

Bash command to flatten nested dicrectory

I have a directory structure like this
a/1/01.jpg
b/2/01.jpg
c/3/01.jpg
I want to make it into a structure like this
a1/01.jpg
b2/01.jpg
c3/01.jpg
So far I have a bash command that looks like this
find . -mindepth 2 -type d -execdir bash -c 'mv -i \"$1\" ./\"${1//\/\[/_[}\"' bash {} \;
However the command failed with these statements
mv: cannot stat '"./3"': No such file or directory
mv: cannot stat '"./2"': No such file or directory
mv: cannot stat '"./1"': No such file or directory
What am I doing wrong here? is there a better way to do this?
Doing that by using plain bash would be easier:
#!/bin/bash
for src in */*/; do
dst=${src/\/}
echo mkdir "$dst"
echo mv "$src"* "$dst"
done
Remove both echos if the output looks fine.
Or, a more efficient version:
for src in */*/; do
mv "$src" "${src/\/}"
done
but this version won't work properly when the destination directory (a1, b2, c3, etc.) already exists.
All operations need to be performed on the leaf directories. These are identified by having two links which you can find with:
$ find -type d -links 2
Once you have these directories, you only have to rename the directory
$ find -type d -links 2 -exec sh -c 'd1={};d2="${d1#./}"; [ "$d2" = "${d2//\//}" ] || mv -v "$d1" "${d2//\//}"' \;
Find will complain a bit as you moved a directory away that it was traversing, but this should do it.
If you have a pure structure, i.e. files only appear in leaf-directories, this should do it. All you need to do now is delete the empty directories:
$ find -type d -empty -delete
Be aware, however, that if a leaf directory was already empty, the latter will remove it.

What is the linux command to move the files of subdirecties into one level up respectively

The path structure of the files on my server is similar to shown below,
/home/sun/sdir1/mp4/file.mp4
/home/sun/collection/sdir2/mp4/file.mp4
I would like to move the files of "mp4" into one level up(into sdir1 and sdir2 respectively)
So the output should be,
/home/sun/sdir1/file.mp4
/home/sun/collection/sdir2/file.mp4
I have no idea to do this, so not tried yet anything...
There are different ways to solve your problem
If you just want to move those specific files, run these commands:
cd /home/sun/
mv sdir1/mp4/file.mp4 sdir1/
mv sdir2/mp4/file.mp4 sdir2/
If you want to move all mp4 files on those directories (sdir1 and sdir2), run these commands:
cd /home/sun/
mv sdir1/mp4/*.mp4 sdir1/
mv sdir2/mp4/*.mp4 sdir2/
Edit:
Make a script that iterates all the directories:
Create a script and name it and edit it with your favorite editor (nano, vim, gedit, ...):
gedit folderIterator.sh
The script file content is:
#/bin/bash
# Go to the desired directory
cd /home/sun/
# Do an action over all the subdirectories in the folder
for dir in /home/sun/*/
do
dir=${dir%*/}
mv "$dir"/mp4/*.mp4 "$dir"/
# If you want to remove the subdirectory after moving the files, uncomment the following line
# rm -rf "$dir"
done
Save the file and give it execute permissions:
chmod +x folderIterator.sh
And execute it:
./folderIterator.sh
You can do this:
# move all .mp4 files from sdir1/mp4 to sdir1 directory
user#host:~/home/sun$ mv sdir1/mp4/*.mp4 sdir/
# move all .mp4 files from collection/sdir2/mp4 to collection/sdir2 directory
user#host:~/home/sun$ mv collection/sdir2/mp4/*.mp4 collection/sdir2/
# move only 1 file
user#host:~/home/sun$ mv sdir1/mp4/file.mp4 sdir/
user#host:~/home/sun$ mv collection/sdir2/mp4/file.mp4 collection/sdir2/
I suggest you use find and something like
cd /home/sun/sdir1/mp4/
find . -name "*" -exec mv {} /home/sun/sdir1/ \;
cd /home/sun/collection/sdir2/mp4/
find . -name "*" -exec mv {} /home/sun/collection/sdir2/ \;
Alternatively, you could use tar and something like
cd /home/sun/sdir1/mp4/
tar cfp - * | (cd ../ ; tar xvvf -)
# Make sure everything looks good
rm -rf mp4
cd /home/sun/collection/sdir2/mp4/
tar cfp - * | (cd ../ ; tar xvvf -)
# Make sure everything looks good
rm -rf mp4
The command to move a file (or directory) up one level is:
mv /home/sun/sdir1/mp4/file.mp4 ..
Wildcards can be used to select more files & directories, you can also provide more than one directory at a time.
mv /home/sun/sdir1/mp4/*.mp4 /home/sun/collection/sdir2/mp4/*.mp4 ..

copy entire directory excluding a file

As we know, cp -r source_dir intended_new_directory creates a copy of source directory with a new name. Now I want to do the same but want to exclude a particular file. I have found some related answers here, using tar and rsync, but in those solutions I need to create the destination directory first (using mkdir).
I honestly searched a lot, but didn't find exactly what I want.
So far the best I got is this:
tar -c --exclude=\*.dll --exclude=\*.exe sourceDir | tar -x -C destDir
(from http://www.linuxquestions.org/questions/programming-9/how-to-copy-an-entire-directory-structure-except-certain-files-385321/)
If you have binutils, you could use find to filter next cpio to copy (and create directories) :
find <sourceDir> \( ! -name *.dll \) -a \( ! -name *.exe \) | cpio -dumpv <destDir>
Try this by excluding the file using 'grep -v' ->
cp `ls | grep -v <exclude-file>` <dest-dir>
If the directory is not very large I used to write something like this:
src=path/to/source/directory
dst=path/to/destination/directory
find $src -type f | while read f ; do mkdir -p "$dst/`dirname $f`"; cp "$f" "$dst/$f" ; done
Here we list all regular files in $src, iterate over this list and for each file make a directory in $dst if it does not exist yet (-p option of mkdir), then copy the file to that directory.
The above command will copy all the files. Finally, just use
find $src -type f | grep -v whatever | while ...... # same as above
to filter out the files you don't need (e.g. \.bak$, \.orig$, or whatever files you don't want to copy).
Move all exclude file into home or other directory,copy the directory containing all remaining files to the destination folder then restore all exclude files.
#cd mydirectory
#mv exclude1 exclude2 /home/
#cp mydirectory destination_folder/
#cd /home/
#mv eclude1 exclude2 mydirectory/

Recursively create directory tree within existing subdirectories

I have a working directory with a large number of subfolders (i.e 1190A, 1993A etc).
'/working/1190A'
'/working/1993A'
I would like to recursively create a certain directory tree within each subfolder. For example:
'/working/1190A/analysis/1'
'/working/1993A/analysis/1'
etc
Thanks.
To force the system create a directory tree without having to create each level of it, add -p to the mkdir command.
Hence, this could work:
for dir in list_of_folders
do
mkdir -p $dir/your/directory/tree
[ $? ] && echo "error on $dir" # if the dir could not be created, print error (thanks #hetepeperfan - see comments)
done
Note that the list_of_folders can be given like /working/1190A /working/1993A, but also generated with a find command. This is just a first version you'd better adapt to your specific requirements.
This is one liner as
cd /working
find . -maxdepth 1 -type d -exec mkdir -p '{}'/analysis/1 \;
yourDirList=/working/*
for f in $yourDirList
do
if [ -d $f ]
mkdir -p $f/analysis/1
fi
done

Copy folder structure (without files) from one location to another

I want to create a clone of the structure of our multi-terabyte file server. I know that cp --parents can move a file and it's parent structure, but is there any way to copy the directory structure intact?
I want to copy to a linux system and our file server is CIFS mounted there.
You could do something like:
find . -type d > dirs.txt
to create the list of directories, then
xargs mkdir -p < dirs.txt
to create the directories on the destination.
cd /path/to/directories &&
find . -type d -exec mkdir -p -- /path/to/backup/{} \;
Here is a simple solution using rsync:
rsync -av -f"+ */" -f"- *" "$source" "$target"
one line
no problems with spaces
preserve permissions
I found this solution there
1 line solution:
find . -type d -exec mkdir -p /path/to/copy/directory/tree/{} \;
I dunno if you are looking for a solution on Linux. If so, you can try this:
$ mkdir destdir
$ cd sourcedir
$ find . -type d | cpio -pdvm destdir
This copy the directories and files attributes, but not the files data:
cp -R --attributes-only SOURCE DEST
Then you can delete the files attributes if you are not interested in them:
find DEST -type f -exec rm {} \;
This works:
find ./<SOURCE_DIR>/ -type d | sed 's/\.\/<SOURCE_DIR>//g' | xargs -I {} mkdir -p <DEST_DIR>"/{}"
Just replace SOURCE_DIR and DEST_DIR.
The following solution worked well for me in various environments:
sourceDir="some/directory"
targetDir="any/other/directory"
find "$sourceDir" -type d | sed -e "s?$sourceDir?$targetDir?" | xargs mkdir -p
This solves even the problem with whitespaces:
In the original/source dir:
find . -type d -exec echo "'{}'" \; > dirs2.txt
then recreate it in the newly created dir:
mkdir -p <../<SOURCEDIR>/dirs2.txt
Substitute target_dir and source_dir with the appropriate values:
cd target_dir && (cd source_dir; find . -type d ! -name .) | xargs -i mkdir -p "{}"
Tested on OSX+Ubuntu.
If you can get access from a Windows machine, you can use xcopy with /T and /E to copy just the folder structure (the /E includes empty folders)
http://ss64.com/nt/xcopy.html
[EDIT!]
This one uses rsync to recreate the directory structure but without the files.
http://psung.blogspot.com/2008/05/copying-directory-trees-with-rsync.html
Might actually be better :)
A python script from Sergiy Kolodyazhnyy
posted on Copy only folders not files?:
#!/usr/bin/env python
import os,sys
dirs=[ r for r,s,f in os.walk(".") if r != "."]
for i in dirs:
os.makedirs(os.path.join(sys.argv[1],i))
or from the shell:
python -c 'import os,sys;dirs=[ r for r,s,f in os.walk(".") if r != "."];[os.makedirs(os.path.join(sys.argv[1],i)) for i in dirs]' ~/new_destination
FYI:
Copy top level folder structure without copying files in linux
How do I copy a directory tree but not the files in Linux?
Another approach is use the tree which is pretty handy and navigating directory trees based on its strong options. There are options for directory only, exclude empty directories, exclude names with pattern, include only names with pattern, etc. Check out man tree
Advantage: you can edit or review the list, or if you do a lot of scripting and create a batch of empty directories frequently
Approach: create a list of directories using tree, use that list as an arguments input to mkdir
tree -dfi --noreport > some_dir_file.txt
-dfi lists only directories, prints full path for each name, makes tree not print the indentation lines,
--noreport Omits printing of the file and directory report at the end of the tree listing, just to make the output file not contain any fluff
Then go to the destination where you want the empty directories and execute
xargs mkdir < some_dir_file.txt
find source/ -type f | rsync -a --exclude-from - source/ target/
Copy dir only with associated permission and ownership
Simple way:
for i in `find . -type d`; do mkdir /home/exemplo/$i; done
cd oldlocation
find . -type d -print0 | xargs -0 -I{} mkdir -p newlocation/{}
You can also create top directories only:
cd oldlocation
find . -maxdepth 1 -type d -print0 | xargs -0 -I{} mkdir -p newlocation/{}
Here is a solution in php that:
copies the directories (not recursively, only one level)
preserves permissions
unlike the rsync solution, is fast even with directories containing thousands of files as it does not even go into the folders
has no problems with spaces
should be easy to read and adjust
Create a file like syncDirs.php with this content:
<?php
foreach (new DirectoryIterator($argv[1]) as $f) {
if($f->isDot() || !$f->isDir()) continue;
mkdir($argv[2].'/'.$f->getFilename(), $f->getPerms());
chown($argv[2].'/'.$f->getFilename(), $f->getOwner());
chgrp($argv[2].'/'.$f->getFilename(), $f->getGroup());
}
Run it as user that has enough rights:
sudo php syncDirs.php /var/source /var/destination

Resources