Jak przetwarzać argumenty wiersza poleceń w Bash?

Powiedzmy, że mam skrypt, który zostanie wywołany z tej linii:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

Lub ten:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

Jaki jest akceptowany sposób parsowania tego tak, że w każdym przypadku (lub jakaś kombinacja tych dwóch) $v, $f, i $d czy wszystkie będą ustawione na true i $outFile będą równe /fizz/someOtherFile?

Author: codeforester, 2008-10-10

29 answers

Metoda #1: Używanie Basha bez getopt [s]

Dwa popularne sposoby przekazywania argumentów klucz-wartość-para to:

Bash oddzielony spacjami (np. --option argument) (bez getopt[s])

Użycie ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts

#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo FILE EXTENSION  = "${EXTENSION}"
echo SEARCH PATH     = "${SEARCHPATH}"
echo LIBRARY PATH    = "${LIBPATH}"
echo DEFAULT         = "${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi

Bash równa się-Oddzielone (np. --option=argument) (bez getopt [s])

Użycie ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi

Aby lepiej zrozumieć ${i#*=}wyszukaj "usuwanie Podłańcuchów" w w tym przewodniku. Jest funkcjonalnie równoważna `sed 's/[^=]*=//' <<< "$i"`, która wywołuje zbędny podproces lub `echo "$i" | sed 's/[^=]*=//'` który wywołuje dwa niepotrzebne podprocesy.

Metoda #2: Używanie bash z getopt [s]

From: http://mywiki.wooledge.org/BashFAQ/035#getopts

Getopt (1) ograniczenia (starsze, stosunkowo najnowsze wersje getopt):

  • nie można obsługiwać argumentów, które są pustymi łańcuchami
  • nie można obsługiwać argumentów z osadzonymi białymi znakami

Nowsze getopt wersje nie mają takich ograniczenia.

Dodatkowo powłoka POSIX (i inne) oferuje getopts, która nie ma tych ograniczeń. Oto uproszczony getopts przykład:

#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"

# End of file

Zalety getopts to:

  1. jest bardziej przenośny i będzie działał w innych powłokach, takich jak dash.
  2. może obsługiwać wiele pojedynczych opcji, takich jak -vf filename w typowy uniksowy sposób, automatycznie.

Wadą getopts jest to, że może obsługiwać tylko krótkie opcje (-h, a nie --help) bez dodatkowego kodu.

Istnieje getopts tutorial , który wyjaśnia, co oznaczają wszystkie składnie i zmienne. W bash istnieje również help getopts, które mogą być pouczające.

 2021
Author: Bruno Bronosky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-18 07:27:42

Żadna odpowiedź nie wspomina enhanced getopt. A najczęściej głosowana odpowiedź {[20] } jest myląca: ignoruje -⁠vfd opcje krótkie w stylu (wymagane przez OP), opcje po argumentach pozycyjnych (również wymagane przez OP) i ignoruje błędy parsowania. Zamiast:

  • użyj enhanced getopt z util-linux lub wcześniej GNU glibc .1
  • Działa z getopt_long() funkcją C GNU glibc.
  • ma wszystkie przydatne cechy wyróżniające (inni ich nie mają):
    • obsługuje spacje, cytowanie znaków, a nawet binarne w argumentach2
    • może obsługiwać opcje na końcu: script.sh -o outFile file1 file2 -v
    • pozwala = - style długie opcje: script.sh --outfile=fileOut --infile fileIn
  • jest już taka stara3 że żadnemu systemowi GNU tego nie brakuje (np. każdemu Linuksowi).
  • można sprawdzić jego istnienie za pomocą: getopt --test → wartość zwracana 4.
  • Inne getopt lub shell-builtin getopts mają ograniczone zastosowanie.

Następujące wywołania

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

All return

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

Z następującym myscript

#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo "I’m sorry, `getopt --test` failed in this environment."
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -use ! and PIPESTATUS to get exit code with errexit set
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1 enhanced getopt jest dostępny na większości "bash-systems", w tym Cygwin; na OS X spróbuj brew zainstalować gnu-getopt lub sudo port install getopt
2 konwencje POSIX exec() nie mają niezawodnego sposobu przekazywania binarnego NULL w argumentach wiersza poleceń; te bajty przedwcześnie kończą argument
3 pierwsza wersja wydana w 1997 lub wcześniej (śledziłem ją tylko do 1997)

 357
Author: Robert Siemer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-09 18:10:30

Od : digitalpeer.com z niewielkimi modyfikacjami

Użycie myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

Aby lepiej zrozumieć ${i#*=}wyszukaj "usuwanie Podłańcuchów" w w tym przewodniku. Jest to funkcjonalnie równoważne `sed 's/[^=]*=//' <<< "$i"`, które wywołuje zbędny podproces lub `echo "$i" | sed 's/[^=]*=//'`, które wywołuje dwa zbędne podprocesy.

 109
Author: guneysus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-20 18:35:46

getopt()/getopts() to dobra opcja. Skradzione z tutaj :

Proste użycie "getopt" jest pokazane w tym mini-skrypcie:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done

Powiedzieliśmy, że każdy z, -b, - c lub-d będą dozwolone, ale po-c następuje argument ("c:" mówi, że).

Jeśli nazwiemy to "g" i wypróbujemy:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

Zaczynamy od dwóch argumentów i "getopt" rozdziela opcje i każdy ma swój własny argument. Informatyka również dodano "--".

 102
Author: Matt J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-10 17:03:38

Ryzykując dodanie kolejnego przykładu do zignorowania, oto mój plan.

  • uchwyty -n arg i --name=arg
  • pozwala na argumenty na końcu
  • pokazuje poprawne błędy, jeśli coś jest źle napisane
  • kompatybilny, nie używa bashismów
  • czytelny, nie wymaga utrzymywania stanu w pętli
Mam nadzieję, że komuś się przyda.
while [ "$#" -gt 0 ]; do
  case "$1" in
    -n) name="$2"; shift 2;;
    -p) pidfile="$2"; shift 2;;
    -l) logfile="$2"; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;

    -*) echo "unknown option: $1" >&2; exit 1;;
    *) handle_argument "$1"; shift 1;;
  esac
