Recursively find all directories that match a string, and symbolically link folder inside a new directory - linux

I have a large collection of photos, sorted by folder. I'd like to write a script that searches through, finds matching folder names, and creates symbolic links to the folders that match inside a new folder.
Something like this:
find /mnt/librarypool/user1/originals -type d -iname "*bbq*" -exec sh -c 'ln -s "{}" "/mnt/mediapool/bbq$("sh basename {}")"' \;
(this works, but returns an error, "sh: cannot open basename: No such file or directory")
This will find any folder that matches "bbq" inside /mnt/librarypool/user1/originals and will create a link inside /mnt/mediapool/bbq/ to that folder.
Is there a better way? And how can I clear that error?

You may be able to use this find command:
find /mnt/librarypool/user1/originals -type d -iname "*bbq*" -exec bash -c \
'for d; do ln -s "$d" "/mnt/mediapool/bbq/${d##*/}"; done' _ {} +
+ after find will pass multiple argument of found directories
for d will loop through those entries
${d##*/} is equivalent of basename command as it strips all path info

On my system at least the ln command does create a link with the basename of its target when given a directory as where to put the link, so something like this should indeed work:
find /mnt/librarypool/user1/originals \
-type d -iname "*bbq*" \
-exec ln -s {} /mnt/mediapool/bbq/ \;

Related

Remove content of many subfolders that are names equally

My folder structure looks like this:
|-20200912
-fringe
-other
|-20200915
-fringe
-other
...
And I want to delete the content of all folders called "fringe". How could I achieve this with one command?
I thought about something like this in pseudocode:
find . -name "fringe" -type d -exec rm <content>
You were close.
find . -name 'fringe' -type d -exec rm -rf {} +
This removes every directory named fringe and everything within them.
Single vs double quotes don't make a difference here, but generally prefer single quotes if you mean to pass something in verbatim (with the possible exception of when the thing you want to pass in contains literal single quotes).
If you want to remove the directory's contents, try
... -execdir sh -c 'rm '*' \;
You are looking to process the files and not the directory through rm and so I would use a regex that searches for for files with fringe in the directory path.
find -regex "^.*/fringe/.*$" -type f -exec rm '{}' \;
I would change the rm to ls to ensure that the results are expected before running the actual rm.
You can force gobbing in the exec command:
find -name fringe -type d -execdir sh -c 'rm {}/*' \;

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"' \;

Moving files with a pattern in their name to a folder with the same pattern as its name

My directory contains mix of hundreds of files and directories similar to this:
508471/
ae_lstm__ts_ 508471_detected_anomalies.pdf
ae_lstm__508471_prediction_result.pdf
mlp_508471_prediction_result.pdf
mlp__ts_508471_detected_anomalies.pdf
vanilla_lstm_508471_prediction_result.pdf
vanilla_lstm_ts_508471_detected_anomalies.pdf
598690/
ae_lstm__ts_598690_detected_anomalies.pdf
ae_lstm__598690_prediction_result.pdf
mlp_598690_prediction_result.pdf
mlp__ts_598690_detected_anomalies.pdf
vanilla_lstm_598690_prediction_result.pdf
vanilla_lstm_ts_598690_detected_anomalies.pdf
There are folders with an ID number as their names, like 508471 and 598690.
In the same path as these folders, there are pdf files that have this ID number as part of their name. I need to move all the pdf files with the same ID in their name, to their related directories.
I tried the following shell script but it doesn't do anything. What am I doing wrong?
I'm trying to loop over all the directories, find the files that have id in their name, and move them to the same dir:
for f in ls -d */; do
id=${f%?} # f value is '598690/', I'm removing the last character, `\`, to get only the id part
find . -maxdepth 1 -type f -iname *.pdf -exec grep $id {} \; -exec mv -i {} $f \;
done
#!/bin/sh
find . -mindepth 1 -maxdepth 1 -type d -exec sh -c '
for d in "$#"; do
id=${d#./}
for file in *"$id"*.pdf; do
[ -f "$file" ] && mv -- "$file" "$d"
done
done
' findshell {} +
This finds every directory inside the current one (finding, for example, ./598690). Then, it removes ./ from the relative path and selects each file that contains the resulting id (598690), moving it to the corresponding directory.
If you are unsure of what this will do, put an echo between && and mv, it will list the mv actions the script would make.
And remember, do not parse ls.
The below code should do the required job.
for dir in */; do find . -mindepth 1 -maxdepth 1 -type f -name "*${dir%*/}*.pdf" -exec mv {} ${dir}/ \;; done
where */ will consider only the directories present in the given directory, find will search only files in the given directory which matches *${dir%*/}*.pdf i.e file name containing the directory name as its sub-string and finally mv will copy the matching files to the directory.
in Unix please use below command
find . -name '*508471*' -exec bash -c 'echo mv $0 ${0/508471/598690}' {} \;
You may use this for loop from the parent directory of these pdf files and directories:
for d in */; do
compgen -G "*${d%/}*.pdf" >/dev/null && mv *"${d%/}"*.pdf "$d"
done
compgen -G is used to check if there is a match for given glob or not.

Bash: Find files containing a certain string and copy them into a folder

What I want:
In a bash script: Find all files in current directory that contain a certain string "teststring" and cop them into a subfolder "./testfolder"
Found this to find the filenames which im looking for
find . -type f -print0 | xargs -0 grep -l "teststring"
..and this to copy found files to another folder (here selecting by strings in filename):
find . -type f -iname "stringinfilename" -exec cp {} ./testfolder/ \;
Whats the best way to combine both commands to achieve what I described at the top?
Just let find do both:
find . -name subdir -prune -o -type f -exec \
grep -q teststring "{}" \; -exec cp "{}" subdir \;
Note that things like this are much easier if you don't try to add to the directory you're working in. In other words, write to a sibling dir instead of writing to a subdirectory. If you want to wind up with the data in a subdir, mv it when you're done. That way, you don't have to worry about the prune (ie, you don't have to worry about find descending into the subdir and attempting to duplicate the work).

How to ignore directories and certain patterns when supplying command-line arguments

I'm a newbie at Linux, and I am wondering if there's a 'one-liner' command that allows me to link everything in a directory to another directory, but ignoring subdirectories and certain wildcards from the source directory.
Let's be more specific...let's say I want to link everything in /foo to /bar/tmp as in...
ln -s /foo/* /bar/tmp/.
...but I want to:
ignore any subdirectories in /foo
ignore any files with the wildcard
runscript*
Any suggestions on how to do this?
You could use find like this
find /foo -maxdepth 1 -type f ! -name 'runscript*' -exec ln -s {} /bar/tmp/ \;
Something like
cd /bar/tmp
find /foo -maxdepth 1 -a -type f -a \! -name 'runscript*' |
while read file; do
ln -s "$file"
done
could do the trick.

Resources