Shell by Example: Getopts POSIX

getopts is a shell built-in for parsing command-line options. It supports short options like -v and -n value.

Limitations: getopts only handles short options. It does not support --long options or the -f=value syntax. For those, you’ll need external tools like GNU getopt or manual parsing.

The basic pattern is a while loop with a case statement. The optstring "v" tells getopts to accept the -v flag. The variable opt receives the option letter being processed.

Edit
#!/bin/sh
demo() {
    verbose=false
    OPTIND=1
    while getopts "v" opt; do
        case "$opt" in
            v) verbose=true ;;
        esac
    done
    echo "Verbose: $verbose"
}

echo "With -v:"
demo -v

echo "Without flags:"
demo
Output:
With -v:
Verbose: true
Without flags:
Verbose: false

You can accept multiple flags by adding more letters to the optstring. Each letter becomes a valid option.

Here "vdh" means the script accepts -v, -d, and -h.

Edit
#!/bin/sh
demo() {
    verbose=false
    debug=false
    OPTIND=1
    while getopts "vdh" opt; do
        case "$opt" in
            v) verbose=true ;;
            d) debug=true ;;
            h) echo "Usage: script [-v] [-d] [-h]" ;;
        esac
    done
    echo "Verbose: $verbose, Debug: $debug"
}

echo "With -v -d:"
demo -v -d

echo "With -v only:"
demo -v
Output:
With -v -d:
Verbose: true, Debug: true
With -v only:
Verbose: true, Debug: false

Users expect -h to show help, but getopts doesn’t handle it automatically. If you don’t include h in your optstring, -h is treated as an unknown option.

This example shows what happens when -h isn’t implemented.

Edit
#!/bin/sh
demo() {
    verbose=false
    OPTIND=1
    while getopts "v" opt 2>/dev/null; do
        case "$opt" in
            v) verbose=true ;;
            ?)
                echo "Unknown option"
                return 1
                ;;
        esac
    done
    echo "Running with verbose=$verbose"
}

echo "With -v (valid):"
demo -v

echo ""
echo "With -h (not in optstring):"
demo -h
Output:
With -v (valid):
Running with verbose=true

With -h (not in optstring):
Unknown option

To support -h, add h to your optstring and handle it in your case statement. Use exit 0 (or return 0 in a function) after showing help to stop execution.

A here-document is a clean way to format multi-line help text.

Edit
#!/bin/sh
show_help() {
    cat <<EOF
Usage: greet [-v] [-h]

Options:
  -v    Enable verbose output
  -h    Show this help message

Example:
  greet -v
EOF
}

demo() {
    verbose=false
    OPTIND=1
    while getopts "vh" opt; do
        case "$opt" in
            v) verbose=true ;;
            h)
                show_help
                return 0
                ;;
        esac
    done
    echo "Running with verbose=$verbose"
}

echo "With -h flag:"
demo -h

echo ""
echo "With -v flag:"
demo -v
Output:
With -h flag:
Usage: greet [-v] [-h]

Options:
  -v    Enable verbose output
  -h    Show this help message

Example:
  greet -v

With -v flag:
Running with verbose=true

Some options need to accept a value, like -n Alice. A colon : after a letter means that option requires an argument.

"n:" means -n takes a value. The value is stored in the special variable $OPTARG.

Edit
#!/bin/sh
demo() {
    name=""
    OPTIND=1
    while getopts "n:" opt; do
        case "$opt" in
            n) name="$OPTARG" ;;
        esac
    done
    echo "Name: ${name:-<not set>}"
}

echo "With -n Alice:"
demo -n "Alice"

echo "With -n Bob:"
demo -n "Bob"
Output:
With -n Alice:
Name: Alice
With -n Bob:
Name: Bob

When an option takes an argument, you can attach the value directly to the flag without a space. Both -n Alice and -nAlice work identically.

This is useful for compact command lines and is commonly seen in tools like tar -xvf archive.tar.

Edit
#!/bin/sh
demo() {
    name=""
    OPTIND=1
    while getopts "n:" opt; do
        case "$opt" in
            n) name="$OPTARG" ;;
        esac
    done
    echo "Name: $name"
}

echo "With space: -n Alice"
demo -n Alice

echo ""
echo "Attached: -nAlice"
demo -nAlice

echo ""
echo "Attached: -nBob"
demo -nBob
Output:
With space: -n Alice
Name: Alice

Attached: -nAlice
Name: Alice

Attached: -nBob
Name: Bob

You can mix flags and options with arguments. Each : only applies to the letter immediately before it.

"vn:o:" means: - -v is a flag (no argument) - -n takes an argument - -o takes an argument

Edit
#!/bin/sh
demo() {
    verbose=false
    name=""
    output=""
    OPTIND=1
    while getopts "vn:o:" opt; do
        case "$opt" in
            v) verbose=true ;;
            n) name="$OPTARG" ;;
            o) output="$OPTARG" ;;
        esac
    done
    echo "Verbose: $verbose, Name: ${name:-<none>}, Output: ${output:-<none>}"
}

echo "With -v -n Alice -o results.txt:"
demo -v -n "Alice" -o "results.txt"

echo "With -n Bob only:"
demo -n "Bob"
Output:
With -v -n Alice -o results.txt:
Verbose: true, Name: Alice, Output: results.txt
With -n Bob only:
Verbose: false, Name: Bob, Output: <none>

When a user passes an unknown option (like -x when only -v is valid), getopts returns ?. By default, getopts also prints an error message to stderr.

It’s good practice to handle the ? case and show usage.

