Python Command line arguments: Part 3
Tags: tutorial, python, productivity
Published: 2020-02-19 19:27:19

As we get into more details with python command-line arguments, we have to remember the generic household name in the command line family; `getopt`.

See, before we had the fancy `argparse`, we had this swiss army knife of a package. If you are a 'regular-rish' C programmer, I bet this rings a bell. As a notable difference between the two, there is definitely more code involved. There are a few more differences that will come to light as we explore the same program from before. Here goes. As a little challenge, we are going to leave the project section out just to see how far we can get without the extra nudge. Remember the [square program](https://marvin.hashnode.dev/python-command-line-arguments-part-1-ck2gd5o7400fewks155jr7isq)? 
```python
import getopt
import sys
from math import sqrt

"""
get the square but get the square root in case the argument 'root' is provided
"""


def usage():
    """
    Show help for the CLI program
    """
    print("python advanced_square.py --number <your_number> \n OR\n")
    print("python advanced_square.py -n <your_number>\n")
    print("To get the square root: python advanced_square.py -n <your_number> -r")
    print("Example: get the square\n\tpython advanced_square.py -n 5")
    


def main():
    
    try:
        option, arguments = getopt.getopt(sys.argv[1:],"hn:r",["help","number=","root"])
    except getopt.GetoptError as error:
        print(error)
        sys.exit()

    # initialize variables for for loop
    number = None
    root_number = False

    for opt, variable in option:
        if opt in ("-h", "--help"):
            usage()
        elif opt in ("-n", "--number"):
            number = int(variable)
        elif opt in ('-r','--root'):
            root_number = True
        else:
            usage()
            sys.exit()
   
    if root_number:
        print(f"The square root of {number} = {sqrt(number)}")
    else:
        print(f"The square of {number} = {number* number} ")
    


if __name__ == '__main__':
    main()
```

What this program does is simple. Get the square of a number parsed as a command line or get it's root if the `--root` or `-r` is parsed.

To run it.
```python
# get the square of 5
python advanced_square.py -n 5

# get the square root of 5
python advanced_square.py -n 5 --root  
```

Let's break this down.
Unlike grandson `argparse` which just knows how to display helpful messages, in `getopt` we do not get this right out of the box. So running `python advanced_square.py` will simply give a user an error screen like so 
```bash
Traceback (most recent call last):
  File "advanced_square.py", line 48, in <module>
    main()
  File "advanced_square.py", line 43, in main
    print(f"The square of {number} = {number* number} ")
TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'

```
 we have to specify how we want this run. That's where our custom function `usage` comes in. Telling the user, *'Hey, know what? You messed up'*

Take a look at this line here:
```python
option, arguments = getopt.getopt(sys.argv[1:],"hn:r",["help","number=","root"])
```

What `getopt.getopt()` does is accept three arguments,
```python
#Accept a list of command-line arguments excluding the program #name itself.
sys.argv[1:]
```
Accept *short* options. The reason why in your Unix-like terminal you can say `ls -a` or `ls --al`l to list contents of a directory including hidden files. `-a` would be the short option in this instance 

```python
"hn:r"
```
Note how we show short options. Our program, `advanced_square.py` accepts three short options, namely, `h` for help, `n` for the number whose square or square root we want and `r` to specify whether it is indeed the number's root we need.

See this `n:`? That little colon after letter **n** specifies that after we write n, we need the actual number parsed. Hence `-n 5` or `-n 29` and so on. The long option alternative, as you might have noticed, would be `number=`.  We add an equal sight to it to show that this option needs an argument to follow it, unlike its counterpart `root` and `help`

We follow it up with `r` to specify that the user can just write `-n 5 -r` which would mean get me the root of 5. The long alternatives to this program would be :

```bash
python advanced_square.py --number 5 --root  
```
The order does not really matter. Whether `--number` comes after `--root` or not is up to you. `getopt` will know what to do. Isn't that cool?

Just note, however, that if you specify `n` to be capable of receiving a number, then that is what must follow it. So you cannot do this
```bash
# wrong move
python advanced_square.py --number  --root 5 
```

As a whole;
```python
getopt.getopt(sys.argv[1:],"hn:r",["help","number=","root"])
```
The return of the above we get  two items. The first, which we name 'options' looks like this `[('-n', '5')]`   while the second, named 'arguments' is simply this `[]`, an empty list! That's odd. One might say.

The arguments variable holds what extra arguments have been parsed to the program. So if a user does:

```bash
python advanced_square.py -n 7  9
```      
options will look like:`[('-n', '5')]` while arguments will look like `['9']`. It would hold all those *weird* extras you might pass in.So if we randomly decide to use:
```bash
python advanced_square.py -n 7  -h
``` 
`options` would look like this `[('-n', '7'), ('-h', '')]`, a list of tuples. See some light at the end of the tunnel?
 
Let's move to the next line.
```python
getopt.GetoptError as error:
```

Notice how we get the error.
In this way, anything that is not in the required command-line argument specification is caught as an error. For instance, you use `python advanced_square.py --animal  5 `.  In such a case, we want to display the error message and gracefully exit the program. In short, using the phrase `animal` is nowhere defined in our program!


Because our main focus is on the arguments the program actually needs, we abandon use on the `arguments`(extras) variable and loop via the list of the `options`. We are saying, if `('-n', '7')`, in this case, **n** is present and has value, take it and do '1 2 3' else, if `('h', '')` which is present is there, call this function - *help me!*.

We convert the value of the parsed item, our '5' to an integer as `getopt` assumes everything coming in is a *string*. So we change the input to an `int` and assign it to a variable called 'number'
 `number = int(variable)`

Getting the number and the value of `root` as either True or False, we can safely get our square and root for use through the program.

This walk through's code, as usual, is at [TheGreenCodes](https://github.com/TheGreenCodes/CommandLineArguments). Till next time though, adios!