command-line الاوامر - كيف يمكنني تحليل حجج سطر الأوامر في Bash؟





على يعتمد (24)


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.

قل ، لدي برنامج نصي يتم استدعاؤه باستخدام هذا الخط:

./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 ؟




أعطي لك الدالة parse_params التي parse_params params:

  1. دون تلويث النطاق العالمي.
  2. يعيد لك جهدًا جاهزًا لاستخدام المتغيرات بحيث يمكنك بناء المزيد من المنطق عليها
  3. مقدار الشرطات قبل params لا يهم ( -all all=all equals -all يساوي all=all )

البرنامج النصي أدناه هو عرض العمل النسخ واللصق. راجع وظيفة show_use لفهم كيفية استخدام parse_params .

محددات:

  1. لا يدعم params محددة في الفضاء ( -d 1 )
  2. ستفقد أسماء Param شرطات حتى --any-param و -anyparam متساوية
  3. 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



طريقة أكثر وضوحا

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



من: digitalpeer.com مع تعديلات طفيفة

Usage myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "[email protected]"
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}

لفهم أفضل ${i#*=} ابحث عن "Substring Removal" في هذا الدليل . وهي مكافئة وظيفيا ل `sed 's/[^=]*=//' <<< "$i"` الذي يستدعي عملية فرعية لا داعي لها أو `echo "$i" | sed 's/[^=]*=//'` `echo "$i" | sed 's/[^=]*=//'` الذي يستدعي معالجة ثانوية غير ضرورية.




Another solution without getopt[s], POSIX, old Unix style

Similar to the solution Bruno Bronosky posted this here is one without the usage of getopt(s) .

Main differentiating feature of my solution is that it allows to have options concatenated together just like tar -xzf foo.tar.gz is equal to tar -x -z -f foo.tar.gz . And just like in tar , ps etc. the leading hyphen is optional for a block of short options (but this can be changed easily). Long options are supported as well (but when a block starts with one then two leading hyphens are required).

Code with example options

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from [email protected][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

For the example usage please see the examples further below.

Position of options with arguments

For what its worth there the options with arguments don't be the last (only long options need to be). So while eg in tar (at least in some implementations) the f options needs to be last because the file name follows ( tar xzf bar.tar.gz works but tar xfz bar.tar.gz does not) this is not the case here (see the later examples).

Multiple options with arguments

As another bonus the option parameters are consumed in the order of the options by the parameters with required options. Just look at the output of my script here with the command line abc XYZ (or -abc XYZ ):

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

Long options concatenated as well

Also you can also have long options in option block given that they occur last in the block. So the following command lines are all equivalent (including the order in which the options and its arguments are being processed):

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

All of these lead to:

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

Not in this solution

الحجج الاختيارية

Options with optional arguments should be possible with a bit of work, eg by looking forward whether there is a block without a hyphen; the user would then need to put a hyphen in front of every block following a block with a parameter having an optional parameter. Maybe this is too complicated to communicate to the user so better just require a leading hyphen altogether in this case.

Things get even more complicated with multiple possible parameters. I would advise against making the options trying to be smart by determining whether the an argument might be for it or not (eg with an option just takes a number as an optional argument) because this might break in the future.

I personally favor additional options instead of optional arguments.

Option arguments introduced with an equal sign

Just like with optional arguments I am not a fan of this (BTW, is there a thread for discussing the pros/cons of different parameter styles?) but if you want this you could probably implement it yourself just like done at http://mywiki.wooledge.org/BashFAQ/035#Manual_loop with a --long-with-arg=?* case statement and then stripping the equal sign (this is BTW the site that says that making parameter concatenation is possible with some effort but "left [it] as an exercise for the reader" which made me take them at their word but I started from scratch).

Other notes

POSIX-compliant, works even on ancient Busybox setups I had to deal with (with eg cut , head and getopts missing).




تعتمد إجابتي إلى حد كبير على إجابة برونو برونوسكي ، لكنني قمت بنقل تطبيقي باش البحتين إلى أسلوب استخدمه كثيرًا.

# 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

يتيح لك هذا الخيار خيارات / قيم منفصلة للفضاء ، بالإضافة إلى قيم محددة متساوية.

حتى تتمكن من تشغيل البرنامج النصي الخاص بك باستخدام:

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

طالما:

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

وكلاهما يجب أن يكون لهما نفس النتيجة النهائية.

PROS:

  • يسمح لكل من قيمة -arg = وقيمة -arg

  • يعمل مع أي اسم arg يمكنك استخدامه في bash

    • المعنى -أو -ارغ أو -ارض أو -ارغ أو أيا كان
  • باش خالص. لا حاجة لتعلم / استخدام getopt أو gopop

سلبيات:

  • لا يمكن الجمع بين args

    • المعنى لا -abc. يجب عليك القيام -a -b -c

هذه هي الإيجابيات / السلبيات الوحيدة التي يمكنني التفكير فيها من قمة رأسي




لاحظ أن 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 للتحقق من $ * قديم:

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




خلط الموضعين والوسائط المستندة إلى العلم

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



Here is my approach - using regexp.

  • no getopts
  • it handles block of short parameters -qwerty
  • it handles short parameters -q -w -e
  • it handles long options --qwerty
  • you can pass attribute to short or long option (if you are using block of short options, attribute is attached to the last option)
  • you can use spaces or = to provide attributes, but attribute matches until encountering hyphen+space "delimiter", so in --q=qwe ty qwe ty is one attribute
  • it handles mix of all above so -oa -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute is valid

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
}
[email protected]

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



أعتقد أن هذا بسيط بما يكفي للاستخدام:

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



The top answer to this question seemed a bit buggy when I tried it -- here's my solution which I've found to be more robust:

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



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



استخدام "الحجج" وحدة نمطية من 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!"



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 :)




