How to move entire content of a folder to its subfolder [duplicate] - linux

This question already has answers here:
Bash move * to subfolder fail: cannot move to a subdirectory of itself
(5 answers)
Closed 1 year ago.
How to move content of a folder to its subfolder.
When I run this:
mv xyz/* xyz/archive
I get this notice:
mv: cannot move 'xyz/archive' to a subdirectory of itself,
'xyz/archive/archive'
Is is possible to exclude archive folder from xyz/* selection ?

You can use extended globs to exclude archive from your pattern as follows:
shopt -s extglob
mv !(archive) archive
This will move everything in the current folder, except for hidden files, to archive.
If you want to move hidden files starting with a . to the archive folder too, you need to set the dotglob option as described in bash manual under Filename Expansion:
shopt -s dotglob
Here is another method using find:
find . -maxdepth 1 -type f ! -name "archive" -exec mv -t archive {} +
Although the first method is preferred, IMO, since bash builtins are perfectly capable of what find is doing here.

Yes, it is possible to exclude archive from the selection.
Enable the extended globbing option
shopt -s extglob
Run the following command that will move everything but archive
mv !(archive) archive
It will now move everything from xyz to archive
PS: I did run the mv command when I was inside the xyz folder.

Related

Bash script to sort files into sub folders based on extension

I have the following structure:
FolderA
Sub1
Sub2
filexx.csv
filexx.doc
FolderB
Sub1
Sub2
fileyy.csv
fileyy.doc
I want to write a script that will move the .csv files into the folder sub1 for each parent directory (Folder A, Folder B and so on) giving me the following structure:
FolderA
Sub1
filexx.csv
Sub2
filexx.doc
FolderB
Sub1
fileyy.csv
Sub2
fileyy.doc
This is what I have till now but I get the error mv: cannot stat *.csv: No such file or directory
for f in */*/*.csv; do
mv -v "$f" */*/Sub1;
done
for f in */*/*.doc; do
mv -v "$f" */*/Sub2;
done
I am new to bash scripting so please forgive me if I have made a very obvious mistake. I know I can do this in Python as well but it will be lengthier which is why I would like a solution using linux commands.
find . -name "*.csv" -type f -execdir mv '{}' Sub1/ \;
Using find, search for all files with the extension .csv and then when we find them, execute a move command from within the directory containing the files, moving the files to directory Sub1
find . -name "*.doc" -type f -execdir mv '{}' Sub2/ \;
Follow the same principle for files with the extension .doc but this time, move the files to Sub2.
I believe you are getting this error because no file matched your wildcard. When it happens, the for loop will give $f the value of the wildcard itself. You are basically trying to move the file *.csv which does not exist.
To prevent this behavior, you can add shopt -s nullglob at the top of your script. When using this, if no file is found, your script won't enter the loop.
My advise is, make sure you run your script from the correct location when using wildcards like this. But maybe what you meant to do by writing */*/*.csv is to recursively match all the csv files. If that's what you intended to do, this is not the right way to do it.
To recursively match all csv/doc/etc files using native bash you can add shopt -s globstar to the top of your script and use **/*.csv as wildcard
#!/bin/bash
shopt -s globstar nullglob
for f in **/*.csv; do
mv "$f" Destination/ # Note that $f is surrounded by "" to handle whitespaces in filenames
done
You could also use the find (1) utility to achieve that. But if you're planning to do more processing on the files than just moving them, a for loop might be cleaner as you won't have to inline everything in the same command.
Side note : "Linux commands" as you say are actually not Linux commands, they are part of the GNU utilities (https://www.gnu.org/gnu/linux-and-gnu.en.html)
If csv files you want to move are in the top directories (from the point of view of the current directory), but not in the subdirectories of them, then simply:
#!/bin/bash
for dir in */; do
mv -v "$dir"*.csv "${dir}Sub1/"
mv -v "$dir"*.doc "${dir}Sub2/"
done
If the files in all subdirectories are wanted to be moved similarly, then:
shopt -s globstar
for file in **/*.csv; do
mv -v "$file" "${file%/*}/Sub1/"
done
for file in **/*.doc; do
mv -v "$file" "${file%/*}/Sub2/"
done
Note that, the directories Sub1 and Sub2 are relative to the directory where csv and doc files reside.

Run script on every level of directory