Edit
#!/bin/sh
demo() {
    verbose=false
    OPTIND=1
    while getopts "vn:" opt; do
        case "$opt" in
            v) verbose=true ;;
            n) name="$OPTARG" ;;
            ?)
                echo "Usage: script [-v] [-n name]"
                return 1
                ;;
        esac
    done
    echo "Verbose: $verbose"
}

echo "With valid -v:"
demo -v

echo "With invalid -x (stderr suppressed for demo):"
demo -x 2>/dev/null
Output:
With valid -v:
Verbose: true
With invalid -x (stderr suppressed for demo):
Usage: script [-v] [-n name]

Adding a leading : to the optstring enables “silent mode”. In silent mode, getopts doesn’t print error messages, letting you provide custom ones.

In silent mode: - ? case: unknown option (OPTARG contains the option letter) - : case: missing required argument (OPTARG contains the option)

Edit
#!/bin/sh
demo() {
    name=""
    OPTIND=1
    while getopts ":n:" opt; do
        case "$opt" in
            n) name="$OPTARG" ;;
            :) echo "Error: -$OPTARG requires an argument" ;;
            ?) echo "Error: Unknown option -$OPTARG" ;;
        esac
    done
    [ -n "$name" ] && echo "Name: $name"
}

echo "With unknown option -x:"
demo -x

echo "With missing argument for -n:"
demo -n

echo "With valid -n Alice:"
demo -n "Alice"
Output:
With unknown option -x:
Error: Unknown option -x
With missing argument for -n:
Error: -n requires an argument
With valid -n Alice:
Name: Alice

Users can combine multiple flags into one argument. Instead of -v -d, they can write -vd. getopts handles this automatically.

This also works with options that take arguments: -vn Alice is the same as -v -n Alice.

Edit
#!/bin/sh
demo() {
    verbose=false
    debug=false
    name=""
    OPTIND=1
    while getopts "vdn:" opt; do
        case "$opt" in
            v) verbose=true ;;
            d) debug=true ;;
            n) name="$OPTARG" ;;
        esac
    done
    echo "Verbose: $verbose, Debug: $debug, Name: ${name:-<none>}"
}

echo "Separate flags: -v -d"
demo -v -d

echo "Combined flags: -vd"
demo -vd

echo "Combined with argument: -vdn Alice"
demo -vdn "Alice"
Output:
Separate flags: -v -d
Verbose: true, Debug: true, Name: <none>
Combined flags: -vd
Verbose: true, Debug: true, Name: <none>
Combined with argument: -vdn Alice
Verbose: true, Debug: true, Name: Alice

After options, scripts often need positional arguments (like filenames). The OPTIND variable tracks where getopts stopped parsing.

Use shift $((OPTIND-1)) to remove processed options, leaving only positional arguments in $@.

Edit
#!/bin/sh
demo() {
    verbose=false
    OPTIND=1
    while getopts "v" opt; do
        case "$opt" in
            v) verbose=true ;;
        esac
    done
    shift $((OPTIND - 1))
    echo "Verbose: $verbose"
    echo "Files: $@"
}

echo "With -v file1.txt file2.txt:"
demo -v "file1.txt" "file2.txt"

echo "With just file3.txt:"
demo "file3.txt"
Output:
With -v file1.txt file2.txt:
Verbose: true
Files: file1.txt file2.txt
With just file3.txt:
Verbose: false
Files: file3.txt

Here’s a complete example combining all getopts concepts: flags, options with arguments, silent error handling, and positional arguments.

This pattern is commonly used in production shell scripts.

Edit
#!/bin/sh
process_files() {
    verbose=false
    output=""
    OPTIND=1

    while getopts ":vo:h" opt; do
        case "$opt" in
            v) verbose=true ;;
            o) output="$OPTARG" ;;
            h)
                echo "Usage: process [-v] [-o outfile] file..."
                return 0
                ;;
            :)
                echo "Error: -$OPTARG requires an argument" >&2
                return 1
                ;;
            ?)
                echo "Error: Unknown option -$OPTARG" >&2
                return 1
                ;;
        esac
    done
    shift $((OPTIND - 1))

    [ $# -eq 0 ] && {
        echo "Error: No files specified" >&2
        return 1
    }

    echo "Verbose: $verbose, Output: ${output:-<stdout>}"
    for file in "$@"; do
        [ "$verbose" = true ] && echo "Processing: $file" || echo "  $file"
    done
}

echo "Basic usage: -v -o results.txt data.txt log.txt"
process_files -v -o "results.txt" "data.txt" "log.txt"

echo ""
echo "Help flag: -h"
process_files -h
Output:
Basic usage: -v -o results.txt data.txt log.txt
Verbose: true, Output: results.txt
Processing: data.txt
Processing: log.txt

Help flag: -h
Usage: process [-v] [-o outfile] file...

A practical tar-like example: -xvf archive.tar combines boolean flags (-x, -v) with an option that takes an attached value (-f archive.tar).

This pattern is common in Unix tools where multiple short options can be combined, with the last one optionally taking an argument.

Edit
#!/bin/sh
tar_demo() {
    extract=false
    verbose=false
    file=""
    OPTIND=1
    while getopts "xvf:" opt; do
        case "$opt" in
            x) extract=true ;;
            v) verbose=true ;;
            f) file="$OPTARG" ;;
        esac
    done
    echo "Extract: $extract, Verbose: $verbose, File: $file"
}

echo "tar-like: -xvf archive.tar"
tar_demo -xvf archive.tar

echo ""
echo "Separate flags: -x -v -f archive.tar"
tar_demo -x -v -f archive.tar
Output:
tar-like: -xvf archive.tar
Extract: true, Verbose: true, File: archive.tar

Separate flags: -x -v -f archive.tar
Extract: true, Verbose: true, File: archive.tar

« Command Line Arguments | Environment Variables »