2542 Stimmen

Wie analysiere ich Kommandozeilenargumente in der Bash?

Angenommen, ich habe ein Skript, das mit dieser Zeile aufgerufen wird:

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

oder dieses:

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

Wie kann man dies so interpretieren, dass in jedem Fall (oder einer Kombination von beiden) $v , $f y $d werden alle auf true y $outFile wird gleich sein mit /fizz/someOtherFile ?

1 Stimmen

Für zsh-Benutzer gibt es ein großartiges builtin namens zparseopts, das das kann: zparseopts -D -E -M -- d=debug -debug=d Und haben beide -d y --debug im $debug Array echo $+debug[1] gibt 0 oder 1 zurück, wenn einer dieser Werte verwendet wird. Ref: zsh.org/mla/users/2011/msg00350.html

2 Stimmen

Ein wirklich gutes Tutorial: linuxcommand.org/lc3_wss0120.php . Besonders gut gefällt mir das Beispiel "Befehlszeilenoptionen".

0 Stimmen

Ich habe ein Skript erstellt, das dies für Sie erledigt, es heißt - github.com/unfor19/bargs

7voto

tmoschou Punkte 855

Es gibt verschiedene Möglichkeiten, cmdline-Args zu parsen (z.B. GNU getopt (nicht portabel) vs. BSD (MacOS) getopt vs. getopts) - alle problematisch. Diese Lösung

  • ist tragbar!
  • hat keine Abhängigkeiten, verlässt sich nur auf die Bash-Build-Ins
  • ermöglicht sowohl kurze als auch lange Optionen
  • Leerzeichen behandelt oder gleichzeitig die Verwendung von = Trennzeichen zwischen Option und Argument
  • unterstützt verkettete Kurzoptionen -vxf
  • behandelt Optionen mit optionalen Argumenten (z.B. --color gegen --color=always ),
  • erkennt und meldet unbekannte Optionen korrekt
  • unterstützt -- um das Ende der Optionen zu signalisieren, und
  • im Vergleich zu alternativen Lösungen für dieselben Funktionen keinen aufgeblähten Code erfordert. D.h. kurz und bündig und daher leichter zu pflegen

Beispiele: Jede der

# flag
-f
--foo

# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"

# option with optional argument
--baz
--baz="Optional Hello"

#!/usr/bin/env bash

usage() {
  cat - >&2 <<EOF
NAME
    program-name.sh - Brief description

SYNOPSIS
    program-name.sh [-h|--help]
    program-name.sh [-f|--foo]
                    [-b|--bar <arg>]
                    [--baz[=<arg>]]
                    [--]
                    FILE ...

REQUIRED ARGUMENTS
  FILE ...
          input files

OPTIONS
  -h, --help
          Prints this and exits

  -f, --foo
          A flag option

  -b, --bar <arg>
          Option requiring an argument <arg>

  --baz[=<arg>]
          Option that has an optional argument <arg>. If <arg>
          is not specified, defaults to 'DEFAULT'
  --     
          Specify end of options; useful if the first non option
          argument starts with a hyphen

EOF
}

fatal() {
    for i; do
        echo -e "${i}" >&2
    done
    exit 1
}

# For long option processing
next_arg() {
    if [[ $OPTARG == *=* ]]; then
        # for cases like '--opt=arg'
        OPTARG="${OPTARG#*=}"
    else
        # for cases like '--opt arg'
        OPTARG="${args[$OPTIND]}"
        OPTIND=$((OPTIND + 1))
    fi
}

# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "$@")  # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
    case "$optchar" in
        h) usage; exit 0 ;;
        f) foo=1 ;;
        b) bar="$OPTARG" ;;
        -) # long option processing
            case "$OPTARG" in
                help)
                    usage; exit 0 ;;
                foo)
                    foo=1 ;;
                bar|bar=*) next_arg
                    bar="$OPTARG" ;;
                baz)
                    baz=DEFAULT ;;
                baz=*) next_arg
                    baz="$OPTARG" ;;
                -) break ;;
                *) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
            esac
            ;;
        *) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
    esac
