Generating the output file name from the input file in bash - bash

I have a bash script:
#!/bin/bash
convert "$1" -resize 50% "$2"
Instead of passing two arguments while the script is run I want to mention just the source (or input file name) and the output file name should be auto-genarated from the source file name. Something like this "$1" | cut -d'.' -f1".jpg". If the input file name was myimage.png, the output name should be myimage.jpg. .jpg should be appended to the fist part of the source file name. It should also work if the argument is: *.png. So how can I modify my script?

The expansion ${X%pattern} removes pattern of the end of $X.
convert "$1" -resize 50% "${1%.*}.jpg"
To work on multiple files:
for filename ; do
convert "$filename" -resize 50% "${filename%.*}.jpg"
done
This will iterate over each of the command line arguments and is shorthand for for filename in "$#". You do not need to worry about checking whether the argument is *.png - the shell will expand that for you - you will simple receive the expanded list of filenames.

convert "$1" -resize 50% "${1%.*}.jpg"
The magic is in the %.* part, which removes everything after the last dot. If your file is missing an extension, it will still work (as long as you don't have a dot anywhere else in the path).

OUTFILE=`echo $1|sed 's/\(.*\)\..*/\1/'`.jpg
convert "$1" -resize 50% "$OUTFILE"

Related

Processing images using ImageMagick through bash scripts

I have a folder of images. I want to iterate through the folder, apply the same ImageMagick convert function to each file, and save the output to a separate folder.
The way I'm doing it currently is this:
#!/bin/bash
mkdir "Folder2"
for f in Folder1/*.png
do
echo "convert -brightness-contrast 10x60" "Folder1/$f" "Folder2/"${f%.*}"_suffix.png"
done
Then I copy and paste that terminal output into a new bash script that ends up looking like this:
#!/bin/bash
convert -brightness-contrast 10x60 Folder1/file1.png Folder2/file1_suffix.png
convert -brightness-contrast 10x60 Folder1/file2.png Folder2/file2_suffix.png
convert -brightness-contrast 10x60 Folder1/file3.png Folder2/file3_suffix.png
I tried to write a single bash script for this task but there was some weirdness with the variable handling, and this two-script method got me what I needed ...but I suspect there's an easier/simpler way and possibly even a one-line solution.
It's enough to change your first script, not to just echo the commands, but to execute them.
#!/bin/bash
mkdir "Folder2"
for f in Folder1/*.png
do
convert -brightness-contrast 10x60 "Folder1/$f" "Folder2/${f%.*}_suffix.png"
done
Crystal ball is telling me that there are spaces in filenames causing "some weirdness with the variable handling". In that case you need a workaround for the spaces. For example, you may try the following script:
#!/bin/bash
hasspaces="^(.+[^\'\"])([ ])(.+)$"
function escapespaces {
declare -n name=$1
while [[ $name =~ $hasspaces ]] ; do
name=${BASH_REMATCH[1]}'\'${BASH_REMATCH[2]}${BASH_REMATCH[3]}
echo 'Escaped string: '\'$name\'
done
}
mkdir Folder2
while read -r entry; do
echo "File '$entry'"
escapespaces entry
echo "File '$entry'"
tmp=${entry#Folder1}
eval "convert -brightness-contrast 10x60" "$entry" "Folder2/"${tmp%.*}"_suffix.png"
done <<<"$(eval "ls -1 Folder1/*.png")"
If this does not work, by all means let me know so I can request a refund for my crystal ball! Also, if you can give more details on the "weirdness in variable handling", we could try to help with those other weirdnesses :-)
Check this answer (and the few in the question) out:
You can use it in your example :
find Folder1 -name "*.png" | sed -e 'p;s/Folder1/Folder2/g' -e 's/.png/_suffix.png' | xargs -n2 convert -brightness-contrast 10x60
note : the p in the first sed makes the trick.
find will list you all the files in Folder1 with a name that matches the *.png expression
sed -e 'p;/Folder1/Folder2/g' will (a) print the input line and (b) replace Folder1 by Folder2
-e 's/.png$/_suffix.png' replaces the .png suffix with the _suffix.png suffix
xargs -n2 tells the shell that xargs should take two arguments max (the first one being printed by sed 'p' and the second one going through all the -e)
convert ... is your command, taking two inputs.

How can I collect and format filename, size and extension of directory contents in bash?

I am looking to output a list of contents formatted by size, type of each file and the file name without the extension.
I've tried using ls grep and awk but to no avail. I'm not sure how else this could be done, could someone help me?
I'm looking for the output to show as below;
Filename FileSize Extension
Filename FileSize Extension
Filename FileSize Extension
Filename FileSize Extension
and so on.
There's no need for ls here (and, in fact, good reason not to use it; see Why you shouldn't parse the output of ls).
As native bash:
#!/usr/bin/env bash
for file in *; do # iterate over contents of current directory
[[ -f "$file" ]] || continue # skip things that aren't files
size=$(wc -c <"$file") # collect size
# ...and actually format and print metadata
printf '%20q %20s %q\n' "${file%.*}" "$size" "${file##*.}"
done
Here, %q is used to ensure that even names with nonprintable components are formatted unambiguously (so you can tell the difference between a name with spaces and a name with tabs, for example).

TCropping images with loop in imagemagick

A pretty basic question but I'm new to
Imagemagick (and bash) and I'm having trouble batch cropping images in a folder. I've tried using a loop:
for image in '/home/donald/Desktop/New Folder'*.jpg; do
convert "$image" -gravity center -crop 95X95% "${image%.jpg}"-modified.jpg
done
but it returns:
convert.im6: unable to open image `/home/donald/Desktop/New Folder/*.jpg': No such file or directory # error/blob.c/OpenBlob/2638.
convert.im6: no images defined `/home/donald/Desktop/New Folder/*-modified.jpg' # error/convert.c/ConvertImageCommand/3044."
What would be the proper way of doing this?
Edit: Apparently a space in the folder name was causing problems I deleted it and things seem to be working.Apparently if you want to use a folder with a space name in bash you need to escape the space.
I believe you have no jpg files in the /home/donald/Desktop/New Folder/ directory. The shell will interpret it as the literal string /home/donald/Desktop/New Folder/*.jpg if there are no files matching the wildcard-ed string.
See this example:
$ for f in *.jpg*; do echo $f; done
file.jpg
file2.jpg
$ for f in *.jpgg; do echo $f; done
*.jpgg
See how that last one is the literal string and not a real file? It should have been displayed the first time too if it was (notice the trailing asterix symbol in *.jpg*).
You can fix this by checking if the file exists before executing the command, using [ -f "${file}" ]. For instance:
for image in '/home/donald/Desktop/New Folder'*.jpg; do
[ -f "${image}" ] && convert "$image" -gravity center -crop 95X95% "${image%.jpg}"-modified.jpg
done
This will check if the file image exists (-f) and execute the following statement only if true is returned &&. Had you written || instead of && then the following statement would be executed when false was returned.
Note that bash doesn't return true or false but it's the easiest way to explain and comprehend the notation.

File name manipulation with shell scripts: changing file extensions

I need to write a shell script to convert the image format from .png to .tif. The script is as follows:
#!/bin/sh
for f in `ls *.png`
do
convert $f $f.tif
done
But doing this will append the .tif format to the existing filename. ie if the image is abc.png the $f will have abc.png and after converting the filename becomes abc.png.tif. This is not what I want. I need it to be abc.tif. How do I manipulate $f to remove .png?
This should work for you:
#!/bin/bash
for file in *.png
do
filename=$(basename "$file")
filename=${filename%.*}
convert $file $filename.tif
done
A line-by-line walkthrough of how this works:
for file in *.png - you don't need command substitution ls *.png to get the list of files with png extension. The wildcard * will auto-expand in shell to match the list of files in cwd; and in this case the list of files ending in .png.
filename=$(basename "$file") - this is only for defensive programming; it gets the actual name of the file
filename=${filename%.*} - this removes the extension from filename
convert $file $filename.tif - runs your actual convert command

resizing images with imagemagick via shell script

I don't really know that much about bash scripts OR imagemagick, but I am attempting to create a script in which you can give some sort of regexp matching pattern for a list of images and then process those into new files that have a given filename prefix.
for example given the following dir listing:
allfiles01.jpg allfiles02.jpg
allfiles03.jpg
i would like to call the script like so:
./resisemany.sh allfiles*.jpg 30 newnames*.jpg
the end result of this would be that you get a bunch of new files with newnames, the numbers match up,
so far what i have is:
IMAGELIST=$1
RESIEZFACTOR=$2
NUMIMGS=length($IMAGELIST)
for(i=0; i<NUMIMGS; i++)
convert $IMAGELIST[i] -filter bessel -resize . RESIZEFACTOR . % myfile.JPG
Thanks for any help...
The parts that I obviously need help with are
1. how to give a bash script matching criteria that it understands
2. how to use the $2 without having it match the 2nd item in the image list
3. how to get the length of the image list
4. how to create a proper for loop in such a case
5. how to do proper text replacement for a shell command whereby you are appending items as i allude to.
jml
Probably the way a standard program would work would be to take an "in" filename pattern and an "out" filename pattern and perform the operation on each file in the current directory that matches the "in" pattern, substituting appropriate parts into the "out" pattern. This is pretty easy if you have a hard-coded pattern, when you can write one-off commands like
for infile in *.jpg; do convert $infile -filter bessel -resize 30% ${infile//allfiles/newnames}; done
In order to make a script that will do this with any pattern, though, you need something more complicated because your filename transformation might be something more complicated than just replacing one part with another. Unfortunately Bash doesn't really give you a way to identify what part of the filename matched a specific part of the pattern, so you'd have to use a more capable regular expression engine, like sed for example:
#!/bin/bash
inpattern=$1
factor=$2
outpattern=$3
for infile in *; do
outfile=$(echo $infile | sed -n "s/$inpattern/$outpattern/p")
test -z $outfile && continue
convert $infile -filter bessel -resize $factor% $outfile
done
That could be invoked as
./resizemany.sh 'allfiles\(.*\).jpg' 30 'newnames\1.jpg'
(note the single quotes!) and it would resize allfiles1.jpg to newnames1.jpg, etc. But then you'd wind up basically having to learn sed's regular expression syntax to specify your in and out patterns. (It's not that bad, really)
You could eliminate the regex problem if you make a folder of all the files to be processed, and then run something like:
for img in `ls *.jpg`
do
convert $img -filter bessel -resize 30% processed-$img
done
Then, if you need to rename them all later, you could do something like:
ls | nl -nrz -w2 | while read a b; do mv "$b" newfilename.$a.jpg; done;
Also, If you are doing a batch process of the same operation, you might see if using mogrify might help (imagemagik's method for converting multiple files). Like the above example, it's always good to make a copy of the folder, and then run any processing so you don't destroy your original files.
Your script should be called using a syntax such as:
./resizemany.sh -r 30 -n newnames -o allfiles allfiles*.jpg
and use getopts to process the options. What you may not be aware of is that the shell expands the file glob before the script gets it so the way you had your arguments your script would never be able to distinguish the filenames from the other parameters.
Output files will be named using the rename script often found on systems with Perl installed. A file named "allfiles03.jpg" will be output as "newname03.jpg".
#!/bin/bash
options=":r:n:o:"
while getopts $options option
do
case $option in
n)
newnamepattern=$OPTARG
;;
o)
oldnamepattern=$OPTARG
;;
r)
resizefacor=$OPTARG
;;
\?)
echo "Invalid option"
exit 1
esac
done
# a check to see if any options are missing should be performed (not implemented)
shift $((OPTIND - 1))
# now all that's left will be treated as filenames
for file
do
convert (input options) "$file" -resize $resizefactor (output options) "${file}.out"
rename "s/$old/$new/;s/\.out$//" "${file}.out"
done
This is untested (obviously since most of the arguments to convert are missing).
Parameter validation such as range checks, missing required options and others are left as exercises for further development. Also absent are checks for successful completion of one step before continuing to the next one. Also issues such as locations of files and name collisions and others are not addressed.

Resources