I have a script called summarize.sh which produces a summary of the file/dirs inside of a directory. I would like to have it run recursively down the whole tree from the top. Whats a good way to do this?
I have tried to loop it with a for loop with
for dir in */; do
cd $dir
./summarize.sh
cd ..
however it returns ./summarize.sh: no file or directory
Is it because I am not moving the script as I run it? I am not very familiar with unix directories.
You can recursively list files using find . -type f and make your script take the interested file as a first argument, so you can do find . -type f -exec myScript.sh {} \;
If you want directories only, use find . -type d instead, or if you want both use just find . without restriction.
Additional option by name, e.g. find . -name '*.py'
Finally, if you do not want to recurse down the directory structure, i.e. only summarize the top level, you can use -maxdepth 1 option, so something like find . -type d -maxdepth 1 -exec myScript.sh {} \;.
The issue is that you are changing to a different directory with the cd command while your summarize.sh script is not located in these directories. One possible solution is to use an absolute path instead of a relative one. For example, change:
./summarize.sh
to something like:
/path/to/file/summarize.sh
Alternatively, under the given example code, you can also use a relative path pointing to the previous directory like this:
../summarize.sh
Try this code if you are running Bash 4.0 or later:
#! /bin/bash -p
shopt -s nullglob # Globs expand to nothing when they match nothing
shopt -s globstar # Enable ** to expand over the directory hierarchy
summarizer_path=$PWD/summarize.sh
for dir in **/ ; do
cd -- "$dir"
"$summarizer_path"
cd - >/dev/null
done
shopt -s nullglob avoids an error in case there are no directories under the current one.
The summarizer_path variable is set to an absolute path for the summarize.sh program. That is necessary to allow it to be run in directories other than the current one. (./summarize.sh only works in the current directory, ..)
Use cd -- ... to avoid problems if any directory name begins with '-'.
cd - >/dev/null to cd to the previous directory, and throw away its path when it is output by cd -.
Shellcheck issues several warnings about the code above, all to do with the use of cd. I'd fix them for "real" code.

Bash script to copy folder and contents without one file

I'm trying to copy a directories contents without one file. The problem i'm having is that the file is a few folders nested and the extglob operator fails to match.
Assume the following folder structure:
I would like to copy everything from source, including subfolders and files into dest except smu.txt.
I would have thought the following would do the trick:
#!/bin/bash
shopt -s extglob
cp -vr source/!(smu.txt) dest/
But it still copies smu.txt.
I also tried the following without success:
#!/bin/bash
shopt -s extglob
shopt -s globstar
cp -vr source/!(**/smu.txt) dest/
It if smu.txt is directly under ../source it successfully ignores it, but how do I get it to ignore files within subdirectories?
Have you tried using find?
Maybe this works:
$ find -name "source/*!(smu.txt)" -exec cp -vr {} dest/\;

How to move all files including hidden files into parent directory via *