done
 68
Author: bronson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-21 15:05:05

Bardziej zwięzły sposób

Script.sh

#!/bin/bash

while [[ "$#" > 0 ]]; do case $1 in
  -d|--deploy) deploy="$2"; shift;;
  -u|--uglify) uglify=1;;
  *) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"

Użycie:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify
 52
Author: Inanc Gumus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-07 21:13:24

Jestem jakieś 4 lata spóźniony na to pytanie, ale chcę się odwdzięczyć. Wykorzystałem wcześniejsze odpowiedzi jako punkt wyjścia do uporządkowania mojego starego parsowania adhoc param. Następnie refakturowałem poniższy kod szablonu. Obsługuje zarówno długie, jak i krótkie paramy, używając argumentów oddzielonych znakiem = lub spacją, a także wiele krótkich param zgrupowanych razem. W końcu ponownie wstawia wszelkie argumenty nie-param z powrotem do $1,$2.. zmienne. Mam nadzieję, że się przyda.

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT="$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE="$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done
 39
Author: Shane Day,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-01 01:20:22

Moja odpowiedź opiera się w dużej mierze naodpowiedzi Bruno Bronosky ' ego , ale w pewnym sensie zmiażdżyłem jego dwie czyste implementacje Basha w jedną, której używam dość często.

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key="$1"
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE="$1"
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done

Pozwala to mieć zarówno opcje/wartości rozdzielone spacjami, jak i równe zdefiniowane wartości.

Więc można uruchomić skrypt używając:

./myscript --foo -b -o /fizz/file.txt

Oraz:

./myscript -f --bar -o=/fizz/file.txt

I oba powinny mieć ten sam efekt końcowy.

PLUSY:

  • Pozwala zarówno na-arg = value, jak i-arg wartość

  • Działa z dowolną nazwą arg, której możesz użyć w bash

    • znaczenie-a lub-arg lub --arg lub-A-r - g lub cokolwiek
  • Czysty bash. Nie musisz uczyć się / używać getopt lub getopts

Wady:

  • Nie można połączyć args

    • czyli nie-abc. Musisz zrobić-A-b-c

To jedyne plusy/minusy, które mogę wymyślić z głowy

 23
Author: Ponyboy47,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:33

Znalazłem sprawę pisania przenośnego parsowania w skryptach tak frustrujące, że napisałem Argbash - generator kodu FOSS, który może generować argumenty-parsowanie kodu dla Twojego skryptu plus ma kilka fajnych funkcji:

Https://argbash.io

 23
Author: bubla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-09 10:39:37

Myślę, że ten jest wystarczająco prosty w użyciu:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

Przykład wywołania:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile
 13
Author: Alek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-01 15:15:03

