Recursively delete all empty folders in Bash - bash

Is there a command to execute in Bash that deletes all empty folders recursively until there is no empty folder in the tree? I could execute this:
find . -type d -empty | xargs -I '{}' rmdir {}
repeatedly until there is no more empty folders, but I am looking for something a bit more efficient. Especially since that to know whether there are empty folders left, I would have to execute the same command, i.e. two calls to find . -type d -empty in each iteration.

This is simple, given the GNU find utility:
find . -type d -empty -delete
This will delete empty directories; since the -delete option implies the -depth option, it will delete directories that only had empty directories underneath them, so there's no need to run it multiple times.

Related

removing directory and sub directory which is not present in the list

This is my directory structure
find -type f -print0 | xargs -0 ls -t
./lisst.txt ./SAMN03272855/SRR1734376/SRR1734376_1.fastq.gz
./SAMN03272854/SRR1734375/SRR1734375_2.fastq.gz ./SAMN07605670/SRR6006890/SRR6006890_2.fastq.gz
./SAMN03272854/SRR1734375/SRR1734375_1.fastq.gz ./SAMN07605670/SRR6006890/SRR6006890_1.fastq.gz
./SAMN03272855/SRR1734376/SRR1734376_2.fastq.gz
So this is a small subset of my folder/files where i have around 70.
I have a made a list of files which i want to keep and other i would like to delete.
My list.txt contains SAMN03272854,SAMN03272855 but I want to remove SAMN07605670.
I ran this
find . ! -name 'lisst.txt' -type d -exec rm -vrf {} +
It removed everything
QUESTION UPDATE
In my list it contains the folder i want to keep and the one which are not there are to be removed.
The folders which are to be removed also contains subdirectories and files. I want to remove everything
Your command selects each directory in the tree, except a directories of the funny name lisst.txt. Once it finds a directory, you do a recursive remove of this directory. No surprise that your files are gone.
You can't use rm -r when you want to spare certain files from deletion. This means that you also can't remove a directory, which somewhere below in its subtree has a file you want to keep.
I would run two find commands: The first removes all the files, ignoring directories, and second one removes all directories, which are empty (bottom-up). Assuming that SAMN03272854 is indeed a file (as you told us in your question), this would be:
find . -type f \( ! \( -name SAMN03272854 -o -name SAMN03272855 \) \) -exec rm {}
find . -depth -type d -exec rmdir {} 2>/dev/null
The error redirection in the latter command suppresses messages from rmdir for directories which still contain files you want to keep. Of course other messages are also suppressed. I would during debugging run the command without error redirection, to see whether it is basically correct.
Things would get more complicated, if you have files and directories to keep, because to keep a directory likely implies to keep all the files below it. In this case, you can use the -prune option of find, which excludes directories including their subdirectories from being processed. See the find man page, which gives examples for this.

Using cp -ur, but update only directories

I have two directories that I want to contain mostly the same information.
I wrote the basic script
#!/bin/bash
# Update the two folders
cp -vur ../catkin_ws/src .
cp -vur . ../catkin_ws/src
Now I want to change this, to only update the directories and their content but not other files on the top level directory, like the bash script itself.
If that is not possible, is there a way to exclude certain files during the update?
Suppose you want to synchronize ../catkin_ws/src with the current directory, and the current script is located in the current directory. As far as I understand, you want to synchronize only the top-level directories including their contents, but not other types of nodes possibly located at the top level, i.e. directly within ../catkin_ws/src, or ./.
Then it is easily done with find command:
src_dir="../catkin_ws/src"
find "$src_dir" -mindepth 1 -maxdepth 1 -type d \
-exec cp -vru {} . \;
find ./ -mindepth 1 -maxdepth 1 -type d \
-exec cp -vru {} "$src_dir" \;
where {} stands for the next directory found.
If you want to filter further, you may use extra options such as -name, -path, or -regex. For example, the following skips directory x:
find "$src_dir" -mindepth 1 -maxdepth 1 -type d \
! -name 'x' \
-exec cp -vru {} . \;
where ! is causes the next expression to return True, if the expression is false, i.e. acts as some kind of logical NOT operator.
The drawback of the above commands is that the cp command is launched for each folder sequentially (due to \;). If you want to run single cp command for all sources, you may use an approach described in this answer.
P.S.
I didn't try to suggest better way to synchronize the directories, but only suggested a way to fix your current approach.

Bash script for removing specific file from certain subdirectories

