Using find to delete symbolic links except those which point to directories - linux

At the moment I recursively remove all softlinks from my current working directory like this:
find . -type l -delete
But I don't want to remove symlinks pointing to a directory anymore.
Is there simple way to customize the find command or do I have to omit the -delete and script something to inspect every found softlink "myself" before removing?

As already suggested in the comments, you can use the test utility for this; but you don't need readlink because test -d always resolves symbolic links.
# replace -print with -exec rm {} +
find . -type l ! -exec test -d {} \; -print
It might be slow due to the overhead from spawning a new process for each symlink though. If that's a problem, you can incorporate a small shell script in your find command to process them in bulks.
find . -type l -exec sh -c '
for link; do
shift
if ! test -d "$link"; then
set "$#" "$link"
fi
done
# remove echo
echo rm "$#"' sh {} +
Or, if you have GNU find installed, you can utilize the -xtype primary.
# replace -print with -delete
find -type l ! -xtype d -print

Related

How to find all files in subdirectories that match pattern and replace pattern

I am attempting to move some video files of mine into new subdirectories while also renaming them on my Unraid system. The files all follow a similar naming convention:
featurette name-featurette.mkv
I would like to move these files from their current directory to a subdirectory and rename them like this:
featurettes/featurette name.mkv
I am able to create the directories and relocate the files using find and execdir:
find . -type f -name *-featurette.mkv -maxdepth 2 -execdir mkdir ./featurettes/ \;
find . -type f -name *-featurette.mkv -maxdepth 2 -execdir mv {} ./featurettes/ \;
I am struggling with the renaming piece. I've tried the rename command but am unable to get it to work within the featurettes directory, let alone from two directories above, which is where I'd like to execute the command. I've tried the following command within the featurettes directory:
rename \-featurette.mkv .mkv *
However I get the error:
invalid option -- 'f'
I thought by escaping the dash I could avoid that issue, but it doesn't appear to work. Any advice on how to remove this pattern from all files within subdirectories matching it would be very much appreciated.
From man rename you see this command gets options and 3 positional parameters:
SYNOPSIS
rename [options] expression replacement file...
So in your case the first parameter is being interpreted as an option. You may use this syntax:
rename -- '-featurette' '' *-featurette.mkv
to rename the files. -- indicates that any options are over and what follows are only positional parameters.
Totally, to copy the files with one mv process and rename them:
mkdir -p target/dir
find . -maxdepth 2 -type f -name "*-featurette.mkv" -exec mv -t target/dir {} +
cd target/dir && rename -- '-featurette' '' *-featurette.mkv
If you want to rename many files located into different subdirectories, you can use this syntax:
find . -name "*-featurette.mkv" -print0 | xargs -r0 rename -- '-featurette' ''
find . \
-maxdepth 2 \
-type f \
-name '*-featurette.mkv' \
-execdir sh -c '
echo mkdir -p ./featurettes/
echo mv -- "$#" ./featurettes/
' _ {} \+
Issue with your implementations I fixed or improved:
-maxdepth 2 must precede -type f
-name '*-featurette.mkv' must have the pattern quoted to prevent the shell to expand globb it.
-execdir is best used with an inline shell, so it can also process multiple arguments from the same directory
Also keep in mind that while running a command with -execdir, find will cd to that directory. It means that mv -- "$#" ./featurettes/' will move files into the ./featurettes/' directory relative to were -execdir has just cd.
Version which also rename files while moving:
( has no echo dry-run protection, so use only if you are sure it does what you want )
#!/usr/bin/env sh
find . \
-maxdepth 2 \
-depth \
-name '*-featurette.mkv' \
-type f \
-execdir sh -c '
mkdir -p featurettes
for arg
do
basename=${arg##*/}
mv -- "$basename" "./featurettes/${basename%-featurette.mkv}.mkv"
done
' _ {} +
You can use Bash's shell parameter expansion feature to get the part of the file name, for example:
$> filename=name-featurette.mkv
$> echo ${filename%-*} #To print first part before '-'
name
$> echo ${filename##*.} #To get the extension
mkv
$> echo ${filename#*-} #To print the part after '-' with extension
featurette.mkv
With this and slightly modifying your find command, you should be able to move+rename your files:
find . -type f -name '*-featurette.mkv' -maxdepth 2 -execdir sh -c 'f="{}"; mv -- "$f" ./featurettes/"${f%-*}.mkv"' \;
In fact you should be able to combine both the find command into one to create_dir, move and rename file.
find . -type f -name '*-featurette.mkv' -maxdepth 2 -execdir sh -c 'f="{}"; mkdir ./featurettes; mv -- "$f" ./featurettes/"${f%-*}.mkv"' \;

How to write a unix command or script to remove files of the same type in all sub-folders under current directory?

Is there a way to remove all temp files and executables under one folder AND its sub-folders?
All that I can think of is:
$rm -rf *.~
but this removes only temp files under current directory, it DOES NOT remove any other temp files under SUB-folders at all, also, it doesn't remove any executables.
I know there are similar questions which get very well answered, like this one:
find specific file type from folder and its sub folder
but that is a java code, I only need a unix command or a short script to do this.
Any help please?
Thanks a lot!
Perl from command line; should delete if file ends with ~ or it is executable,
perl -MFile::Find -e 'find(sub{ unlink if -f and (/~\z/ or (stat)[2] & 0111) }, ".")'
You can achieve the result with find:
find /path/to/directory \( -name '*.~' -o \( -perm /111 -a -type f \) \) -exec rm -f {} +
This will execute rm -f <path> for any <path> under (and including) /path/to/base/directory which:
matches the glob expression *.~
or which has an executable bit set (be it owner, group or world)
The above applies to the GNU version of find.
A more portable version is:
find /path/to/directory \( -name '*.~' -o \( \( -perm -01 -o -perm -010 -o -perm -0100 \) \
-a -type f \) \) -exec rm -f {} +
find . -name "*~" -exec rm {} \;
or whatever pattern is needed to match the tmp files.
If you want to use Perl to do it, use a specific module like File::Remove
This should do the job
find -type f -name "*~" -print0 | xargs -r -0 rm

find command in bash script resulting in "No such file or directory" error only for directories?

UPDATE 2014-03-21
So I realized I wasn't as efficient as I could be, as all the disks that I needed to "scrub" were under /media and named "disk1, disk2,disk3, etc." Here's the final script:
DIRTY_DIR="/media/disk*"
find $DIRTY_DIR -depth -type d -name .AppleDouble -exec rm -rf {} \;
find $DIRTY_DIR -depth -type d -name .AppleDB -exec rm -rf {} \;
find $DIRTY_DIR -depth -type d -name .AppleDesktop -exec rm -rf {} \;
find $DIRTY_DIR -type f -name ".*DS_Store" -exec rm -f {} \;
find $DIRTY_DIR -type f -name ".Thumbs.db" -exec rm -f {} \; # I know, I know, this is a Windows file.
Next will probably to just clean up the code even more, and add features like logging and reporting results (through e-mail or otherwise); excluding system and directories; and allowing people to customize the list of files/directories.
Thanks for all the help!
UPDATE
Before I incorporated the helpful suggestions provided by everyone, I performed some tests, the results of which were very interesting (see below).
As a test, I ran this command:
root#doi:~# find /media/disk3 -type d -name .AppleDouble -exec echo rm -rf {} \;
The results (which is what I expected):
rm -rf /media/disk3/Videos/Chorus/.AppleDouble
However, when I ran the actual command (without echo):
root#doi:~# find /media/disk3 -type d -name .AppleDouble -exec rm -rf {} \;
I received the same "error" output:
find: `/media/disk3/Videos/Chorus/.AppleDouble': No such file or directory
I put "error" in quotes because obviously the folder was removed, as verified by immediately running:
root#doi:~# find /media/disk3 -type d -name .AppleDouble -exec rm -rf {} \;
root#doi:~#
It seems like the find command stored the original results, acted on it by deleting the directory, but then tried to delete it again? Or is the -f option of rm, which is supposed to be for ignoring nonexistent files and arguments, is ignored? I note that when I run tests with the rm command alone without the find command, everything worked as expected. Thus, directly running rm -rf ... \nonexistent_directory, no errors were returned even though the "non_existent_directory" was not there, and directly running rm -r \nonexistent_directory provided the expected:
rm: cannot remove 'non_existent_directory': No such file or directory
Should I use the -delete option instead of the -exec rm ... option? I had wanted to make the script as broadly applicable as possible for systems that didn't have -delete option for find.
Lastly, I don't presume it matters if /media/disk1, /media/disk2, ... are combined in an AUFS filesystem under /media/storage as the find command is operating on the individual disks themselves?
Thanks for all the help so far, guys. I'll publish the script when I'm done.
ORIGINAL POST
I'm writing a bash script to delete a few OS X remnants on my Lubuntu file shares. However, when executing this:
...
BASE_DIR="/media/disk" # I have 4 disks: disk1, disk2, ...
COUNTER=1
while [ $COUNTER -lt 5 ]; do # Iterate through disk1, disk2, ...
DIRTY_DIR=${BASE_DIR}$COUNTER # Look under the current disk counter /media/disk1, /media/disk2, ...
find $DIRTY_DIR -name \.AppleDouble -exec rm -rf {} \; # Delete all .AppleDouble directories
find $DIRTY_DIR -name ".*DS_Store" -exec rm -rf {} \; # Delete all .DS_Store and ._.DS_Store files
COUNTER=$(($COUNTER+1))
done
...
I see the following output:
find: /media/disk1/Pictures/.AppleDouble: No such file or directory
Before I added the -exec rm ... portion the script found the /media/disk1/Pictures/.AppleDouble directory. The script works properly for removing DS_Store files, but what am I missing for the find command for directories?
I'm afraid to screw too much with the -exec portion as I don't want to obliterate directories in error.
tl;dr - Pass -prune if you're deleting directories using find.
For anyone else who stumbles on this question. Running an example like this
find /media/disk3 -type d -name .AppleDouble -exec rm -rf {} \;
results in an error like
rm: cannot remove 'non_existent_directory': No such file or directory
When finding and deleting directories with find, you'll often encounter this error because find stores the directory to process subdirectories, then deletes it with exec, then tries to traverse the subdirectories which no longer exist.
You can either pass -maxdepth 0 or -prune to prevent this issue. Like so:
find /media/disk3 -type d -name .AppleDouble -prune -exec rm -rf {} \;
Now it deletes the directories without any errors. Hurray! :)
You don't need to escape DOT in shell glob as this is not regex. So use .AppleDouble instead of \.AppleDouble:
find $DIRTY_DIR -name .AppleDouble -exec rm -rf '{}' \;
PS: I don't see anywhere $COUNTER being incremented in your script.

Remove all files under cache directory using find

I'm trying to use find to remove all files under cache directories in a web hosted environment.
find /home/hosted -maxdepth 2 -type d -name "cache" -print -exec rm -rf "{}/*" \;
I've tried several variations of this, but for some reason find won't remove the cache/* files. Anyone see anything I'm missing?
Thanks
Arguments for -exec don't get expanded as you expect. That is because -exec calls execve() directly and thus * does not get expanded to all files in a matching directory. If you want to have shell expansion, you have to feed -exec with /bin/sh (or a shell of your choice), like this:
find /your/dir -name "cache" -type d -maxdepth 2 -print -exec sh -c "rm -f {}/*" \;

nonzero return code although find -exec rm works

I'm on a linux system I wonder what is wrong with the following execution of find:
mkdir a && touch a/b
find . -name a -type d -exec echo '{}' \;
./a
find . -name a -type d -exec rm -r '{}' \;
find: `./a': No such file or directory
The invocation of echo is just for testing purposes. I would expect the last command to remove the directory './a' entirely and return 0. Instead it removes the directory and generates the error message. To repeat, it does remove the directory! What is going on?
rm executes without a problem. The issue is that find is confused, since it knew the directory ./a was there, it tries to visit that directory to look for directories named a. However, find cannot enter the directory, since it was already removed.
One way to avoid this is to do
find -name a -type d | xargs rm -r
This will let the find move along before the rm command is executed. Or, you can simply ignore the error in your original command.
Based on epsalon's comment the solution is to use the -depth option which causes the deeper files to be visited first.
find . -depth -name a -type d -exec rm -r '{}' \;
does the trick. Thanks a bunch!
If performance is an issue, use -prune in order to prevent find from descending into directories named "a":
find . -name a -type d -prune -exec rm -r '{}' \;

Resources