Comment puis-je analyser les arguments en ligne de commande dans Bash?
Dites, j'ai un script qui est appelé avec cette ligne:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
ou celui-ci:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
Quelle est la façon acceptée de l'analyser de telle sorte que dans chaque cas (ou une combinaison des deux) $v
, $f
, et $d
seront tous réglés à true
et $outFile
seront égaux à /fizz/someOtherFile
?
29 réponses
la Méthode #1: Utilisation de bash sans getopt[s]
deux façons courantes de passer les arguments clé-valeur-paire sont:
Espace Bash séparé (p.ex., --option argument
) (sans getopt[s])
Utilisation ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts
#!/bin/bash
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key=""
case $key in
-e|--extension)
EXTENSION=""
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH=""
shift # past argument
shift # past value
;;
-l|--lib)
LIBPATH=""
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
*) # unknown option
POSITIONAL+=("") # 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 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 ""
fi
Bash égal-séparé (e.g., --option=argument
) (sans getopt[s])
Utilisation ./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 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1
fi
pour mieux comprendre ${i#*=}
rechercher" enlèvement de substrat "dans ce guide . Il est fonctionnellement équivalent à `sed 's/[^=]*=//' <<< "$i"`
qui appelle un sous-processus inutile ou `echo "$i" | sed 's/[^=]*=//'`
qui appelle deux sous-processus inutiles.
la Méthode #2: Utilisation de bash avec getopt[s]
de: http://mywiki.wooledge.org/BashFAQ/035#getopts
getopt(1) limitations (anciennes, relativement récentes getopt
versions):
- ne peut pas gérer des arguments qui sont des chaînes vides
- ne peut pas gérer les arguments avec des espaces encastrés
les versions plus récentes getopt
n'ont pas ces limitations.
de plus, le shell POSIX (et d'autres) offre getopts
qui n'a pas ces limitations. Voici une exemple simpliste getopts
:
#!/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
les avantages de getopts
sont:
- c'est plus portable, et fonctionnera dans d'autres coquillages comme
dash
. - il peut gérer plusieurs options simples comme
-vf filename
de la manière Unix typique, automatiquement.
l'inconvénient de getopts
est qu'il ne peut traiter que des options courtes ( -h
, Non --help
) sans code additionnel.
il y a un tutoriel getopts qui explique ce que signifie toute la syntaxe et les variables. À bash, il y a aussi help getopts
, ce qui pourrait être instructif.
Pas de réponse mentionne "1519170920 renforcée" getopt . Et le top-voté réponse est trompeuse: Il ignore -vfd
style court d'options (demandé par l'OP), les options d'après les arguments de position (également à la demande de l'OP) et il ignore l'analyse d'erreurs. À la place:
- une meilleure Utilisation des
getopt
de util-linux ou anciennement GNU glibc . 1 - il fonctionne avec
getopt_long()
la fonction C de GNU glibc. - A tous utile les traits distinctifs (les autres n'ont pas eux):
- gère les espaces, en citant des caractères et même des binaires dans les arguments 2
- il peut gérer les options à la fin:
script.sh -o outFile file1 file2 -v
- permet
=
- style longues options:script.sh --outfile=fileOut --infile fileIn
- est déjà si vieux 3 qu'aucun système GNU ne manque cela (par exemple, tout Linux L'a).
- vous pouvez tester son existence avec:
getopt --test
→ valeur de retour 4. - Autres
getopt
ou shell builtingetopts
sont d'une utilité limitée.
Les appels suivants
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
tous de retour
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
avec la mention 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 ""151920920"" -- "$@")
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 "" in
-d|--debug)
d=y
shift
;;
-f|--force)
f=y
shift
;;
-v|--verbose)
v=y
shift
;;
-o|--output)
outFile=""
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
;;
esac
done
# handle non-option arguments
if [[ $# -ne 1 ]]; then
echo ""151920920": A single input file is required."
exit 4
fi
echo "verbose: $v, force: $f, debug: $d, in: , out: $outFile"
1 getopt amélioré est disponible sur la plupart des "bash-systems", y compris Cygwin; sur OS X essayer brew installer gnu-getopt ou sudo port install getopt
2 les conventions POSIX exec()
n'ont aucun moyen fiable de passer binaire NULL dans les arguments de ligne de commande; ces octets mettent prématurément fin à l'argument
3 first version released in 1997 or before (I only tracked it back to 1997)
de: digitalpeer.com avec modifications mineures
Utilisation 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}
Pour mieux comprendre ${i#*=}
rechercher "sous-Chaîne de Suppression de" dans de ce guide", 151980920" . Il est fonctionnellement équivalent à `sed 's/[^=]*=//' <<< "$i"`
qui appelle un sous-processus inutile ou `echo "$i" | sed 's/[^=]*=//'`
qui appelle deux sous-processus inutiles.
getopt()
/ getopts()
c'est une bonne option. Volé de ici :
l'utilisation simple de "getopt" est montrée dans ce mini-script:
#!/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
ce que nous avons dit, -b, -c ou-d sera autorisé, mais que c est suivi par un argument (le "c:" dit que).
si nous appelons cela " g "et l'essayons:
bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
nous commençons par deux arguments, et "getopt" sépare les options et met chacun dans son propre argument. Il a également ajouter."--"
au risque d'ajouter un autre exemple à ignorer, voici mon plan.
- poignées
-n arg
et--name=arg
- permet des arguments à la fin
- montre sane erreurs si quelque chose est mal orthographié
- compatible, n'utilise pas de bashismes
- lisible, ne nécessite pas de maintenir l'état dans une boucle
Espère que c'est utile à quelqu'un.
while [ "$#" -gt 0 ]; do
case "" in
-n) name=""; shift 2;;
-p) pidfile=""; shift 2;;
-l) logfile=""; shift 2;;
--name=*) name="${1#*=}"; shift 1;;
--pidfile=*) pidfile="${1#*=}"; shift 1;;
--logfile=*) logfile="${1#*=}"; shift 1;;
--name|--pidfile|--logfile) echo " requires an argument" >&2; exit 1;;
-*) echo "unknown option: " >&2; exit 1;;
*) handle_argument ""; shift 1;;
esac
done
de façon plus succincte
script.sh
#!/bin/bash
while [[ "$#" > 0 ]]; do case in
-d|--deploy) deploy=""; shift;;
-u|--uglify) uglify=1;;
*) echo "Unknown parameter passed: "; exit 1;;
esac; shift; done
echo "Should deploy? $deploy"
echo "Should uglify? $uglify"
Utilisation:
./script.sh -d dev -u
# OR:
./script.sh --deploy dev --uglify
je suis environ 4 ans en retard à cette question, mais je veux donner en retour. J'ai utilisé les réponses précédentes comme point de départ pour ranger mon vieux param adhoc. J'ai ensuite remanié le code de modèle suivant. Il gère à la fois les params longs et courts, en utilisant des arguments séparés par = ou par l'espace, ainsi que plusieurs params courts regroupés. Enfin, il ré-insère tous les arguments non-param dans le $1,$2.. variable. J'espère que c'est utile.
#!/usr/bin/env bash
# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash "151900920" $@ ; 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 "" ]; do
# Copy so we can modify it (can't modify )
OPT=""
# 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=""
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 ( ..)
eval set -- $REMAINS
echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done
ma réponse est largement basée sur la réponse de Bruno Bronosky , mais j'ai en quelque sorte masqué ses deux implémentations de pur bash en une que j'utilise assez fréquemment.
# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
key=""
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=""
;;
# 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
cela vous permet d'avoir à la fois des options/valeurs séparées par l'espace, ainsi que des valeurs définies égales.
pour que vous puissiez exécuter votre script en utilisant:
./myscript --foo -b -o /fizz/file.txt
ainsi que:
./myscript -f --bar -o=/fizz/file.txt
et les deux devrait avoir le même résultat final.
PROS:
-
permet à la fois-arg=valeur et-arg valeur
-
fonctionne avec n'importe quel nom arg que vous pouvez utiliser dans bash
- Sens -ou -arg ou arg ou -a-r-g ou que ce soit
-
Pur bash. Pas besoin d'apprendre /utiliser getopt ou getopts
CONS:
-
ne Peut pas combiner args
- qui signifie non-abc. Vous devez faire-a-b-c
ce sont les seuls avantages/inconvénients que je peux penser à du haut de ma tête
j'ai trouvé la question d'écrire l'analyse portable dans les scripts tellement frustrant que j'ai écrit Argbash - un générateur de code de logiciel libre qui peut générer les arguments-l'analyse du code pour votre script plus il a quelques belles fonctionnalités:
je pense que celui-ci est assez simple à utiliser:
#!/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
l'Invocation exemple:
./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v
OPT:d
OPT:o OPTARG:/fizz/someOtherFile
OPT:f
ARG:./foo/bar/someFile
développant l'excellente réponse de @guneysus, voici un tweak qui permet à l'utilisateur d'utiliser la syntaxe qu'il préfère, par exemple
command -x=myfilename.ext --another_switch
vs
command -x myfilename.ext --another_switch
C'est-à-dire que les égaux peuvent être remplacés par des espaces.
cette "interprétation floue" pourrait ne pas être à votre goût, mais si vous faites des scripts qui sont interchangeables avec d'autres utilitaires (comme c'est le cas avec le mien, qui doit fonctionner avec ffmpeg), le la flexibilité est utile.
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
getopts fonctionne très bien si #1 vous l'avez installé et #2 vous avez l'intention de l'exécuter sur la même plate-forme. OSX et Linux (par exemple) se comportent différemment à cet égard.
Voici une solution (non getopts) qui supporte les drapeaux equals, non-equals et booléen. Par exemple, vous pouvez exécuter votre script de cette façon:
./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
je vous donne la fonction parse_params
qui analysera les paramètres de la ligne de commande.
- C'est un pur Bash solution, pas d'outils supplémentaires.
- ne pollue pas la portée mondiale.
- vous renvoie facilement des variables simples à utiliser, sur lesquelles vous pouvez construire plus de logique.
- le nombre de tirets avant les params n'a pas d'importance (
--all
égale-all
égaleall=all
)
le script ci-dessous est une démonstration de travail de copier-coller. Voir show_use
pour savoir comment utiliser parse_params
.
Limitations:
- ne supporte pas l'espace délimité par les params (
-d 1
) - Param noms de perdre des tirets donc
--any-param
et-anyparam
est l'équivalent de -
eval $(parse_params "$@")
doit être utilisé à l'intérieur de bash fonction (il ne fonctionnera pas dans la portée globale)
#!/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 [[ "" != "" ]]; do
# Escape asterisk to prevent bash asterisk expansion
_escaped=${1/\*/\'\"*\"\'}
# If equals delimited named parameter
if [[ "" =~ ^..*=..* ]]; then
# Add to named parameters array
echo "ARGN+=('$_escaped');"
# key is part before first =
local _key=$(echo "" | 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 [[ "" =~ ^\-. ]]; 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
C'est ce que je fais dans une fonction pour éviter de casser getopts exécuter en même temps quelque part plus haut dans la pile:
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
...
}
EasyOptions , ne nécessite pas d'analyse:
## 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
j'aimerais offrir ma version de l'option parsing, qui permet ce qui suit:
-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello
permet aussi ceci (pourrait être indésirable):
-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder
vous devez décider avant d'utiliser si = doit être utilisé sur une option ou non. Ceci est pour garder le code propre (ish).
while [[ $# > 0 ]]
do
key=""
while [[ ${key+x} ]]
do
case $key in
-s*|--stage)
STAGE=""
shift # option has parameter
;;
-w*|--workfolder)
workfolder=""
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
Note que getopt(1)
était une courte erreur de vie de AT & T.
getopt a été créé en 1984 mais déjà enterré en 1986, parce qu'il n'était pas vraiment utilisable.
une preuve du fait que getopt
est très dépassé est que la page de manuel getopt(1)
mentionne toujours "$*"
au lieu de "$@"
, qui a été ajouté au shell Bourne en 1986 avec le getopts(1)
shell builtin afin de traiter les arguments avec les espaces à l'intérieur.
BTW: si vous êtes intéressé par l'analyse des options longues dans les scripts shell, il peut être intéressant de savoir que l'implémentation getopt(3)
de libc (Solaris) et ksh93
ont toutes deux ajouté une implémentation uniforme des options longues qui supporte les options longues comme Alias pour les options courtes. Cela entraîne ksh93
et le Bourne Shell
à mettre en œuvre une interface uniforme pour les options longues via getopts
.
un exemple pour les options longues tiré de la page de Bourne Shell man:
getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"
montre combien de temps les alias d'options peuvent être utilisés dans Bourne Shell et ksh93.
Voir la page de man de un récent Bourne Shell:
http://schillix.sourceforge.net/man/man1/bosh.1.html
et la page de manuel pour getopt (3) de OpenSolaris:
http://schillix.sourceforge.net/man/man3c/getopt.3c.html
et le dernier, le getopt(1) page de manuel pour vérifier le obsolètes $*:
Mélange de position et indicateur d'arguments fondés sur la
--param=arg (=délimité)
mélangeant librement les drapeaux entre les arguments de position:
./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d
peut être accompli avec une approche assez concise:
# 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=
ip_address=
--param arg (espace délimité)
il est généralement plus clair de ne pas mélanger les styles --flag=value
et --flag value
.
./script.sh dumbo 127.0.0.1 --environment production -q -d
C'est un peu risqué à lire, mais est toujours valable
./script.sh dumbo --environment production 127.0.0.1 --quiet -d
Source
# 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=
ip_address=
supposons que nous créons un script shell nommé test_args.sh
comme suit
#!/bin/sh
until [ $# -eq 0 ]
do
name=${1:1}; shift;
if [[ -z "" || == -* ]] ; then eval "export $name=true"; else eval "export $name="; shift; fi
done
echo "year=$year month=$month day=$day flag=$flag"
après avoir exécuté la commande suivante:
sh test_args.sh -year 2017 -flag -month 12 -day 22
la sortie serait:
year=2017 month=12 day=22 flag=true
Utiliser le module "arguments" de bash-modules
exemple:
#!/bin/bash
. import.sh log arguments
NAME="world"
parse_arguments "-n|--name)NAME;S" -- "$@" || {
error "Cannot parse command line."
exit 1
}
info "Hello, $NAME!"
cela aussi pourrait être utile de savoir, vous pouvez définir une valeur et si quelqu'un fournit une entrée, outrepasser la valeur par défaut avec cette valeur..
myscript.sh -F./ serverlist.txt ou tout simplement ./myscript.sh (et il prend par défaut)
#!/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=""
shift
case $key in
-i|--inputlist)
SERVER_FILE_LIST=""
shift
;;
esac
done
echo "SERVER LIST = ${SERVER_FILE_LIST}"
une Autre solution sans getopt[s], POSIX, vieux style Unix
similaire à la solution Bruno Bronosky posté ceci ici est un sans l'utilisation de getopt(s)
.
la principale caractéristique différentielle de ma solution est qu'elle permet d'avoir des options concaténées ensemble tout comme tar -xzf foo.tar.gz
est égal à tar -x -z -f foo.tar.gz
. Et comme dans tar
, ps
etc. le principal trait d'union est facultatif bloc d'options courtes (mais cela peut être changé facilement). Les options longues sont également supportées (mais quand un bloc commence avec un, alors deux traits d'Union principaux sont requis).
Code avec options d'exemple
#!/bin/sh
echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"
echo
print_usage() {
echo "Usage:
"151900920" {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 "" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)//')
fi
# get the first character -> check whether long option
first_chr=$(echo "$opt" | awk '{print substr(, 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 [ "" ]; then
echo "Option BBB with argument '' activated!"
shift
else
echo "BBB parameters incomplete!" >&2
print_usage
exit 1
fi
;;
c*|-ccc-2-args)
if [ "" ] && [ "" ]; then
echo "Option CCC with arguments '' and '' 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(, 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(, 2)}')
# if block is now empty then shift to the next one
[ "$opt" ] || shift
fi
done
echo "Doing something..."
exit 0
pour l'exemple d'utilisation, voir les exemples ci-dessous.
Position d'options avec des arguments
pour ce que sa valeur là les options avec des arguments ne sont pas le dernier (seulement longue options doivent être). Alors que par exemple dans tar
(au moins dans certaines implémentations) les options f
doivent être dernières parce que le nom du fichier suit ( tar xzf bar.tar.gz
fonctionne mais tar xfz bar.tar.gz
ne fonctionne pas) ce n'est pas le cas ici (voir les exemples plus loin).
Options multiples avec arguments
comme autre bonus les paramètres de l'option sont consommés dans l'ordre des options par les paramètres avec les options requises. Il suffit de regarder la sortie de mon script ici avec la ligne de commande abc X Y Z
(ou -abc X Y Z
):
Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!
Longue options concaténés
vous pouvez aussi avoir de longues options dans le bloc d'options étant donné qu'elles se produisent en dernier dans le bloc. Ainsi, les lignes de commande suivantes sont toutes équivalentes (y compris l'ordre dans lequel les options et ses arguments sont traités):
-
-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
tout cela conduit à:
Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...
pas dans cette solution
arguments optionnels
Les options avec des arguments optionnels devraient être possibles avec un peu de travail, par exemple en regardant vers l'avenir s'il y a un bloc sans trait d'Union; l'utilisateur devrait alors mettre un trait d'Union devant chaque bloc suivant un bloc avec un paramètre ayant un paramètre optionnel. Peut-être que c'est trop compliqué de communiquer à l'utilisateur alors mieux vaut juste exiger un trait d'Union tout à fait dans ce cas.
les Choses deviennent encore plus compliquées avec plusieurs paramètres. Je voudrais conseillez de ne pas faire les options en essayant d'être intelligent en déterminant si un argument pourrait être pour elle ou non (par exemple avec une option prend juste un nombre comme un argument optionnel) parce que cela pourrait casser dans le futur.
personnellement, je préfère les options supplémentaires aux arguments optionnels.
arguments D'Option introduits avec un signe égal
comme avec les arguments optionnels Je ne suis pas un fan de ceci (BTW, est-il un fil de discussion sur les avantages et les inconvénients des différents styles de paramètres?) mais si vous voulez cela, vous pouvez probablement le mettre en œuvre vous-même comme fait à http://mywiki.wooledge.org/BashFAQ/035#Manual_loop avec une déclaration de cas --long-with-arg=?*
et ensuite enlever le signe égal (C'est BTW le site qui dit Que faire la concaténation de paramètre est possible avec un certain effort mais "laissé [il] comme un exercice pour le lecteur" qui m'a fait les prendre à leur mot mais j'ai commencé à zéro.)
autres notes
conforme à POSIX, fonctionne même sur les anciennes configurations Busybox que j'ai eu à traiter (avec par exemple cut
, head
et getopts
manquant).
Solution qui préserve les arguments sans entrave. Démos Incluses.
voici ma solution. Il est très flexible et contrairement à d'autres, ne devrait pas exiger de paquets externes et traite les arguments restants proprement.
Usage: ./myscript -flag flagvariable -otherflag flagvar2
tout ce que vous avez à faire est d'éditer la ligne validflags. Il prépare un trait d'Union et recherche tous les arguments. Il définit ensuite l'argument suivant comme le nom du drapeau par exemple
./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2
Le code principal (version courte, détaillé avec des exemples plus bas, aussi une version avec erroring arrière):
#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
do
match=0
argval=
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=
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
La version détaillé avec echo démos:
#!/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=
# argval=$(echo $@ | cut -d ' ' -f$count)
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=
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: arg2:
echo leftovers: $leftovers
echo rate $rate time $time number $number
Final, est ce une erreur si un invalide-argument est passé à travers.
#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
do
argval=
match=0
if [ "${argval:0:1}" == "-" ]
then
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=
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
pour: ce qu'il fait, il gère très bien. Il préserve les arguments inutilisés que beaucoup d'autres solutions ici ne font pas. Il permet également variables à appeler sans être définies à la main dans le script. Il permet également la pré-population des variables si aucun argument correspondant n'est donné. (Voir verbose exemple).
Cons: ne peut pas analyser une seule chaîne arg complexe, par exemple-xcvf traiterait comme un seul argument. Vous pouvez un peu facilement écrire du code supplémentaire dans la mine qui ajoute cette fonctionnalité.
la meilleure réponse à cette question semblait un peu buggée quand je l'ai essayée -- voici ma solution que j'ai trouvé pour être plus robuste:
boolean_arg=""
arg_with_value=""
while [[ $# -gt 0 ]]
do
key=""
case $key in
-b|--boolean-arg)
boolean_arg=true
shift
;;
-a|--arg-with-value)
arg_with_value=""
shift
shift
;;
-*)
echo "Unknown option: "
exit 1
;;
*)
arg_num=$(( $arg_num + 1 ))
case $arg_num in
1)
first_normal_arg=""
shift
;;
2)
second_normal_arg=""
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 ""151900920"") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
exit 1
fi
cet exemple montre comment utiliser getopt
et eval
et HEREDOC
et shift
pour traiter des paramètres courts et longs avec et sans une valeur requise qui suit. De plus, la déclaration de cas/commutateur est concise et facile à suivre.
#!/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 "151900920")
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:\"\" $2:\"\""
case "" in
-h | --help ) usage; exit; ;;
-n | --num ) num_str=""; shift 2 ;;
-t | --time ) time_str=""; 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
les lignes les plus significatives du script ci-dessus sont:
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 "" in
-h | --help ) usage; exit; ;;
-n | --num ) num_str=""; shift 2 ;;
-t | --time ) time_str=""; shift 2 ;;
--dry-run ) dryrun=1; shift ;;
-v | --verbose ) verbose=$((verbose + 1)); shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
Court, précis, lisible, et peut gérer à peu près tout (à mon humble avis).
Espère que ça aide quelqu'un.
je dois écrire un bash de l'aide pour écrire une belle bash outil
projet d'accueil: https://gitlab.mbedsys.org/mbedsys/bashopts
exemple:
#!/bin/bash -ei
# load the library
. bashopts.sh
# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR
# Initialize the library
bashopts_setup -n ""151900920"" -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
aidera:
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:)
Voici mon approche-en utilisant regexp.
- pas de getopts
- il gère bloc de paramètres courts
-qwerty
- il gère court paramètres
-q -w -e
- poignées "options de 151930920"
- vous pouvez passer l'attribut à l'option courte ou longue (si vous utilisez le bloc d'options courtes, l'attribut est attaché à la dernière option)
- vous pouvez utiliser des espaces ou
=
pour fournir des attributs, mais les correspondances d'attribut jusqu'à la rencontre tiret+espace "délimiter", donc dans--q=qwe ty
qwe ty
est un attribut - il gère le mélange de tout ce qui précède ainsi
-o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute
est valide
script:
#!/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
voici ma solution améliorée de la réponse de Bruno Bronosky en utilisant des tableaux variables.
il vous permet de mélanger la position des paramètres et de vous donner un tableau de paramètres préserver l'ordre sans les options
#!/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[@]}
produira par exemple:
$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP = .c .obj
SOFT = 1
Parameters:
parameter somefile
je veux soumettre mon projet: https://github.com/flyingangel/argparser
source argparser.sh
parse_args "$@"
C'est aussi Simple que ça. L'environnement sera rempli de variables avec le même nom que les arguments