Its must be a popular question but I could not find an answer.
How to move all files via * including hidden files as well to parent directory like this:
mv /path/subfolder/* /path/
This will move all files to parent directory like expected but will not move hidden files. How to do that?
You can find a comprehensive set of solutions on this in UNIX & Linux's answer to How do you move all files (including hidden) from one directory to another?. It shows solutions in Bash, zsh, ksh93, standard (POSIX) sh, etc.
You can use these two commands together:
mv /path/subfolder/* /path/ # your current approach
mv /path/subfolder/.* /path/ # this one for hidden files
Or all together (thanks pfnuesel):
mv /path/subfolder/{.,}* /path/
Which expands to:
mv /path/subfolder/* /path/subfolder/.* /path/
(example: echo a{.,}b expands to a.b ab)
Note this will show a couple of warnings:
mv: cannot move ‘/path/subfolder/.’ to /path/.’: Device or resource busy
mv: cannot remove /path/subfolder/..’: Is a directory
Just ignore them: this happens because /path/subfolder/{.,}* also expands to /path/subfolder/. and /path/subfolder/.., which are the directory and the parent directory (See What do “.” and “..” mean when in a folder?).
If you want to just copy, you can use a mere:
cp -r /path/subfolder/. /path/
# ^
# note the dot!
This will copy all files, both normal and hidden ones, since /path/subfolder/. expands to "everything from this directory" (Source: How to copy with cp to include hidden files and hidden directories and their contents?)
I think this is the most elegant, as it also does not try to move ..:
mv /source/path/{.[!.],}* /destination/path
This will move all files to parent directory like expected but will
not move hidden files. How to do that?
You could turn on dotglob:
shopt -s dotglob # This would cause mv below to match hidden files
mv /path/subfolder/* /path/
In order to turn off dotglob, you'd need to say:
shopt -u dotglob
Alternative simpler solution is to use rsync utility:
sudo rsync -vuar --delete-after --dry-run path/subfolder/ path/
Note: Above command will show what is going to be changed. To execute the actual changes, remove --dry-run.
The advantage is that the original folder (subfolder) would be removed as well as part of the command, and when using mv examples here you still need to clean up your folders, not to mention additional headache to cover hidden and non-hidden files in one single pattern.
In addition rsync provides support of copying/moving files between remotes and it would make sure that files are copied exactly as they originally were (-a).
The used -u parameter would skip existing newer files, -r recurse into directories and -v would increase verbosity.
By using the find command in conjunction with the mv command, you can prevent the mv command from trying to move directories (e.g. .. and .) and subdirectories. Here's one option:
find /path/subfolder -maxdepth 1 -type f -name '*' -exec mv -n {} /path \;
There are problems with some of the other answers provided. For example, each of the following will try to move subdirectories from the source path:
1) mv /path/subfolder/* /path/ ; mv /path/subfolder/.* /path/
2) mv /path/subfolder/{.,}* /path/
3) mv /source/path/{.[!.],}* /destination/path
Also, 2) includes the . and .. files and 3) misses files like ..foobar, ...barfoo, etc.
You could use, mv /source/path/{.[!.],..?,}* /destination/path, which would include the files missed by 3), but it would still try to move subdirectories. Using the find command with the mv command as I describe above eliminates all these problems.
Let me introduce you to my friend "dotglob". It turns on and off whether or not "*" includes hidden files.
$ mkdir test
$ cd test
$ touch a b c .hidden .hi .den
$ ls -a
. .. .den .hi .hidden a b c
$ shopt -u dotglob
$ ls *
a b c
$ for i in * ; do echo I found: $i ; done
I found: a
I found: b
I found: c
$ shopt -s dotglob
$ ls *
.den .hi .hidden a b c
$ for i in * ; do echo I found: $i ; done
I found: .den
I found: .hi
I found: .hidden
I found: a
I found: b
I found: c
It defaults to "off".
$ shopt dotglob
dotglob off
It is best to turn it back on when you are done otherwise you will confuse things that assume it will be off.
My solution for this problem when I have to copy all the files (including . files) to a target directory retaining the permissions is: (overwrite if already exists)
yes | cp -rvp /source/directory /destination/directory/
yes is for automatically overwriting destination files,
r recursive,
v verbose,
p retain permissions.
Notice that the source path is not ending with a / (so all the files/directory and . files are copied)
Destination directory ends with / as we are placing contents of the source folder to destination as a whole.
Just do
for I in $(ls -A dir)
do
mv dir/$I newDir
done
Assuming you are in the subfolder
run find . -maxdepth 1 -exec mv {} .. \;

Copy all files in a directory to a local subdirectory in linux

I have a directory with the following structure:
file_1
file_2
dir_1
dir_2
# etc.
new_subdir
I'd like to make a copy of all the existing files and directories located in this directory in new_subdir. How can I accomplish this via the linux terminal?
This is an old question, but none of the answers seem to work (they cause the destination folder to be copied recursively into itself), so I figured I'd offer up some working examples:
Copy via find -exec:
find . ! -regex '.*/new_subdir' ! -regex '.' -exec cp -r '{}' new_subdir \;
This code uses regex to find all files and directories (in the current directory) which are not new_subdir and copies them into new_subdir. The ! -regex '.' bit is in there to keep the current directory itself from being included. Using find is the most powerful technique I know, but it's long-winded and a bit confusing at times.
Copy with extglob:
cp -r !(new_subdir) new_subdir
If you have extglob enabled for your bash terminal (which is probably the case), then you can use ! to copy all things in the current directory which are not new_subdir into new_subdir.
Copy without extglob:
mv * new_subdir ; cp -r new_subdir/* .
If you don't have extglob and find doesn't appeal to you and you really want to do something hacky, you can move all of the files into the subdirectory, then recursively copy them back to the original directory. Unlike cp which copies the destination folder into itself, mv just throws an error when it tries to move the destination folder inside of itself. (But it successfully moves every other file and folder.)
You mean like
cp -R * new_subdir
?
cp take -R as argument which means recursive (so, copy also directories), * means all files (and directories).
Although * includes new_subdir itself, but cp detects this case and ignores new_subdir (so it doesn't copy it into itself!)
Try something like:
cp -R * /path_to_new_dir/

Resources