bash: moving files to original directory based on filename? - bash

I've got a bunch of subdirectories with a couple thousand PNG files that will be sent through Photoshop, creating PSD files. Photoshop can only output those to a single folder, and I want to move each one back to their original directory - so the new file foo_bar_0005.psd should go to where foo_bar_0005.png already is. Every filename only exists once.
Can somebody help me with this? I'm on OSX.

You might start from this minimal script:
#!/bin/bash
search_dir="search/png/from/this/directory/"
psd_dir="path/to/psd/directory/"
for psd_file in "$psd_dir"*.psd; do
file_name="$(echo $psd_file | sed 's/.*\/\(.*\).psd$/\1/g')"
png_dir="$(find $search_dir -name $file_name.png | grep -e '.*/' -o)"
mv $psd_file $png_dir
done
But note that this script doesn't include any error handlers e.g. file collision issue, file not found issue, etc.

Each file found with this find is piped to a Bash command that successively make the psd conversion and move the .psd to the .png original directory.
psd_dir=/psd_dir/
export psd_dir
find . -type f -name '*.png' | xargs -L 1 bash -c 'n=${1##*/}; echo photoshop "$1" && echo mv ${psd_dir}${n%.png}.psd ${1%/*}/; echo' \;
The echo are here to give you an overview of the result.
You should remove them to launch the real photoshop command.

Related

Shell script for finding (and deleting) video files if they came from a rar

