Running shell script loop in parallel - linux

I wrote an shell script which
get list of all image files from directory
create new folder if needed for new image
optimize image in order to save storage resources
I've tried to use parallel -j "$(nproc)" before mogrify but found that it was wrong, because before mogrify is used DIR and mkdir, i need instead something like & at end of mogrify but to do it only for n processes.
the current code look like:
#!/bin/bash
find $1 -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" -type f | while read IMAGE
do
DIR="$2"/`dirname $IMAGE`
echo "$IMAGE > $DIR"
mkdir -p $DIR
mogrify -path "$DIR" -resize "6000000#>" -filter Triangle -define filter:support=2 -unsharp 0.25x0.08+8.3+0.045 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB "$IMAGE"
done
exit 0
Can someone suggest what will be the right way to run such script in parallel? as each run take about 15 seconds.

When you have a shell loop that does some setup and invokes an expensive command, the way to parallelize it is to use sem from GNU parallel:
for i in {1..10}
do
echo "Doing some stuff"
sem -j +0 sleep 2
done
sem --wait
This allows the loop to run and do its thing as normal, while also scheduling the commands to run in parallel (-j +0 runs one job per CPU core).

Make a bash function that deals correctly with one file and call that in parallel:
#!/bin/bash
doit() {
IMAGE="$1"
DIR="$2"/`dirname $IMAGE`
echo "$IMAGE > $DIR"
mkdir -p $DIR
mogrify -path "$DIR" -resize "6000000#>" -filter Triangle -define filter:support=2 -unsharp 0.25x0.08+8.3+0.045 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB "$IMAGE"
}
export -f doit
find $1 -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" -type f |
parallel doit
Default for GNU Parallel is to run one job per CPU-thread, so Çıproc is not needed.
This has less overhead than starting sem for each file (sem = 0.2 sec per call, parallel = 7 ms per call).

Related

How to combine Linux find, convert and copy commands into one command

