bash - ويندوز - نظام تشغيل يعتمد على سطر الأوامر command line interface
كيف يمكنني تحليل حجج سطر الأوامر في Bash؟ (19)
قل ، لدي برنامج نصي يتم استدعاؤه باستخدام هذا الخط:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
ما سر جديدة هذا:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
ما هي الطريقة المقبولة لتحليل هذا بحيث في كل حالة (أو مزيج من الاثنين) $v
، $f
، و $d
سيتم تعيينها كلها إلى true
و $outFile
سيكون مساويا لـ /fizz/someOtherFile
؟
الطريقة المفضلة: استخدام bash المستقيمي دون getopt [s]
أنا في البداية أجاب على السؤال كما طلب OP. هذا Q / A هو الحصول على الكثير من الاهتمام ، لذلك ينبغي أن أقدم أيضا طريقة غير السحرية للقيام بذلك. سوف أتوسع على إجابة Guneysus لإصلاح سيد سيئة وتشمل اقتراح Tobias Kienzler .
اثنين من الطرق الأكثر شيوعا لتمرير الحجج الزوج قيمة مفتاح هي:
باش منفصل الفضاء المفصول
الاستخدام ./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
باش مستقيم يساوي المفصول
الاستخدام ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
#!/bin/bash
for i in "[email protected]"
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
لفهم ${i#*=}
للبحث عن "Substring Removal" في هذا الدليل . وهي مكافئة وظيفيا ل `sed 's/[^=]*=//' <<< "$i"`
الذي يستدعي عملية فرعية لا داعي لها أو `echo "$i" | sed 's/[^=]*=//'`
`echo "$i" | sed 's/[^=]*=//'`
الذي يستدعي معالجة ثانوية غير ضرورية.
باستخدام getopt [s]
من: http://mywiki.wooledge.org/BashFAQ/035#getopts
لا تستخدم ابدا getopt (1). لا يمكن getopt
معالجة سلاسل الوسائط الفارغة أو الوسائط مع whitespace المضمن. يرجى نسيان أنه موجود من أي وقت مضى.
توفر POSIX shell (وغيرها) getopts
التي تعتبر آمنة للاستخدام بدلاً من ذلك. في ما يلي مثال 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: [email protected]"
# End of file
مزايا getopts
هي:
- انها محمولة ، وستعمل على سبيل المثال اندفاعة.
- يمكنه التعامل مع أشياء مثل
-vf filename
بطريقة Unix المتوقعة ، تلقائيًا.
عيوب getopts
هو أنه لا يمكن إلا التعامل مع خيارات قصيرة ( -h
، وليس --help
) دون الخداع.
هناك تعليمي يوتوبس الذي يفسر ما يعنيه كل من بناء الجملة والمتغيرات. في bash ، هناك أيضًا help getopts
، والتي قد تكون مفيدة.
طريقة أكثر وضوحا
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"
الاستعمال:
./script.sh -d dev -u
# OR:
./script.sh --deploy dev --uglify
Solution that preserves unhandled arguments. Demos Included.
Here is my solution. It is VERY flexible and unlike others, shouldn't require external packages and handles leftover arguments cleanly.
Usage is: ./myscript -flag flagvariable -otherflag flagvar2
All you have to do is edit the validflags line. It prepends a hyphen and searches all arguments. It then defines the next argument as the flag name eg
./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2
The main code (short version, verbose with examples further down, also a version with erroring out):
#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in [email protected]
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
The verbose version with built in echo demos:
#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
[email protected]"
validflags="rate time number"
count=1
for arg in [email protected]
do
match=0
argval=$1
# argval=$(echo [email protected] | 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:
[email protected]"
shift $#
echo "post final clear args:
[email protected]"
set -- $leftovers
echo "all post set args:
[email protected]"
echo arg1: $1 arg2: $2
echo leftovers: $leftovers
echo rate $rate time $time number $number
Final one, this one errors out if an invalid -argument is passed through.
#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in [email protected]
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
Pros: What it does, it handles very well. It preserves unused arguments which a lot of the other solutions here don't. It also allows for variables to be called without being defined by hand in the script. It also allows prepopulation of variables if no corresponding argument is given. (See verbose example).
Cons: Can't parse a single complex arg string eg -xcvf would process as a single argument. You could somewhat easily write additional code into mine that adds this functionality though.
خلط الموضعين والوسائط المستندة إلى العلم
--param = arg (يساوي محدد)
مزج علامات بحرية بين الحجج الموضعية:
./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d
يمكن تحقيقه من خلال نهج موجز إلى حد ما:
# 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 (مساحة محددة)
من --flag=value
أن لا يتم الخلط --flag=value
و - --flag value
.
./script.sh dumbo 127.0.0.1 --environment production -q -d
هذا أمر مشبوه قليلاً للقراءة ، ولكنه ما زال صالحًا
./script.sh dumbo --environment production 127.0.0.1 --quiet -d
مصدر
# 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
أعتقد أن هذا بسيط بما يكفي للاستخدام:
#!/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
مثال على الاستحضار:
./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v
OPT:d
OPT:o OPTARG:/fizz/someOtherFile
OPT:f
ARG:./foo/bar/someFile
أعطي لك الدالة parse_params
التي parse_params
params:
- دون تلويث النطاق العالمي.
- يعيد لك جهدًا جاهزًا لاستخدام المتغيرات بحيث يمكنك بناء المزيد من المنطق عليها
- مقدار الشرطات قبل params لا يهم (
-all
all=all
equals-all
يساويall=all
)
البرنامج النصي أدناه هو عرض العمل النسخ واللصق. راجع وظيفة show_use
لفهم كيفية استخدام parse_params
.
محددات:
- لا يدعم params محددة في الفضاء (
-d 1
) - ستفقد أسماء Param شرطات حتى
--any-param
و-anyparam
متساوية -
eval $(parse_params "[email protected]")
داخل وظيفة bash (لن تعمل في النطاق العالمي)
#!/bin/bash
# Universal Bash parameter parsing
# Parse equals separated params into named local variables
# Standalone named parameter value will equal param name (--force creates variable $force=="force")
# Parses multi-valued named params into array (--path=path1 --path=path2 creates ${path[*]} array)
# Parses un-named params into ${ARGV[*]} array
# @author Oleksii Chekulaiev
# @version v1.2 (Aug-24-2017)
parse_params ()
{
local existing_named
local ARGV=()
echo "local ARGV=(); "
while [[ "$1" != "" ]]; do
# If equals delimited named parameter
if [[ "$1" =~ ^..*=..* ]]; then
# 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//\-}
# 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//\-}
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 "[email protected]")
# --
echo "${ARGV[0]}"
echo "${ARGV[1]}"
echo "$anyparam"
echo "$k"
echo "${multivalue[0]}"
echo "${multivalue[1]}"
[[ "$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
استخدام "الحجج" وحدة نمطية من bash-modules
مثال:
#!/bin/bash
. import.sh log arguments
NAME="world"
parse_arguments "-n|--name)NAME;S" -- "[email protected]" || {
error "Cannot parse command line."
exit 1
}
info "Hello, $NAME!"
بالتوسع في الإجابة الممتازة بواسطةguneysus ، هنا عبارة عن قرص يتيح للمستخدم استخدام أي صيغة تفضلها ، على سبيل المثال
command -x=myfilename.ext --another_switch
ضد
command -x myfilename.ext --another_switch
وهذا يعني أن الأنداد يمكن استبداله بمسافة بيضاء.
قد لا يكون هذا "التفسير الغامض" على هوايتك ، ولكن إذا كنت تقوم بعمل نصوص قابلة للتبديل مع أدوات مساعدة أخرى (كما هو الحال مع الأعمال المتعلقة بالألغام ، والتي يجب أن تعمل مع ffmpeg) ، فإن المرونة مفيدة.
STD_IN=0
prefix=""
key=""
value=""
for keyValue in "[email protected]"
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
على خطر إضافة مثال آخر لتجاهل ، وهنا مخطط بلدي.
- يعالج
-n arg
و--name=arg
- يسمح الحجج في النهاية
- يظهر أخطاء عاقل إذا كان أي خطأ إملائي
- متوافق ، لا يستخدم bashisms
- مقروء ، لا يتطلب الحفاظ على الحالة في حلقة
آمل أن يكون مفيدا لشخص ما.
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
قد يكون هذا مفيدًا أيضًا في معرفة ما إذا كان بإمكانك تعيين قيمة وما إذا كان هناك شخص ما يقدم مدخلات أم لا ، فعليك تجاوز القيمة الافتراضية بتلك القيمة.
myscript.sh -f ./serverlist.txt أو فقط ./myscript.sh (ويستغرق الإعدادات الافتراضية)
#!/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}"
لاحظ أن getopt(1)
كان خطأ حيًا قصيرًا من AT & T.
تم إنشاء getopt في عام 1984 ولكن دفنت بالفعل في عام 1986 لأنه لم يكن قابلا للاستخدام حقا.
والدليل على حقيقة أن getopt
قديم جدا هو أن صفحة الرجل getopt(1)
لا تزال تذكر "$*"
بدلا من "[email protected]"
، التي أضيفت إلى Bourne Shell في عام 1986 جنبا إلى جنب مع getopts(1)
shell getopts(1)
من أجل التعامل مع الحجج بمسافات بداخله.
راجع للشغل: إذا كنت مهتمًا بتحليل خيارات طويلة في نصوص القشرة ، قد يكون من المثير معرفة أن تطبيق getopt(3)
من libc (Solaris) و ksh93
أضاف كلاهما خيارًا طويلًا للتنفيذ يدعم خيارات طويلة ksh93
مستعارة قصيرة خيارات. هذا يسبب ksh93
و Bourne Shell
لتنفيذ واجهة موحدة لخيارات طويلة عن طريق getopts
.
مثال على خيارات طويلة مأخوذة من صفحة رجل بورن شل:
getopts "f:(file)(input-file)o:(output-file)" OPTX "[email protected]"
يوضح كيف يمكن استخدام خيار الأسماء المستعارة للخيار في كل من Bourne Shell و ksh93.
انظر صفحة رجل من Bourne Shell الأخيرة:
http://schillix.sourceforge.net/man/man1/bosh.1.html
والصفحة الرجل للحصول على getopt (3) من OpenSolaris:
http://schillix.sourceforge.net/man/man3c/getopt.3c.html
وأخيرًا ، صفحة getopt (1) man للتحقق من $ * قديم:
هذه هي الطريقة التي أقوم بها في وظيفة لتفادي كسر getopts في نفس الوقت في مكان أعلى في المكدس:
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
...
}
يعمل getopts بشكل رائع إذا كانت # 1 قد قمت بتثبيته و # 2 تنوي تشغيله على نفس النظام الأساسي. يتصرف OSX و Linux (على سبيل المثال) بشكل مختلف في هذا الصدد.
هنا هو الحل (غير getopts) التي تدعم المساواة ، غير متساوية ، والأعلام المنطقية. على سبيل المثال ، يمكنك تشغيل البرنامج النصي الخاص بك بهذه الطريقة:
./script --arg1=value1 --arg2 value2 --shouldClean
# parse the arguments.
COUNTER=0
ARGS=("[email protected]")
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
لا توجد إجابة تذكر تحسين getopt . والإجابة الأعلى تصويتًا مضللة: إنها تتجاهل الخيارات القصيرة للنمط -vfd
(التي يطلبها OP) ، والخيارات بعد الحجج الموضعية (تطلب أيضًا من البروتوكول الاختياري) وتتجاهل أخطاء التحليل. في حين أن:
- استخدام
getopt
المحسنة من استخدام لينكس أو سابقا GNU glibc . 1 - إنه يعمل مع
getopt_long()
الدالة C من GNU glibc. - يحتوي على جميع الميزات المميزة المفيدة (لا يمتلكها الآخرون):
- يعالج المسافات ، نقلا عن الأحرف وحتى ثنائي في الحجج 2
- يمكن معالجة الخيارات في النهاية:
script.sh -o outFile file1 file2 -v
- يسمح خيارات طويلة على غرار
=
:script.sh --outfile=fileOut --infile fileIn
- قديم جدا 3 لا يفقد نظام غنو هذا (على سبيل المثال أي لينكس).
- يمكنك اختبار وجودها مع:
getopt --test
return value 4. - غيرها من
getopt
أوgetopt
shell-getopts
هي ذات الاستخدام المحدود.
المكالمات التالية
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
كل العودة
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
مع myscript
التالي
#!/bin/bash
getopt --test > /dev/null
if [[ $? -ne 4 ]]; then
echo "I’m sorry, `getopt --test` failed in this environment."
exit 1
fi
OPTIONS=dfo:v
LONGOPTIONS=debug,force,output:,verbose
# -temporarily store output to be able to check for errors
# -e.g. use “--options” parameter by name to activate quoting/enhanced mode
# -pass arguments only via -- "[email protected]" to separate them correctly
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "[email protected]")
if [[ $? -ne 0 ]]; then
# e.g. $? == 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"
# 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 getopt المحسنة متوفرة في معظم "أنظمة bash" ، بما في ذلك Cygwin ؛ على OS X محاولة الشراب تركيب gnu-getopt
2 لا تحتوي اصطلاحات POSIX exec()
على طريقة موثوقة لتمرير BULL NULL في وسائط سطر الأوامر؛ تلك البايت إنهاء الأسبق الخلاف
3 الإصدار الأول الذي صدر في عام 1997 أو قبل ذلك (أنا فقط تتبع ذلك مرة أخرى إلى عام 1997)
Assume we create a shell script named test_args.sh
as follow
#!/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"
After we run the following command:
sh test_args.sh -year 2017 -flag -month 12 -day 22
The output would be:
year=2017 month=12 day=22 flag=true
Here is my improved solution of Bruno Bronosky's answer using variable arrays.
it lets you mix parameters position and give you a parameter array preserving the order without the options
#!/bin/bash
echo [email protected]
PARAMS=()
SOFT=0
SKIP=()
for i in "[email protected]"
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[@]}
Will output for example:
$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP = .c .obj
SOFT = 1
Parameters:
parameter somefile
I have write a bash helper to write a nice bash tool
project home: https://gitlab.mbedsys.org/mbedsys/bashopts
مثال:
#!/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 "[email protected]"
# Process argument
bashopts_process_args
will give help:
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 :)
This example shows how to use getopt
and eval
and HEREDOC
and shift
to handle short and long parameters with and without a required value that follows. Also the switch/case statement is concise and easy to follow.
#!/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" -- "[email protected]")
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
The most significant lines of the script above are these:
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]")
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
Short, to the point, readable, and handles just about everything (IMHO).
Hope that helps someone.