My download program automatically unrars rar archives, which is all well and good as Sonarr and Radarr need that original video file to import. But now my download HDD fills up with all these video files I no longer need.
I've tried playing around with modifying existing scripts I have, but every step seems to take me further from the goal.
Here's what I have so far (that isnt working and I clearly dont know what im doing). My main problem is I can't get it to find the files correctly yet. This script jumps right to "no files found". So I'm doing the search wrong at the very least. Or I'm pretty sure I might need to completely rewrite from scratch using a different method I'm not aware of..
#!/bin/bash
# Find video files and if it came from a rar, remove it.
# If no directory is given, work in local dir
if [ "$1" = "" ]; then
DIR="."
else
DIR="$1"
fi
# Find all the MKV files in this dir and its subdirs
find "$DIR" -type f -name '*.mkv' | while read filename
do
# If video file and rar file exists, delete mkv.
for f in ...
do
if [[ -f "$DIR/*.mkv" ]] && [[ -f "$DIR/*.rar" ]]
then
# rm $filename
printf "[Dry run delete]: $filename\n"
else
printf "No files found\n"
exit 1
fi
done
Example of directory structure before and after. Note the file names are often different to the extracted file. And I want to leave other folders that don't have rars in them alone.
Before:
/folder/moviename/Movie.that.came.from.rar.2021.dvdrip.mkv
/folder/moviename/movie.rar
/folder/moviename/movie.r00
/folder/moviename/movie.r01
/folder/moviename2/Movie.that.lives.alone.2021.dvdrip.mkv
/folder/moviename2/Movie.2021.dvdrip.nfo
After
# (deleted the mkv only from the first folder)
/folder/moviename/movie.rar
/folder/moviename/movie.r00
/folder/moviename/movie.r01
# (this mkv survives)
/folder/moviename2/Movie.that.lives.alone.2021.dvdrip.mkv
/folder/moviename2/Movie.2021.dvdrip.nfo
TL:DR I would like a script to look recursively in my download drive for video files and rar files, and if it sees both in the same folder, delete the video file.
With GNU find, you can condense this to one command:
find "${1:-.}" -type f -name '*.rar' -execdir sh -c 'echo rm *.mkv' \;
${1:-.} says "use $1, or . if $1 is undefined or empty".
For each .rar file found, this starts a new shell in the directory of the file found (that's what -execdir sh -c '...' does) and runs echo rm *.mkv.
If the list of files to delete looks correct, you can actually delete them by dropping the echo:
find "${1:-.}" -type f -name '*.rar' -execdir sh -c 'rm *.mkv' \;
Two remarks, though:
-execdir rm *.mkv \; would be shorter, but then the glob might be expanded prematurely in case there are .mkv files in the current directory
if a directory contains a .rar file, but no .mkv, this will try to delete a file called literally *.mkv and cause an error message

Rename files in bash based on content inside

I have a directory which has 70000 xml files in it. Each file has a tag which looks something like this, for the sake of simplicity:
<ns2:apple>, <ns2:orange>, <ns2:grapes>, <ns2:melon>. Each file has only one fruit tag, i.e. there cannot be both apple and orange in the same file.
I would like rename every file (add "1_" before the beginning of each filename) which has one of: <ns2:apple>, <ns2:orange>, <ns2:melon> inside of it.
I can find such files with egrep:
egrep -r '<ns2:apple>|<ns2:orange>|<ns2:melon>'
So how would it look as a bash script, which I can then user as a cron job?
P.S. Sorry I don't have any bash script draft, I have very little experience with it and the time is of the essence right now.
This may be done with this script:
#!/bin/sh
find /path/to/directory/with/xml -type f | while read f; do
grep -q -E '<ns2:apple>|<ns2:orange>|<ns2:melon>' "$f" && mv "$f" "1_${f}"
done
But it will rescan the directory each time it runs and append 1_ to each file containing one of your tags. This means a lot of excess IO and files with certain tags will be getting 1_ prefix each run, resulting in names like 1_1_1_1_file.xml.
Probably you should think more on design, e.g. move processed files to two directories based on whether file has certain tags or not:
#!/bin/sh
# create output dirs
mkdir -p /path/to/directory/with/xml/with_tags/ /path/to/directory/with/xml/without_tags/
find /path/to/directory/with/xml -maxdepth 1 -mindepth 1 -type f | while read f; do
if grep -q -E '<ns2:apple>|<ns2:orange>|<ns2:melon>'; then
mv "$f" /path/to/directory/with/xml/with_tags/
else
mv "$f" /path/to/directory/with/xml/without_tags/
fi
done
Run this command as a dry run, then remove --dry_run to actually rename the files:
grep -Pl '(<ns2:apple>|<ns2:orange>|<ns2:melon>)' *.xml | xargs rename --dry-run 's/^/1_/'
The command-line utility rename comes in many flavors. Most of them should work for this task. I used the rename version 1.601 by Aristotle Pagaltzis. To install rename, simply download its Perl script and place into $PATH. Or install rename using conda, like so:
conda install rename
Here, grep uses the following options:
-P : Use Perl regexes.
-l : Suppress normal output; instead print the name of each input file from which output would normally have been printed.
SEE ALSO:
grep manual

Is my bash script accurate enough to check if the list of images are being referred anywhere in directory?

I have a list of images which I wanted to delete if they are not being referred anywhere. My directory consists of multiple directories and within them, there are .js files. I need to search each image name in the above files. If they are referred anywhere, I need to output them so I will retain those images.
My script goes like this: I am trying to check each image in the following .js or .json files in the entire directory ( includes multiple directories inside) and output them to c.out if any of these files contain the above image name. Am I doing it right? I still could see some images are not coming in output even if they are being used.
#!/bin/bash
filename='images.txt'
echo Start
while read p; do
echo $p
find -name "*.js" | xargs grep -i $p > c.out
done < $filename
images.txt contains:
a.png
b.png
c.jpeg
....
Step 1: Keep a text file with list of images ( one name per line ), use dos2unix file_name if the file is generated/ created on Windows machine
Step 2: Run find /path/to/proj/dir -name '*.js' -o -name '*.json' | xargs grep -Ff pic_list.txt
You get the list of paths where those images are being referred.
Thanks #shelter for the answer

usage error when moving files of one extension to a directory

I have files with extension .mp3 that are in different folders within a directory, and I need to move them all to one directory to work with. I have looked at multiple tutorials and questions on SO, and no matter what I try, I either get
usage: mv [-f | -i | -n] [-v] source target
mv [-f | -i | -n] [-v] source ... directory
or "No such file or directory".
There are way too many to into each individual folder, but for now I cd into one of the folders. With this:
mv *.mp3 /Users/myname/Volumes/LaCie/model/folder/media
I get the "usage" error above. I looked at Moving files to a directory and tried:
find . | grep ".mp3" | xargs mv /Users/myname/Volumes/LaCie/model/folder/media
and get same error.
What am I doing wrong? What is the correct syntax? Also, will I be able to extract the mp3 files and move them if I'm in a directory that contains the directory with the files, but not in that directory itself? I appreciate insights into this. Thanks.
EDIT: A major part of this issue was that the path when using /Volumes starts with /Volumes. I was doing /Users/myname/Volumes and that was one reason I had so much trouble.
The problem with the way you're using xargs is that by default, xargs will append the arguments to the end of the command string you've provided it. So you'll end up running a bunch of mv commands that look like this:
mv /Users/myname/Volumes/LaCie/model/folder/media foo.mp3
You can fix that by telling xargs where to place the arguments within the command:
<other commands> | xargs -I{} mv {} /Users/myname/Volumes/LaCie/model/folder/media
The -I option lets you provide any arbitrary string as a placeholder for where the args should go. I used {} just because that seems to be the conventional token that you see used in similar contexts (such as with the -exec option of find, as shown below).
But there's an easier way to do it, using the find command's -exec option:
find . -name '*.mp3' -exec mv {} /Users/myname/Volumes/LaCie/model/folder/media \;
Also note the -name '*.mp3' part, which lets you get rid of the | grep ".mp3" part.
Lastly, just to be safe, I'd personally put a / at the end of your destination path. If the media directory doesn't exist in /Users/myname/Volumes/LaCie/model/folder, or if a non-directory item (such as a regular file or a symlink) named media exists in that location, then the find command above will happily just move all your mp3 files, one at a time, to that folder, creating a file named media there each time. And you will have lost all of your mp3 files except for the last one, which will now be a file named media.
However, with a trailing /, if media is not a directory, the mv commands will fail with an error saying so. So the revised command would be:
find . -name '*.mp3' -exec mv {} /Users/myname/Volumes/LaCie/model/folder/media/ \;
Update: Per Gordon Davisson's comment below, you should also consider adding -i or -n to the mv command, to avoid accidentally overwriting files with duplicate names. For example, if you have a/foo.mp3 and b/foo.mp3, the above command will overwrite one with the other. The -i option will cause mv to prompt you to confirm each file move, whereas the -n option (a.k.a. --no-clobber) will prevent mv from overwriting a file if a file with the same name already exists.
There are different implementations and versions of mv. You can check the allowed syntax of your version using man mv.
If you have GNU mv you could use mv -t target/dir *.mp3.
Most implementations should support mv *.mp3 target/dir.
If your mv only supports the absolute minimum of mv source target with exactly one source and one target file you can use the following command which should always work if target/dir/ exists.
for i in *.mp3; do mv "$i" "target/dir/$i"; done

Batch convert PNGs to individual PDFs while maintaining deep folder hierarchy in bash

I've found a solution that claims to do one folder, but I have a deep folder hierarchy of sheet music that I'd like to batch convert from png to pdf. What do my solutions look like?
I will run into a further problem down the line, which may complicate things. Maybe I should write a script? (I'm a total n00b fyi)
The "further problem" is that some of my sheet music spans more than one page, so if the script can parse filenames that include "1of2" and "2of2" to be turned into a single pdf, that'd be neat.
What are my options here?
Thank you so much.
Updated Answer
As an alternative, the following should be faster (as it does the conversions in parallel) and also able to handle larger numbers of files:
find . -name \*.png -print0 | parallel -0 convert {} {.}.pdf
It uses GNU Parallel which is readily available on Linux/Unix and which can be simply installed on OSX with homebrew using:
brew install parallel
Original Answer (as accepted)
If you have bash version 4 or better, you can use extended globbing to recurse directories and do your job very simply:
First enable extended globbing with:
shopt -s globstar
Then recursively convert PNGs to PDFs:
mogrify -format pdf **/*.png
You can loop over png files in a folder hierarchy, and process each one as follows:
find /path/to/your/files -name '*.png' |
while read -r f; do
g=$(basename "$f" .png).pdf
your_conversion_program <"$f" >"$g"
done
To merge pdf-s, you could use pdftk. You need to find all pdf files that have a 1of2 and 2of2 in their name, and run pdftk on those:
find /path/to/your/files -name '*1of2*.pdf' |
while read -r f1; do
f2=${f1/1of2/2of2} # name of second file
([ -f "$f1" ] && [ -f "$f2" ]) || continue # check both exist
g=${f1/1of2//} # name of output file
(! [ -f "$g" ]) || continue # if output exists, skip
pdftk "$f1" "$f2" output "$g"
done
See:
bash string substitution
Regarding a deep folder hierarchy you may use find with -exec option.
First you find all the PNGs in every subfolder and convert them to PDF:
find ./ -name \*\.png -exec convert {} {}.pdf \;
You'll get new PDF files with extension ".png.pdf" (image.png would be converted to image.png.pdf for example)
To correct extensions you may run find command again but this time with "rename" after -exec option.
find ./ -name \*\.png\.pdf -exec rename s/\.png\.pdf/\.pdf/ {} \;
If you want to delete source PNG files, you may use this command, which deletes all files with ".png" extension recursively in every subfolder:
find ./ -name \*\.png -exec rm {} \;
if i understand :
you want to concatenate all your png files from a deep folders structure into only one single pdf.
so...
insure you png are ordered as you want in your folders
be aware you can redirect output of a command (say a search one ;) ) to the input of convert, and tell convert to output in one pdf.
General syntax of convert :
convert 1.png 2.png ... global_png.pdf
The following command :
convert `find . -name '*'.png -print` global_png.pdf
searches for png files in folders from cur_dir
redirects the output of the command find to the input of convert, this is done by back quoting find command
converts works and output to pdf file
(this very simple command line works fine only with unspaced filenames, don't miss quoting the wild char, and back quoting the find command ;) )
[edit]Care....
be sure of what you are doing.
if you delete your png files, you will just loose your original sources...
it might be a very bad practice...
using convert without any tricky -quality output option could create an enormous pdf file... and you might have to re-convert with -quality "60" for instance...
so keep your original sources until you do not need them any more

Resources