Rozszerzając doskonałą odpowiedź @guneysus, oto tweak, który pozwala użytkownikowi korzystać z dowolnej składni, którą preferuje, np

command -x=myfilename.ext --another_switch 

Vs

command -x myfilename.ext --another_switch

Oznacza to, że równe mogą być zastąpione białymi spacjami.

Ta "rozmyta interpretacja" może Ci się nie podobać, ale jeśli tworzysz skrypty, które są wymienne z innymi narzędziami (jak w przypadku mojego, który musi działać z ffmpeg), użyteczna jest elastyczność.

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "$@"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done
 12
Author: unsynchronized,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-09 14:07:58

Getopts działa świetnie, jeśli #1 masz go zainstalowany i # 2 zamierzasz uruchomić go na tej samej platformie. OSX i Linux (na przykład) zachowują się inaczej pod tym względem.

Oto rozwiązanie (non getopts), które obsługuje znaczniki equals, non-equals i boolean. Na przykład możesz uruchomić swój skrypt w ten sposób:

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done
 8
Author: vangorra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-12 22:17:48

Podaję Ci funkcję parse_params, która będzie analizować params z linii poleceń.

  1. jest to czyste rozwiązanie Bash, bez dodatkowych narzędzi.
  2. nie zanieczyszcza globalnego zasięgu.
  3. bez wysiłku zwraca proste w użyciu zmienne, na których można zbudować dalszą logikę.
  4. Ilość kresek przed params nie ma znaczenia (--all równa się -all równa się all=all)

Poniższy skrypt to demonstracja pracy kopiuj-wklej. Patrz show_use Funkcja do zrozumieć, jak używać parse_params.

Ograniczenia:

  1. nie obsługuje param rozdzielonych spacjami(-d 1)
  2. nazwy Param utracą myślniki, więc --any-param i -anyparam są równoważne
  3. eval $(parse_params "$@") musi być użyta wewnątrz funkcji bash (nie będzie działać w zakresie globalnym)

#!/bin/bash

