LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Newbie (http://www.linuxquestions.org/questions/linux-newbie-8/)
-   -   Unexpected output getopts in AIX (http://www.linuxquestions.org/questions/linux-newbie-8/unexpected-output-getopts-in-aix-937869/)

Mark1986 04-03-2012 06:51 AM

Unexpected output getopts in AIX
 
Hi all,

Currently I am using AIX at work. I am no administrator of the system, merely a user.

I am trying to create a script that will accept either a file with a list of files as content, or a list of files. At first I build it for myself, but I want it to be available for my colleagues in the longer run.

The script is called mailen (Dutch for mailing). Either you start the script as: ./mailen file1 file2 file3 or as: ./mailen -f file_list file1 file2

In the first case, it should e-mail the files (file1, file2 and file3) to me as an attachment. In the second case, it should read the content of the file file_list and e-mail me the files in the content from that list and file1 and file2.

So I started with a script that would e-mail me the files I put on the command line. The script works as designed.

The next step is to try and see if I can make a script that will get the content of the file in case I use the -f (file) option.

The script I have should contain 2 options, namely -v (verbose) and -f (file to read content from).

This is what I have so far:

Code:

#!/bin/bash
#

V_FLAG=
F_FLAG=
FILE=

while getopts vf: opt
do
  case $opt in
  v)
    V_FLAG=1
  ;;
  f)
    F_FLAG=1
    FILE="$OPTARG"
  ;;
  \?)
    echo ""
    echo "Usage of mailen:"
    echo "$0 [-v] [-f filename] [file ...]"
    echo ""
    exit 1
  ;;
  esac
done

shift $((OPTIND - 1))

echo "Rest $@"
echo ""
echo "V_FLAG=${V_FLAG}"
echo "F_FLAG=${F_FLAG}"
echo "FILE=${FILE}"

exit 0

Now running it:

$ ./mailen_getopts.sh -f foo foo1 foo2
Rest foo1 foo2

V_FLAG=
F_FLAG=1
FILE=foo

$ ./mailen_getopts.sh -f foo -v foo1 foo2
Rest foo1 foo2

V_FLAG=1
F_FLAG=1
FILE=foo

$ ./mailen_getopts.sh -v foo -f foo1 foo2
Rest foo -f foo1 foo2

V_FLAG=1
F_FLAG=
FILE=

As you can see, in the first 2 cases output is as expected. However in the last case, -f seems not to be an option anymore... For some reason if you tell getopts that an option should not have an argument, it will not parse the rest of the string if you don't provide a new option right away.

Has anyone encountered this before? Does anyone know how to overcome this without loosing too much flexibility?

catkin 04-03-2012 08:06 AM

That's normal behaviour.

When getopts finds a non-option after -v it treats the rest of the command line as arguments.

On GNU/Linux systems the getopt command is available and (I think) behaves as you want. Don't know about AIX.

Mark1986 04-03-2012 08:14 AM

Isn't there something to tell getopts that an argument is prohibited for a certain option? Like you can use : to say an argument is mandatory.

I tried some other ways to go around this:

1. Tell getopts that v also needs an argument: v:f:
Outcome: if you don't provide an argument it takes -f as the argument...
2. Try v;f:
Outcome: seems not to work as a way to tell getopts that an argument is prohibited for the option v.

catkin 04-03-2012 08:58 AM

The absence of a : after v tells getopts that the v takes no argument.

You can get the behaviour you want with
Code:

#!/bin/bash
#

V_FLAG=
F_FLAG=
FILE=
files=( )
option_regex=^-

while getopts vf: opt
do
  case $opt in
  v)
    V_FLAG=1
    if [[ ! $2 =~ $option_regex ]]; then
        files+=( "$2" )
        shift
    fi
  ;;
  f)
    F_FLAG=1
    FILE="$OPTARG"
  ;;
  \?)
    echo ""
    echo "Usage of mailen:"
    echo "$0 [-v] [-f filename] [file ...]"
    echo ""
    exit 1
  ;;
  esac
done

