Manolo Dominguez Becerra
Manolo Dominguez Becerra

Reputation: 1357

Parsing optional and not optional arguments

I am new with bash and after reading and trying a lot about how to parse arguments I cannot what I really want to do I want to parse optional and not optional arguments. More specifically I want to parse 3 arguments, first (a fastaq file) second (a second optional fastaq file) a third argument that will be a directory.

my_script.sh -f1 file1.fasta --f2 file2.fasta -d/home/folder1/folder2 or

my_script.sh -f1 file1.fasta -d /home/folder1/folder2

I have tried to do this in many ways but I dont know how to let the program identifies when there are two fasta files and a directory and, when there is only one fasta file and a directory.

With this arguments I want to save them in variables because they will be used later by third parties.

I have tried this:



for i in "$@"; do
 case $i in
   -f1=|-fasta1=)
     FASTA1="${i#=}"
     shift # past argument=value
     ;;
   -d) DIRECTORY=$2
  shift 2
     ;;
   -d=|-directory=) DIRECTORY="${i#=}"
   shift # past argument=value
     ;;
   --f2=|-fasta2=) FASTA2="${i#*=}"
    shift # past argument=value
     ;;
   *)
     ;;
 esac
done

But I just got this

scripts_my_first_NGS]$ ./run.sh -f1 fasta.fasta -d /home/folder1
FASTA1  =
DIRECTORY     =
FASTA2     =

Upvotes: 2

Views: 3112

Answers (2)

konsolebox
konsolebox

Reputation: 75488

Basically you need to add a separate parser for versions of the options where they aren't used with the equal sign.

Also your shift commands are useless since you're processing a for loop. So convert it to to a while [[ $# -gt 0 ]]; do loop instead.

I also added a few modifications which I suggest be added.

while [[ $# -gt 0 ]]; do
    case $1 in
    -f1|-fasta1)
        FASTA1=$2
        shift
        ;;
    -f1=*|-fasta1=*)
        FASTA1=${1#*=}
        ;;
    -d|-directory)
        DIRECTORY=$2
        shift
        ;;
    -d=*|-directory=*)
        DIRECTORY=${1#*=}
        ;;
    -f2|fasta2)
        FASTA2=$2
        shift
        ;;
    -f2=*|-fasta2=*)
        FASTA2=${1#*=}
        ;;
    -*)
        echo "Invalid option: $1" >&2
        exit 1
        ;;
    --)
        # Do FILES+=("${@:2}") maybe
        break
        ;;
    *)
        # TODO
        # Do FILES+=("$1") maybe
        ;;
    esac

    shift
done

The "parser" for the with-equal and non-with-equal versions of the options can also be unified by using a helper function:

function get_opt_arg {
    if [[ $1 == *=* ]]; then
        __=${1#*=}
        return 1
    elif [[ ${2+.} ]]; then
        __=$2
        return 0 # Tells that shift is needed
    else
        echo "No argument provided to option '$1'." >&2
        exit 1
    fi
}

while [[ $# -gt 0 ]]; do
    case $1 in
    -d|-directory|-d=*|-directory=*)
        get_opt_arg "$@" && shift
        DIRECTORY=$__
        ;;
    -f1|-fasta1|-f1=*|-fasta1=*)
        get_opt_arg "$@" && shift
        FASTA1=$__
        ;;
    -f2|fasta2|-f2=*|-fasta2=*)
        get_opt_arg "$@" && shift
        FASTA2=$__
        ;;
    -*)
        echo "Invalid option: $1" >&2
        exit 1
        ;;
    --)
        # Do FILES+=("${@:2}") maybe
        break
        ;;
    *)
        # TODO
        # Do FILES+=("$1") maybe
        ;;
    esac

    shift
done

Update

I found a complete solution to command-line parsing without relying on getopt[s] and it does it even more consistentlty: https://konsolebox.io/blog/2022/05/14/general-command-line-parsing-solution-without-using-getopt-s.html

Upvotes: 0

ceving
ceving

Reputation: 23822

Never parse command line options on your own!

Instead either use the Bash function getopts, if you do not need GNU style long options or use use the GNU program getopt otherwise.

The following examples uses an array for FASTA. FASTA1 is ${FASTA[0]} and FASTA2 is ${FASTA[1]}. In case of getopts this makes it possible to use just one option character (-f) multiple times.

Using getopts with only one-character options:

#! /bin/bash

FASTA=()
DIRECTORY=

while getopts 'f:d:' option; do
  case "$option" in
    f)
      FASTA+=("$OPTARG")
      ;;
    d)
      DIRECTORY="$OPTARG"
      ;;
    *)
      printf 'ERROR: Invalid argument\n' >&2
      exit 1
      ;;
  esac
done
shift $((OPTIND-1))

if [[ -z ${FASTA[0]} ]]; then
  printf 'ERROR: FASTA1 missing\n' >&2
  exit 1
fi

if [[ -z $DIRECTORY ]]; then
  printf 'ERROR: DIRECTORY missing\n' >&2
  exit 1
fi

printf 'FASTA1 = %s\n' "${FASTA[0]}"
printf 'FASTA2 = %s\n' "${FASTA[1]}"
printf 'DIRECTORY = %s\n' "$DIRECTORY"

Usage:

run -f file1.fasta -f file2.fasta -d /home/folder1/folder2

Using getopt with one-character and GNU style long options mixed:

#! /bin/bash

FASTA=()
DIRECTORY=

options=$(getopt -o d: -l f1: -l f2: -- "$@") || {
  printf 'ERROR: Invalid argument\n' >&2
  exit 1
}
eval set -- "$options"

while true; do
  case "$1" in
    --f1)
      FASTA[0]="$2"
      shift 2;;
    --f2)
      FASTA[1]="$2"
      shift 2;;
    -d)
      DIRECTORY="$2"
      shift 2;;
    --)
      shift
      break;;
    *)
      break;;
  esac
done

if [[ -z ${FASTA[0]} ]]; then
  printf 'ERROR: FASTA1 missing\n' >&2
  exit 1
fi

if [[ -z $DIRECTORY ]]; then
  printf 'ERROR: DIRECTORY missing\n' >&2
  exit 1
fi

printf 'FASTA1 = %s\n' "${FASTA[0]}"
printf 'FASTA2 = %s\n' "${FASTA[1]}"
printf 'DIRECTORY = %s\n' "$DIRECTORY"

Usage:

run --f1 file1.fasta --f2 file2.fasta -d /home/folder1/folder2

Upvotes: 1

Related Questions