# Universal Bash parameter parsing
# Parses equal sign separated params into local variables (--name=bob creates variable $name=="bob")
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Parses un-named params into ${ARGV[*]} array
# Additionally puts all named params raw into ${ARGN[*]} array
# Additionally puts all standalone "option" params raw into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4 (Jun-26-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion
        _escaped=${1/\*/\'\"*\"\'}
        # If equals delimited named parameter
        if [[ "$1" =~ ^..*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "$1" | cut -d = -f 1)
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key=\"$_val\";"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        elif [[ "$1" =~ ^\-. ]]; then
            # remove dashes
            local _key=${1//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "$@")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
 8
Author: Oleksii Chekulaiev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-06 13:00:40

Tak robię w funkcji, aby uniknąć łamania getoptów uruchamianych w tym samym czasie gdzieś wyżej w stosie:

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}
 6
Author: akostadinov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-19 07:50:31

EasyOptions nie wymaga żadnego parsowania:

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi
 6
Author: Renato Silva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-04 16:47:40

Chciałbym zaoferować moją wersję parsowania opcji, która pozwala na:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

Pozwala również na to (może być niechciane):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

Musisz zdecydować przed użyciem, czy = ma być używany w danej opcji, czy nie. Ma to na celu zachowanie czystości kodu (ish).

while [[ $# > 0 ]]
do
    key="$1"
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE="$2"
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder="$2"
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done
 4
Author: galmok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 10:54:23

Zauważ, że getopt(1) był krótkim życiowym błędem AT & T.

Getopt został stworzony w 1984 roku, ale już pogrzebany w 1986 roku, ponieważ nie był naprawdę użyteczny.

Dowodem na to, że getopt jest bardzo nieaktualna jest to, że strona podręcznika getopt(1) nadal wspomina "$*" zamiast "$@", która została dodana do powłoki Bourne ' a w 1986 roku wraz z powłoką getopts(1) wbudowaną w celu radzenia sobie z argumentami ze spacjami wewnątrz.

BTW: jeśli jesteś zainteresowany analizowaniem długich opcji w powłoce skryptów, warto wiedzieć, że implementacja getopt(3) z libc (Solaris) i ksh93 dodała jednolitą implementację długich opcji, która obsługuje długie opcje jako aliasy dla krótkich opcji. Powoduje to, że ksh93 i Bourne Shell implementują jednolity interfejs dla długich opcji poprzez getopts.

Przykład długich opcji pobranych ze strony podręcznika powłoki Bourne ' a:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

Pokazuje jak długie aliasy opcji mogą być używane zarówno w powłoce Bourne ' a jak i ksh93

Zobacz stronę podręcznika najnowszej powłoki Bourne ' a:

Http://schillix.sourceforge.net/man/man1/bosh.1.html

I strona podręcznika dla getopt (3) z OpenSolaris:

Http://schillix.sourceforge.net/man/man3c/getopt.3c.html

I Ostatnia, strona podręcznika getopt (1), Aby zweryfikować nieaktualne $*:

Http://schillix.sourceforge.net/man/man1/getopt.1.html

 4
Author: schily,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-20 21:59:55

Mieszanie argumentów pozycyjnych i flagowych

--param = arg (equals delimited)

Dowolne mieszanie FLAG między pozycyjnymi argumentami:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

Można osiągnąć dość zwięzłym podejściem:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

-- param arg (spacja rozdzielona)

Jest zwykle jaśniejsze, aby nie mieszać --flag=value i --flag value stylów.

./script.sh dumbo 127.0.0.1 --environment production -q -d

To jest trochę ryzykowne do czytania, ale nadal jest ważne

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

Źródło

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2
 2
Author: Mark Fox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-27 02:42:47

Załóżmy, że tworzymy skrypt powłoki o nazwie test_args.sh w następujący sposób

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

Po uruchomieniu następującego polecenia:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

Wyjście będzie:

year=2017 month=12 day=22 flag=true
 2
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-10 22:49:30

Użyj modułu "argumenty" z bash-modules

Przykład:

#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "$@" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"
 1
Author: Volodymyr M. Lisivka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-09 16:51:09

To również może być przydatne, aby wiedzieć, Można ustawić wartość i jeśli ktoś dostarcza dane wejściowe, nadpisać domyślną z tej wartości..

Myscript.sh -f ./ serverlisttxt lub po prostu. /myscript.sh (i przyjmuje wartości domyślne)

    #!/bin/bash
    # --- set the value, if there is inputs, override the defaults.

    HOME_FOLDER="${HOME}/owned_id_checker"
    SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

    while [[ $# > 1 ]]
    do
    key="$1"
    shift

    case $key in
        -i|--inputlist)
        SERVER_FILE_LIST="$1"
        shift
        ;;
    esac
    done


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"
 1
Author: Mike Q,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-14 18:01:08

Inne rozwiązanie bez getopt [s], POSIX, old Unix style

Podobne do rozwiązanie Bruno Bronosky opublikował ten tutaj jest jeden bez użycia getopt(s).

Główną cechą odróżniającą moje rozwiązanie jest to, że pozwala ono na łączenie ze sobą opcji tak jak {[4] } jest równe tar -x -z -f foo.tar.gz. I tak jak w tar, ps itd. łącznik wiodący jest opcjonalny dla bloku krótkich opcji (ale można to łatwo zmienić). Długie opcje są również obsługiwane (ale gdy blok zaczyna się od jednego, wtedy wymagane są dwa myślniki wiodące).

Kod z przykładowymi opcjami

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"
echo

print_usage() {
  echo "Usage:

  $0 {a|b|c} [ARG...]

Options:

  --aaa-0-args
  -a
    Option without arguments.

  --bbb-1-args ARG
  -b ARG
    Option with one argument.

  --ccc-2-args ARG1 ARG2
  -c ARG1 ARG2
    Option with two arguments.

" >&2
}

if [ $# -le 0 ]; then
  print_usage
  exit 1
fi

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  elif [ ! "$opt" ]; then

    # we are at the beginning of a fresh block
    # remove optional leading hyphen and strip trailing whitespaces
    opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

  fi

  # get the first character -> check whether long option
  first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
  [ "$first_chr" = - ] && long_option=T || long_option=F

  # note to write the options here with a leading hyphen less
  # also do not forget to end short options with a star
  case $opt in

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

      if [ "$2" ]; then
        echo "Option BBB with argument '$2' activated!"
        shift
      else
        echo "BBB parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    c*|-ccc-2-args)

      if [ "$2" ] && [ "$3" ]; then
        echo "Option CCC with arguments '$2' and '$3' activated!"
        shift 2
      else
        echo "CCC parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

      if [ "$long_option" = T ]; then
        opt=$(echo "$opt" | awk '{print substr($1, 2)}')
      else
        opt=$first_chr
      fi
      printf 'Error: Unknown option: "%s"\n' "$opt" >&2
      print_usage
      exit 1
      ;;

  esac

  if [ "$long_option" = T ]; then

    # if we had a long option then we are going to get a new block next
    shift
    opt=

  else

    # if we had a short option then just move to the next character
    opt=$(echo "$opt" | awk '{print substr($1, 2)}')

    # if block is now empty then shift to the next one
    [ "$opt" ] || shift

  fi

done

echo "Doing something..."

exit 0

Przykładowe użycie można znaleźć w poniższych przykładach.

Pozycja opcji z argumentami

Jeśli ma to wartość, opcje z argumentami nie są ostatnie (muszą być tylko długie opcje). Tak więc podczas gdy np. w tar (przynajmniej w niektórych implementacjach) opcje f muszą być ostatnie, ponieważ następuje nazwa pliku (tar xzf bar.tar.gz działa, ale tar xfz bar.tar.gz nie) tutaj tak nie jest (patrz późniejsze przykłady).

Wiele opcji z argumentami

Jako kolejny bonus parametry opcji są zużywane w kolejności opcji przez parametry z wymaganymi opcjami. Wystarczy spojrzeć na wynik mojego skryptu tutaj z linii poleceń abc X Y Z (lub -abc X Y Z):

Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!

Długie opcje również konkatenowane

Możesz również mieć długie opcje w bloku opcji, biorąc pod uwagę, że występują one ostatnio w bloku. Tak więc następujące linie komend są równoważne (łącznie z kolejnością przetwarzania opcji i ich argumentów):

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

Wszystko to prowadzi do:

Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...

Nie w tym roztworze

Argumenty opcjonalne

Opcje z opcjonalnymi argumentami powinny być możliwe przy odrobinie pracy, np. patrząc forward, czy istnieje blok bez myślnika; użytkownik musi wtedy umieścić myślnik przed każdym blokiem po bloku z parametrem o parametrze opcjonalnym. Być może jest to zbyt skomplikowane, aby komunikować się z użytkownikiem, więc lepiej w tym przypadku całkowicie wymagać wiodącego myślnika.

Sprawy stają się jeszcze bardziej skomplikowane z wieloma możliwymi parametrami. Ja bym odradzał dokonywanie opcji starając się być mądrym przez określenie, czy argument a może być za to lub nie (np. z opcją przyjmuje tylko liczbę jako opcjonalny argument), ponieważ może się to zepsuć w przyszłości.

Osobiście preferuję dodatkowe opcje zamiast opcjonalnych argumentów.

Argumenty opcji wprowadzone znakiem równości

Tak jak z opcjonalnymi argumentami nie jestem fanem tego (BTW, czy istnieje wątek do dyskusji na plusy / minusy różnych stylów parametrów?) ale jeśli chcesz to prawdopodobnie możesz wdrożyć to samodzielnie tak jak to robiono w http://mywiki.wooledge.org/BashFAQ/035#Manual_loop ze stwierdzeniem przypadku --long-with-arg=?*, a następnie usunięciem znaku równości (jest to BTW strona, która mówi, że tworzenie konkatenacji parametru jest możliwe przy pewnym wysiłku, ale "zostawiono [to] jako ćwiczenie dla czytelnika", co zmusiło mnie do wzięcia ich na słowo, ale zacząłem od zera).

Inne uwagi

Zgodny z POSIX, działa nawet na starożytnych konfiguracjach Busybox, z którymi miałem do czynienia (np. cut, head oraz getopts brak).

 1
Author: phk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:36

Rozwiązanie, które zachowuje nieobsługiwane argumenty. W Zestawie Demo.

Oto moje rozwiązanie. Jest bardzo elastyczny i w przeciwieństwie do innych, nie powinien wymagać zewnętrznych pakietów i radzić sobie z pozostawionymi argumentami.

Użycie to: ./myscript -flag flagvariable -otherflag flagvar2

Wystarczy edytować linię validflags. Dodaje myślnik i przeszukuje wszystkie argumenty. Następnie definiuje następny argument jako nazwę flagi, np.

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

Główny kod (wersja skrócona, z przykładami dalej w dół, także wersja z błędami):

#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers

Wersja słowna z wbudowanymi demami echo:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
#   argval=$(echo $@ | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1: $1 arg2: $2

echo leftovers: $leftovers
echo rate $rate time $time number $number

Ostatni, ten błąd, jeśli niepoprawny argument jest przekazywany.

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
do
    argval=$1
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=$2
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers

Plusy: to, co robi, radzi sobie bardzo dobrze. Zachowuje nieużywane argumenty, których wiele innych rozwiązań tutaj nie ma. pozwala również na wywołanie zmiennych bez ręcznego definiowania w skrypcie. Pozwala również na prepopulację zmiennych, jeśli nie podano odpowiedniego argumentu. (Patrz verbose example).

Wady: nie można przetworzyć pojedynczego złożonego ciągu arg, np.-xcvf przetworzyłby jako pojedynczy argument. Możesz nieco łatwo napisać dodatkowy kod do mojego, który dodaje tę funkcjonalność.

 1
Author: Noah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-29 04:07:33

Najlepsza odpowiedź na to pytanie wydawała mi się trochę błędna, kiedy próbowałem go -- oto moje rozwiązanie, które okazało się być bardziej wytrzymałe:

boolean_arg=""
arg_with_value=""

while [[ $# -gt 0 ]]
do
key="$1"
case $key in
    -b|--boolean-arg)
    boolean_arg=true
    shift
    ;;
    -a|--arg-with-value)
    arg_with_value="$2"
    shift
    shift
    ;;
    -*)
    echo "Unknown option: $1"
    exit 1
    ;;
    *)
    arg_num=$(( $arg_num + 1 ))
    case $arg_num in
        1)
        first_normal_arg="$1"
        shift
        ;;
        2)
        second_normal_arg="$1"
        shift
        ;;
        *)
        bad_args=TRUE
    esac
    ;;
esac
done

# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
    echo "first_normal_arg: $first_normal_arg"
    echo "second_normal_arg: $second_normal_arg"
    echo "boolean_arg: $boolean_arg"
    echo "arg_with_value: $arg_with_value"
    exit 0
fi

if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
    echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
    exit 1
fi
 1
Author: Daniel Bigham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-01 12:09:07

Ten przykład pokazuje, jak używać getopt i eval oraz HEREDOC i shift do obsługi krótkich i długich parametrów z wymaganą wartością i bez niej. Również instrukcja switch / case jest zwięzła i łatwa do naśladowania.

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, don't change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<-EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below

Najważniejsze linie powyższego skryptu to:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Krótki, do rzeczy, czytelny i obsługuje prawie wszystko (IMHO).

Mam nadzieję, że to komuś pomoże.
 1
Author: phyatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-07 18:25:44

I have write a bash helper to write a nice bash tool

Projekt domu: https://gitlab.mbedsys.org/mbedsys/bashopts

Przykład:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "$@"

# Process argument
bashopts_process_args

Da pomoc:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

Enjoy:)

 1