shift $((OPTIND - 1))
while [[ $# -gt 0 ]]
do
    files+=( "$1" )
    shift
done
echo "DEBUG: files (array): ${files[*]}"

echo "Rest $@"
echo ""
echo "V_FLAG=${V_FLAG}"
echo "F_FLAG=${F_FLAG}"
echo "FILE=${FILE}"

exit 0


Mark1986 04-03-2012 09:09 AM

On the version of AIX that I am working on, it does not work. It seems not to handle the += and the ( ) on line 16.

catkin 04-03-2012 09:31 AM

Try this change
Code:

FILE=
files=( )
i=0
option_regex=^-

while getopts vf: opt
do
  case $opt in
  v)
    V_FLAG=1
    if [[ ! $2 =~ $option_regex ]]; then
        files[i]="$2"
        let i=i+1

        shift
    fi
  ;;


Mark1986 04-04-2012 03:18 AM

That did the trick, thank you very much!

Edit: It actually only works in the example given, if I start the script as ./mailen_getopts.sh -f foo foo3 -v foo1 foo2

It gives:

$ ./mailen_getopts.sh -v foo foo3 -f foo1 foo2
Rest foo3 -f foo1 foo2

V_FLAG=1
F_FLAG=
FILE=

So the problem occurs again when I enter an extra argument. The solution seems to be static, not dynamic. Even though with that shift I would think this would be dynamic.

catkin 04-04-2012 05:02 AM

Better to custom program it to solve that:
Code:

#!/bin/bash

V_FLAG=
F_FLAG=
files=( )
option_regex=^-
i=0
set -e

while [[ $# -gt 0 ]]
do
  if [[ $1 =~ $option_regex ]]; then
    opt=${1:1:1}
    optarg=${1:2}
    echo "DEBUG: opt: $opt, optarg: $optarg"
    case $opt in
      v )
        V_FLAG=1
        ;; 
      f )
        F_FLAG=1
        if [[ $optarg = '' ]]; then
          files[i]="$2"
          let i=i+1
          shift
        else
          files[i]=$optarg
          let i=i+1
        fi 
        ;; 
    * )
      echo "Usage: ${0##*/}: [-v] [-f filename] [file ...]"
      exit 1
    esac
  else
    files[i]=$1
    let i=i+1
  fi
  shift
done

echo "DEBUG: V_FLAG: $V_FLAG"
echo "DEBUG: F_FLAG: $F_FLAG"
echo "DEBUG: files (array): ${files[*]}"

exit 0

EDIT: the above allows the -f option argument to be joined to the -f:
Code:

c@CW8:/tmp$ ./try.sh -v foo1 foo2 -f foo3 foo4 -ffoo5
DEBUG: opt: v, optarg:
DEBUG: opt: f, optarg:
DEBUG: opt: f, optarg: foo5
DEBUG: V_FLAG: 1
DEBUG: F_FLAG: 1
DEBUG: files (array): foo1 foo2 foo3 foo4 foo5


Mark1986 04-16-2012 07:18 AM

I used what you have given me. I finally figured out that indeed getopt or getopts wasn't going to do what I wanted. And I custom crafted the script as I want it to be:

Code:

#!/bin/bash

# Set paramaters and variables
V_FLAG=
F_FLAG=
flags=( )
PREV_FLAG=""
files=( )
i=0
a=0

# Function to add a flag that was used in the flags array
function ADD_FLAG(){
  flags[a]=$1
  let a=a+1
}

# Loop through all arguments given
while [[ $# -gt 0 ]]
do
  opt=${1}
  case $opt in
#If there was an argument -v, mark the flag
    -v )
      V_FLAG=1
      PREV_FLAG="v"
      ;; 
#If there was an argument -f, mark the flag
    -f )
      F_FLAG=1
      PREV_FLAG="f"
      ;; 
#If the argument was not a -v or -f act as below
    * )
#The argument -f should be the last argument given, if not then exit with the usage details
      if [[ $PREV_FLAG = "f" ]] && [[ $i > 0 ]]
      then
        echo "Usage: mailen_getopts [-v] [-f file] [file ...]"
        exit 1
      fi
#If the previous argument was -f, then this argument is the file first mentioned
      if [[ $PREV_FLAG = "f" ]]
      then
        file=$opt
      else
#If none of the above apply, then add argument into the files array
        files[i]=$opt
        let i=i+1
      fi
#Unmark the flag for previous argument
      PREV_FLAG=
      ;;
    esac
#If the previous flag was not empty, then add the flag to the flags array
  if [[ ! $PREV_FLAG = "" ]]
  then
      ADD_FLAG $PREV_FLAG
  fi
  shift
done

#Echo the debug information
echo "DEBUG: V_FLAG: $V_FLAG"
echo "DEBUG: F_FLAG: $F_FLAG"
echo "DEBUG: flags (array): ${flags[*]}"
echo "DEBUG: file: $file"
echo "DEBUG: files (array): ${files[*]}"

exit 0

This looks for the arguments given. The inflexibility is that you cannot combine arguments like -vf. Something for the future maybe (most likely not ;-) )...

If you have any comments or questions about this script, please let me know.

I am marking this thread as solved. Thank you for your time.

catkin 04-16-2012 09:32 AM

Glad you evolved a solution to meet your requirements and thanks for the update :)


All times are GMT -5. The time now is 03:33 AM.