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

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

Related

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

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

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 {}/*' \;

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.

/usr/bin/find: Argument list too long in for loop bash script

I have such a bash script. I want to gzip all .ppm files under a directory to another directory. For this reason I have written such a bash script:
cd /export/students/sait/12-may
for next_file in $(find . -type f ! -name *.ppm )
do
/bin/gzip -f -c $next_file > /export/students/sait/12-may-yedek/$next_file.gz
done
When I execute this script, I get such error:
/usr/bin/find: Argument list too long
How can I fix this problem?
Quote this part : *.ppm to prevent filename globbing, and also remove the ! as you want to find files with .ppm extension, not the other way around.
find . -type f -name '*.ppm'
Instead of running a loop you could do it with single find command which would provide white space safety:
find /export/students/sait/12-may -type f -name '*.ppm' -exec sh -c '/bin/gzip -f -c "$0" > "$0".gz' {} \;

Run an Executable Program File in Multiple Subdirectories Using Shell

I have a main directory with 361 subdirectories. Within the each subdirectory, there is a parameter file and one executable program file. The executable file is coded to look for the parameter file in the directory where the executable is located. (The same executable file is in all subdirectories. The parameter files all have the same file name in all subdirectories)
Instead of executing the program file individually, is there a cshell command for terminal to run them all at once?
UPDATED
If your Linux is so old it doesn't have -execdir, you could try this:
find $(pwd) -name YourProgram -exec dirname {} \; | while read d; do cd "$d" && pwd; done
If that correctly prints the names of the directories where your program needs to be run, just remove the pwd and replace with whatever you want done in tha directory - presumably something like this:
find $(pwd) -name YourProgram -exec dirname {} \; | while read d; do cd "$d" && ./YourPrgram; done
ORIGINAL ANSWER
Like this maybe:
find . -type f -name YourProgramName -execdir ./YourProgramName YourParameterFile \;
But backup first and check it looks right before using.
The -execdir causes find to change to the directory it has found before running the commands there.
If your command is more complicated, you can do this:
find . -type f -name YourProgramName -execdir sh -c "command1; command2; command3" \;
Check it does what you want like this:
find . -type f -name YourProgramName -execdir pwd \;
Maybe this will help. Suppose you have in each folder a file named params_file and an executable named exec_file, then:
for dir in `find . -maxdepth 1 -mindepth 1 -type d` ; do
cd $dir
cat params_file | xargs ./exec_file
cd ..
done

Resources