Author: Emeric Verschuur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-20 21:30:19

Oto moje podejście - użycie wyrażenia regularnego.

  • no getopts
  • obsługuje blok krótkich parametrów -qwerty
  • obsługuje krótkie parametry -q -w -e
  • obsługuje długie opcje --qwerty
  • możesz przekazać atrybut do opcji krótkiej lub długiej (jeśli używasz bloku krótkich opcji, atrybut jest dołączony do ostatniej opcji)
  • możesz użyć spacji lub = do podania atrybutów, ale atrybuty pasują do siebie aż do napotkania myślnika+spacja "ogranicznik", więc w --q=qwe ty qwe ty jest jednym atrybutem
  • obsługuje mieszanie wszystkich powyższych, więc {[7] } jest poprawne

Skrypt:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
options=$@

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done
 1
Author: a_z,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-17 16:48:58

Oto moje ulepszone rozwiązanie odpowiedzi Bruno Bronosky ' ego za pomocą zmiennych tablic.

Pozwala na mieszanie pozycji parametrów i daje tablicę parametrów zachowującą kolejność bez opcji

#!/bin/bash

echo $@

PARAMS=()
SOFT=0
SKIP=()
for i in "$@"
do
case $i in
    -n=*|--skip=*)
    SKIP+=("${i#*=}")
    ;;
    -s|--soft)
    SOFT=1
    ;;
    *)
        # unknown option
        PARAMS+=("$i")
    ;;
esac
done
echo "SKIP            = ${SKIP[@]}"
echo "SOFT            = $SOFT"
    echo "Parameters:"
    echo ${PARAMS[@]}

Wyświetli na przykład:

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP            = .c .obj
SOFT            = 1
Parameters:
parameter somefile
 1
Author: Masadow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-05 09:17:12

Chcę zgłosić swój projekt: https://github.com/flyingangel/argparser

source argparser.sh
parse_args "$@"
To proste. Środowisko będzie wypełnione zmiennymi o tej samej nazwie co argumenty
 0
Author: Thanh Trung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-16 17:45:50