I have the following cmd that fetches all .pdf files with an STP pattern in the filename and places them into a folder:
find /home/OurFiles/Images/ -name '*.pdf' |grep "STP*" | xargs cp -t /home/OurFiles/ImageConvert/STP/
I have another cmd that converts pdf to jpg.
find /home/OurFiles/ImageConvert/STP/ -type f -name '*.pdf' -print0 |
while IFS= read -r -d '' file
do convert -verbose -density 500 -resize 800 "${file}" "${file%.*}.jpg"
done
Is it possible to combine these commands into one? Also, I would like pre-pend a prefix onto the converted image file name in the single command, if possible. Example: STP_OCTOBER.jpg to MSP-STP_OCTOBER.jpg. Any feedback is much appreciated.
find /home/OurFiles/Images/ -type f -name '*STP*.pdf' -exec sh -c '
destination=$1; shift # get the first argument
for file do # loop over the remaining arguments
fname=${file##*/} # get the filename part
cp "$file" "$destination" &&
convert -verbose -density 500 -resize 800 "$destination/$fname" "$destination/MSP-${fname%pdf}jpg"
done
' sh /home/OurFiles/ImageConvert/STP {} +
You could pass the destination directory and all PDFs found to find's -exec option to execute a small script.
The script removes the first argument and saves it to variable destination and then loops over the given PDF paths. For each filepath, extract the filename, copy the file to the destination directory and run the convert command if the copy operation was successful.
Maybe something like:
find /home/OurFiles/Images -type f -name 'STP*.pdf' -print0 |
while IFS= read -r -d '' file; do
destfile="/home/OurFiles/ImageConvert/STP/MSP-$(basename "$file" .pdf).jpg"
convert -verbose -density 500 -resize 800 "$file" "$destfile"
done
The only really new thing in this merged one compared to your two separate commands is using basename(1) to strip off the directories and extension from the filename in order to create the output filename.

Correct syntax for shell script

Am trying to create one and add the same to the cron.
This is the commands I am trying to run through the script.
#!/bin/bash
find . -mmin -60 -name "*.jpg" $(printf "! -name %s " $(cat processed.txt) ! -name cache) -exec convert -resize 1000x800 -quality 85% {} {};
find -mmin -60 -type f -name "*.jpg" -exec basename {} \; &> processed.txt
f I am running these commands directly on shell, I don't get any error.
but if say I have stored this in a file called compress and run the script as ./compress
I get the error -
find: missing argument to `-exec'
what mistake I am making and how I can fix that.
Build an array of arguments for the first find command instead of relying on the command substitution.
while IFS= read -r line; do
processed+=(! -name "$line")
done < processed.txt
Your immediate problem, though, is that you forgot to escape the semicolon so that it would be treated as an argument to find, rather than a command terminator.
find . -mmin -60 -name "*.jpg" "${processed[#]}" \
! -name cache -exec convert -resize 1000x800 -quality 85% {} {} \;
# ^^
find -mmin -60 -type f -name "*.jpg" -exec basename {} \; &> processed.txt

Ignore case when matching file name extensions with find [duplicate]

This question already has answers here:
Ignore case when trying to match file names using find command in Linux
(5 answers)
Closed 5 years ago.
I wrote a bash script a while back that resizes and compresses all images in directory with Image Magick.
# Usage: smartResize "2400x2400>"
function smartResize() {
find ./ -name "*.jpg" -exec magick mogrify -resize $1 -sampling-factor 4:2:0 -strip -interlace JPEG -quality 85 -colorspace RGB {} \;
find ./ -name "*.JPG" -exec magick mogrify -resize $1 -sampling-factor 4:2:0 -strip -interlace JPEG -quality 85 -colorspace RGB {} \;
find ./ -name "*.jpeg" -exec magick mogrify -resize $1 -sampling-factor 4:2:0 -strip -interlace JPEG -quality 85 -colorspace RGB {} \;
find ./ -name "*.JPEG" -exec magick mogrify -resize $1 -sampling-factor 4:2:0 -strip -interlace JPEG -quality 85 -colorspace RGB {} \;
}
I had to add extra lines to find some alternative cases for .jpg extensions. Which I don't particularly like but got the job done.
Does anybody have a better idea of how to handle case sensitivity and the optional e in jpeg extensions?
n.b. I'm running this in Cmder: Git Bash on Windows.
GNU versions of find have an -iname flag which enables case insensitive matching of file name globs,
find ./ -iname '*.jpg'
or if you are on a system without GNU utilities, use the bracket expressions to the glob
find ./ -name '*.[Jj][Pp][Gg]'
If you are interested in multiple name filters, just use the -o expression for including multiple name globs
find ./ \( -iname "*.jpg" -o -iname "*.jpeg" \)

How to mogrify recursive folders compacting the files

I need to excute in ubuntu something like this:
find ./ -name '*.jpg' -execdir "mogrify -quality 50 *.jpg" {} \;
To compact all the *.jpg to 50% of your quality but this need to be recursive, because i have 1350 files in a long tree of folders...
And this return ever something like:
mogrify: unable to open image `Banner-Caixa.jpg': permission denied # error/blob.c/OpenBlob/2712.
I discovered!
sudo find . -name '*.jpg' -execdir sh -c "mogrify -quality 50 *.jpg" {} \;
This works fine!
widoth / on ./ and add sh -c after -execdir

How to convert images low width 200

I have the code below for image conversion.
I have a directory with many images, I would like to convert all images that the width was less than 200 pixel.
Regardless of the extension, jpg, gif or png
find . -iname \*.jpg -exec convert -verbose -resize 200x140! "{}" "{}" \;
I think you want this - or something very close to it - so make a backup first!
find . \( -iname \*.jpg -o -iname \*.png -o -iname \*.gif \) \
-exec bash -c '[ $(identify -format %w "$0" ) -lt 200 ] && convert "$0" -resize 200x140\! "$0"' {} \;
That says... "find, starting in the current directory (.), any files whose names end, in a case-insensitive fashion (-iname), in JPG, PNG or GIF and start a new bash shell for each one. Once inside the shell, get the width of the file and if it is less than 200 pixels, execute the convert command to resize the file to 200x140, ignoring aspect ratio."
The "first" part in there is: acquiring the width of all images in that folder. And if I read your question correctly, that is where you have problems with; thus you can look into the identify command coming with ImageMagick. It works like this
identify -format "%wx%h" pic.jpg
See here for handling formatting. As soon as you got your list of "width matching" files, you should be able to further convert them.

Resources