done

shift $((OPTIND-1))

if [ "$#" -eq 0 ]; then
    fatal "Expected at least one required argument FILE" \
    "See '${0} --help' for usage"
fi

echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"

7voto

akostadinov Punkte 16114

Dies ist, wie ich in einer Funktion zu tun, um zu vermeiden, brechen getopts laufen zur gleichen Zeit irgendwo höher im Stapel:

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
...
}

5voto

Lösung, die unbehandelte Argumente beibehält. Enthaltene Demos.

Hier ist meine Lösung. Sie ist SEHR flexibel und sollte im Gegensatz zu anderen keine externen Pakete benötigen und sauber mit übrig gebliebenen Argumenten umgehen.

Die Verwendung ist: ./myscript -flag flagvariable -otherflag flagvar2

Dazu müssen Sie lediglich die Zeile validflags bearbeiten. Es wird ein Bindestrich vorangestellt und alle Argumente werden durchsucht. Dann wird das nächste Argument als Name des Flags definiert, z.B.

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

Der Hauptcode (kurze Version, ausführliche Version mit Beispielen weiter unten, auch eine Version mit Fehlermeldungen):

#!/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

Die ausführliche Version mit eingebauten Echo-Demos:

#!/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

Der letzte, der fehlschlägt, wenn ein ungültiges -Argument durchgereicht wird.

#!/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

Profis: Was es tut, erledigt es sehr gut. Unbenutzte Argumente bleiben erhalten, was bei vielen anderen Lösungen hier nicht der Fall ist. Es ermöglicht auch den Aufruf von Variablen, ohne dass diese von Hand im Skript definiert werden müssen. Es erlaubt auch die Vorbelegung von Variablen, wenn kein entsprechendes Argument angegeben wird. (Siehe ausführliches Beispiel).

Nachteile: Kann keine einzelnen komplexen Argumente parsen, z.B. würde -xcvf als einzelnes Argument verarbeitet. Man könnte ziemlich leicht zusätzlichen Code in mein Programm schreiben, der diese Funktionalität hinzufügt.

4voto

a_z Punkte 123

Hier ist mein Ansatz - mit regexp.

  • keine Getopts
  • er verarbeitet Blöcke mit kurzen Parametern -qwerty
  • es behandelt kurze Parameter -q -w -e
  • es behandelt lange Optionen --qwerty
  • Sie können Attribute an kurze oder lange Optionen übergeben (bei einem Block von kurzen Optionen wird das Attribut an die letzte Option angehängt)
  • können Sie Leerzeichen verwenden oder = um Attribute zu liefern, aber das Attribut stimmt überein, bis es auf den Bindestrich+Leerzeichen-"Begrenzer" trifft, also in --q=qwe ty qwe ty ist ein Attribut
  • er beherrscht eine Mischung aus allen oben genannten Aspekten -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute ist gültig

Drehbuch:

#!/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

0 Stimmen

Wie dieses hier. Vielleicht fügen Sie einfach den Parameter -e zum Echo mit neuer Zeile hinzu.

3voto

John Punkte 393

Angenommen, wir erstellen ein Shell-Skript namens test_args.sh wie folgt

#!/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"

Nachdem wir den folgenden Befehl ausgeführt haben:

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

Die Ausgabe würde lauten:

year=2017 month=12 day=22 flag=true

6 Stimmen

Dies ist der gleiche Ansatz wie Noahs Antwort , hat aber weniger Sicherheitskontrollen / Schutzmaßnahmen. Dies ermöglicht es uns, beliebige Argumente in die Skriptumgebung zu schreiben, und ich bin mir ziemlich sicher, dass Ihre Verwendung von eval hier eine Befehlseinschleusung ermöglichen kann.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X