أنا في وقت متأخر من 4 سنوات لهذا السؤال ، ولكن أريد أن أعود. لقد استخدمت الإجابات السابقة كنقطة بداية لترتيب إعجابي القديم الأساسي. أنا ثم refactored من رمز القالب التالي. يتعامل مع كل من params طويلة وقصيرة ، باستخدام الحجج = أو الفضاء فصل ، فضلا عن عدة معلمات قصيرة مجمعة معا. وأخيرًا ، يعيد إدراج أي حجج غير معلمة مرة أخرى في المتغيرات $ 1 ، $ 2 ... آمل أن تكون مفيدة.

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 [email protected] ; 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



أرغب في تقديم الإصدار الخاص بي من تحليل الخيار ، والذي يسمح بما يلي:

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

كما يسمح لهذا (يمكن أن يكون غير مرغوب فيه):

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

يجب عليك أن تقرر قبل الاستخدام إذا = هو أن تستخدم في خيار أم لا. هذا هو الحفاظ على رمز نظيفة (العش).

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



لا يتطلب EasyOptions أي تحليل:

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



لقد وجدت هذه المسألة لكتابة تحليل محمول في البرامج النصية المحبطة جدًا لدرجة أنني كتبت Argbash - وهو مولد شفرة FOSS يمكنه إنشاء شفرة تحليل الحجج للنص البرمجي بالإضافة إلى أنه يحتوي على بعض الميزات Argbash :

https://argbash.io




الطريقة المفضلة: استخدام 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 هي:

  1. انها محمولة ، وستعمل على سبيل المثال اندفاعة.
  2. يمكنه التعامل مع أشياء مثل -vf filename بطريقة Unix المتوقعة ، تلقائيًا.

عيوب getopts هو أنه لا يمكن إلا التعامل مع خيارات قصيرة ( -h ، وليس --help ) دون الخداع.

هناك تعليمي يوتوبس الذي يفسر ما يعنيه كل من بناء الجملة والمتغيرات. في bash ، هناك أيضًا help getopts ، والتي قد تكون مفيدة.




getopt() / getopts() خيار جيد. سرقت من here :

يتم عرض الاستخدام البسيط لـ "getopt" في هذا البرنامج النصي المصغر:

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

ما قلناه هو أن أيًا من -a أو -b أو -c أو -d سيتم السماح به ، لكن ذلك -c يتبعه حجة ("c:" تقول ذلك).

إذا كنا نسمي هذا "g" ونجربه:

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

نبدأ بحجوتين ، و "getopt" تفصل الخيارات وتضع كل واحدة في حجة خاصة بها. وأضاف أيضا "-".




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



على خطر إضافة مثال آخر لتجاهل ، وهنا مخطط بلدي.

  • يعالج -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



يعمل 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



لقد قمت بمقارنة العديد من الإجابات المقدمة ، والتوصل إلى بعض الحلول الصغيرة. يبدو أن هذه الأمور تتعامل مع جميع حالات الحواف المجنونة التي تنشأ عن مزيجك المفضل من:

  • المسارات المطلقة أو المسارات النسبية
  • الملف وصلات الدليل لينة
  • استدعاء كنص مكتوب أو script bash script أو bash -c script أو source script أو . script . script
  • المساحات ، علامات التبويب ، newlines ، unicode ، إلخ في الدلائل و / أو اسم الملف
  • أسماء الملفات التي تبدأ بواصلة

إذا كنت تعمل من Linux ، فيبدو أن استخدام مقبض proc هو أفضل حل لتحديد مصدر الحل الكامل للبرنامج النصي الجاري تشغيله حاليًا (في جلسة تفاعلية ، يشير الارتباط إلى /dev/pts/X المعني):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

يحتوي هذا على القليل من القبح ، لكن الإصلاح مضغوط وسهل الفهم. نحن لا نستخدم الأعداد الأولية من bash فقط ، لكنني على ما يرام لأن readlink يبسط المهمة بشكل كبير. يضيف echo X علامة X إلى نهاية السلسلة المتغيرة بحيث لا يتم تناول أي مسافة بيضاء لاحقة في اسم الملف ، ويتخلص استبدال المعلمة ${VAR%X} في نهاية السطر من X ولأن readlink يضيف سطر جديد خاص بها (والذي عادة ما يؤكل في استبدال الأمر إن لم يكن لخداعنا السابق) ، يجب علينا التخلص منه أيضًا. يتم إنجاز ذلك بسهولة باستخدام نظام $'' quote $'' ، والذي يتيح لنا استخدام تسلسلات الهروب مثل \n لتمثيل الخطوط الجديدة (وهذا أيضًا هو كيف يمكنك بسهولة إنشاء ملفات وملفات محددة بشكل خادع).

يجب أن يغطي ما سبق احتياجاتك لتحديد موقع النص الجاري قيد التشغيل حاليًا على نظام التشغيل Linux ، ولكن إذا لم يكن لديك نظام ملفات proc تحت تصرفك ، أو إذا كنت تحاول تحديد موقع المسار الذي تمت معالجته بشكل كامل من بعض الملفات الأخرى ، فربما تكون أنت ' ليرة لبنانية العثور على التعليمات البرمجية أدناه مفيدة. انها مجرد تعديل طفيف من بطانة واحدة أعلاه. إذا كنت تلهو بأسماء / أسماء ملفات غريبة ، فإن التحقق من الإخراج بكل من ls و readlink ، حيث إن ls readlink مسارات "مبسطة" ، تستبدل ? لأشياء مثل الخطوط الجديدة.

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"




bash command-line scripting arguments