On a unix server, I'm trying to figure out how to remove a file, say "example.xls", from any subdirectories that start with v0 ("v0*").
I have tried something like:
find . -name "v0*" -type d -exec find . -name "example.xls" -type f
-exec rm {} \;
But i get errors. I have a solution but it works too well, i.e. it will delete the file in any subdirectory, regardless of it's name:
find . -type f -name "example.xls" -exec rm -f {} \;
Any ideas?
You will probably have to do it in two steps -- i.e. first find the directories, and then the files -- you can use xargs to make it in a single line, like
find . -name "v0*" -type d | \
xargs -l -I[] \
find [] -name "example.xls" -type f -exec rm {} \;
what it does, is first generating a list of viable directory name, and let xargs call the second find with the names locating the file name within that directory
Try:
find -path '*/v0*/example.xls' -delete
This matches only files named example.xls which, somewhere in its path, has a parent directory name that starts with v0.
Note that since find offers -delete as an action, it is not necessary to invoke the external executable rm.
Example
Consider this directory structure:
$ find .
.
./a
./a/example.xls
./a/v0
./a/v0/b
./a/v0/b/example.xls
./a/v0/example.xls
We can identify files example.xls who have one of their parent directories named v0*:
$ find -path '*/v0*/example.xls'
./a/v0/b/example.xls
./a/v0/example.xls
To delete those files:
find -path '*/v0*/example.xls' -delete
Alternative: find only those files directly under directory v0*
find -regex '.*/v0[^/]*/example.xls'
Using the above directory structure, this approach returns one file:
$ find -regex '.*/v0[^/]*/example.xls'
./a/v0/example.xls
To delete such files:
find -regex '.*/v0[^/]*/example.xls' -delete
Compatibility
Although my tests were performed with GNU find, both -regex and -path are required by POSIX and also supported by OSX.

Bash script that deletes files older than N days but excludes files in certain folder

I need to create a bash script that deletes all files older than N days in downloads folder but would exclude all files in archive sub-folder. My folder structure is like this:
downloads/
user1_folder/
archive/
user2_folder/
archive/
...
Based on this Q&A I was able to create script that finds and deletes files older than N days, but I would like to exclude all files in archive subfolders.
#!/bin/bash
find ./downloads -mtime +32 -type f -delete
Try:
find ./downloads -maxdepth 2 -type f -mtime +32 -delete
-maxdepth levels
Descend at most levels (a non-negative integer) levels of directories below the command line arguments. -maxdepth 0
means only apply the tests and actions to the command line arguments.
Adding ! -path (your path) should do the trick
find ./downloads ! -path ./downloads/*/archive/* -mtime +32 -type f -delete

Exclude specified directory when using `find` command

I have a directory which contains a number of files (no subdirectories). I wish to find these files. The following gets me close:
$ find docs
docs
docs/bar.txt
docs/baz.txt
docs/foo.txt
I don't want the directory itself to be listed. I could do this instead:
$ find docs -type f
docs/bar.txt
docs/baz.txt
docs/foo.txt
Using a wildcard seems to do the trick as well:
$ find docs/*
docs/bar.txt
docs/baz.txt
docs/foo.txt
My understanding is that these work in different ways: with -type, we're providing a single path to find, whereas in the latter case we're using wildcard expansion to pass several paths to find. Is there a reason to favour one approach over the other?
You have a UNIX tag, and you example has a *. Some versions of find have a problem with that.
If the directory has no subdirectories.
FYI.
Generally the first parms to find has to be a directory or a list of directories
find /dir1 /dir2 -print
Find is recursive - so it will follow each directory down listing every thing, symlinks, directories, pipes, and regular files. This can be confusing. -type delimits your search
find /dir1 /dir2 -type f -print
You can also have find do extra output example: have it rm files older than 30 days for example:
find /dir1 /dir2 -type f -mtime +30 -exec rm {} \;
Or give complete infomation
find /dir1 /dir2 -type f -mtime +30 -exec ls -l {} \;
find /dir1 /dir2 -type f -mtime +30 -ls # works on some systems
To answer your question: because find can be dangerous ALWAYS fully specify each directory , file type ,etc., when you are using a nasty command like rm. You might have forgotten your favorite directory is also in there. Or the one used to generate your paycheck. Using a wildcard is ok for just looking around.
Using *
find /path/to/files -type f -name 'foo*'
-- tics or quotes around strings with a star in them in some UNIX systems.
find docs -type f
will get you a listing of every non-directory file of every subdirectory of docs
find docs/*
will get you a listing of every file AND every subdirectory of docs

Resources