views:

494

answers:

4

I'm writing a script to batch resize images. Originally I was applying an operation for file in $(ls $1), but I would like to be able to use globbing, so I'm looking at something more like for file in $(echo $1). The problem is that dotglob may or may not be enabled, so echo * could return hidden files (notably, .DS_Store), which cause convert to throw an error and stop the script. I would like the default behavior of the command to be that if I cd into a directory full of images and execute resize * 400x400 jpg, all of the images will be resized excluding hidden files, regardless of whether dotglob is enabled.

So, in pseudo code, I'm looking for:

for file in $(echo $1 | [filter-hidden-files])

Here is my script with the older behavior. Will update with new behavior when I find a solution:

# !/bin/bash

# resize [folder] [sizeXxsizeY] [outputformat]
# if [outputformat] is omitted, the input file format is assumed

for file in $(ls $1)
do
 IMGNAME=$(echo "$file" | cut -d'.' -f1)
 if test -z $3
 then
  EXTENSION=$(echo "$file" | cut -d'.' -f2)
  convert $1/$file -resize $2 -quality 100 $1/$IMGNAME-$2.$EXTENSION
  echo "$file => $IMGNAME-$2.$EXTENSION"
 else 
  convert $1/$file -resize $2 -quality 100 $1/$IMGNAME-$2.$3
  echo "$file => $IMGNAME-$2.$3"
 fi
done

Here is the current script:

# !/bin/bash

# resize [pattern] [sizeXxsizeY] [outputformat]
# if [outputformat] is omitted, the input file format is assumed

for file in $(echo $1)
do
  IMGNAME=$(echo "$file" | cut -d'.' -f1)
  if test -z $3 && if test -f $3
  then
   EXTENSION=$(echo "$file" | cut -d'.' -f2)
   convert $file -resize $2 -quality 100 $IMGNAME-$2.$EXTENSION
   echo "$file => $IMGNAME-$2.$EXTENSION"
  else 
   convert $file -resize $2 -quality 100 $IMGNAME-$2.$3
   echo "$file => $IMGNAME-$2.$3"
  fi
done

Given the command resize * 400x400, convert throws an error as it cannot process .DS_Store (a hidden file residing in every file on an OSX system). As I will never be processing hidden images, I would like to automatically filter them. I've been trying to do this with grep or find, but I haven't figured it out yet.

New script goes here:

for file in $(echo $1)
do
+1  A: 

There is a dotglob shell option that decides if files starting with . are included when globbing. You can check if this is the case with

shopt dotglob

You also can explicitly disable it in your script:

#!/bin/bash

shopt -u dotglob

for file in $1/*; do
  ...
done
sth
For some reason trying to set the dotglob parameter in the script doesn't work. If I run shopt -u dotglob in the shell, then echo * has the desired parameter, but If I put the line in the script and execute it, it doesn't not change the parameter.
msutherl
+1  A: 

as you are getting a list of files in $* you can check them one by one

for i in $*
do 
   expr $i : '^\..*' > /dev/null && continue
   # process file
done
catwalk
Bear in mind that I don't want the script to automatically act on all files in the current directory. I want to be able to use all globbers, so the test has to be of the form "echo $1 | [filter]", unless echo can do the filter or I don't need echo at all. It needs to work with positional parameters.
msutherl
sorry, did not get it right, will update the solution
catwalk
$* returns all positional parameters, including my image dimension and output format paramters.
msutherl
if you change your invocation syntax to 'resize 400x400 *' you can shift params out of $*. (which is also similar to the way other utitilies handle arguments, i.e. grep [SWITCHES] file1 file2 ...
catwalk
+1  A: 

there's no need to use ls with a for loop, most of the time its useless. also the for loop with * doesn't return hidden files, unless you specifically specify it. To show hidden files,

for files in .*
do
 echo $files
done
ghostdog74
Remember, I'm looking to use positional parameter $1 as an argument so that I can use any globber.
msutherl
Also, with shopt dotglob set to "yes", * will return hidden files. Unfortunately I seem to be unable to change this parameter with script.
msutherl
+2  A: 

I would suggest changing the commandline of your script from resize * 400x400 to resize 400x400 *, the script would be more like the standard unix tools that way (grep, sed, ln, etc.). You would not have to unset the dotglob option in your script (which is better since it's up to the user of the script if he wants hidden files globbed or not).

Your script would look something like this:

#!/bin/bash

OUTPUTFORMAT=$1
# Remove original $1 from the list of arguments
shift

for i in "$@"
do
    # Use $OUTPUTFORMAT here
    etc....

If you do not want to change the commandline for your script. You could try setting GLOBIGNORE

export GLOBIGNORE=".*"

Or if extglob is set you could try file globbing like so:

echo !(.*)
Peter van der Heijden
I wanted the syntax to be consistent with the convert command, which takes commands like so: convert [input file] [options] [output file], but I will take your advice for now.
msutherl
Also, if I do this, I'm not sure how I can make it so that the output format can be omitted.
msutherl
I assume 400x400 is the ouput format. I have edited my script to make it more clear how to skip that.
Peter van der Heijden
Thanks for your help Peter. 400x400 is the output dimensions. The output format is a second parameter. See usage example: resize * 400x400 jpg. The user can choose whether to specify the output format or to keep the format of the input file. So, I need to shift either once or twice depending on whether the output format is specified.
msutherl
Perhaps ideally the parameters would be flags (prefixed with a -). Can you point to a tutorial on how to do that?
msutherl
Hmm, since your output format is optional there seems to be no way to solve this using positional arguments. Flags should work though. The standard way to do this is using getopts. See man getopts.
Peter van der Heijden
Will have a look at getopts. Thanks again Peter.
msutherl