Bash: processing arguments in a script when called from the shell

Этот пост был опубликован мной более года назад. Информация, описанная ниже, уже могла потерять актуальность, но всё ещё может быть полезна.

Read in Medium

Hi. In my spare time, I write a project for work purposes, which consists of a bunch of bash scripts. There is one entry point that connects the rest of the functionality via ‘source’. They contain functions that should only process the required arguments directly from the cli.

In early versions of the project, I just explicitly passed $1, $2, $N from top to bottom to other functions. It was a stupid solution, and it worked because the possible arguments and their order were known and simple. But it was disgusting aesthetically. I wanted to enjoy reading code and to unify a lot of things. To do this, the entire code had to be greatly complicated to make everything conceptually much simpler.

So, different functions must accept different arguments from input, and also I want to give to user an ability to pass:

  • long arguments with no values:
    ./script.sh --foo --bar
  • long arguments with values:
    ./script.sh --foo = bar
  • short arguments without values:
    ./script.sh -a -b -c
  • short arguments with values:
    ./script.sh -a avalue -b bvalue
  • combine any short short arguments into one word:
    ./script.sh -abc bvalue
  • all of above ones at the same time!
  • the order of the arguments shouldn’t matter
  • I hould have access to any of them from anywhere at any time.

getopt could solve this problem. But inside each separate function it is impossible to parse different arguments:

#!/bin/bash

# all possible arguments should be listed here
getopt -o a -- $@

func1() {
    getopt -o b -- $@ # throws error
}

func2() {
    getopt -o с -- $@ # throws error
}

getopts just doesn’t support long arguments.

It is always possible to get the required arguments by numeric indices and pass them on. I did this before — and I want to get rid of it.

Inside any bash script that you run from the cli, an array of arguments $@ is available with which it was called. I just have to get them and parse correctly.

TL;DR

  • for short arguments: I loop through $@ and check if current element is an expected argument (-b) or if it is among a combination of other short arguments (-abc). If this is it and the fact of its presence is expected, then I return 1. If this is it and its value is expected, then I return the next argument;
  • for long arguments the same thing, but simpler, because there is no need to check combinations: if we find an argument and only its presence is required, then we return 1, and if we find an argument with a value, we return everything from = to the end of the argument.

I also wrote a function to get an argument by its index — it can also be useful if the order of the arguments is known and you need to take a specific one.

Below is the finished code, with examples and more detailed comments step by step. You can save this code to the args.sh file, and add . args.sh or source args.sh where you need it. Then you’ll be able to use arg, argl and argn functions as shown.

Leave a comment

Your email address will not be published. Required fields are marked *