Как проанализировать аргументы командной строки в Bash?

скажем, у меня есть скрипт, который вызывается с этой строки:

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

29 ответов


метод #1: Использование bash без getopt [s]

два распространенных способа передачи аргументов пары ключ-значение:

Bash разделены пробелами (например,--option argument) (без getopt[s])

использование ./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 равно-разделено (например,--option=argument) (без getopt[s])

использование ./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

чтобы лучше понять ${i#*=} поиск "удаление подстроки" в данное руководство. Это функционально эквивалентно `sed 's/[^=]*=//' <<< "$i"` который вызывает ненужный подпроцесс или `echo "$i" | sed 's/[^=]*=//'` которых звонки два излишне подпроцессов.

Метод #2: Использование bash с getopt [s]

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

ограничения getopt(1) (старые, сравнительно недавно getopt версии):

  • не может обрабатывать аргументы, которые являются пустые строки
  • не могу справиться аргументы со встроенными пробелами

последние getopt версии не имеют этих ограничений.

кроме того, оболочка POSIX (и другие) предлагают 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: $@"

# End of file

преимущества getopts являются:

  1. он более портативный и будет работать в других оболочках, таких как dash.
  2. оно может отрегулировать множественные одиночные варианты как -vf filename типичным для Unix способом, автоматически.

недостаток getopts это то, что он может обрабатывать только короткие параметры (-h, а не --help) без дополнительного кода.

есть getopts учебник что объясняет, что означают все синтаксис и переменные. В Баш, есть также help getopts, что может быть информативным.


нет ответа упоминает расширенный getopt. И топ-проголосовали ответ вводит в заблуждение: он игнорирует -⁠vfd style short options (запрошенные OP), опции после позиционных аргументов (также запрошенные OP), и он игнорирует ошибки разбора. Вместо этого:

  • используйте enhanced getopt из util-linux или ранее GNU glibc.1
  • он работает с getopt_long() в C функция GNU glibc.
  • и все полезные отличительные особенности (другие не имеют их):
    • обрабатывает пробелы, цитируя символы и даже двоичные в аргументах2
    • он может обрабатывать параметры в конце: script.sh -o outFile file1 file2 -v
    • позволяет =-стиль длинные варианты: script.sh --outfile=fileOut --infile fileIn
  • уже так стар3 что нет системы GNU хватает этого (например, Linux имеет его).
  • вы можете проверить его существование с: getopt --test → возвращаемое значение 4.
  • другое getopt или shell-builtin 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
# 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 "" -- "$@")
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 ": A single input file is required."
    exit 4
fi

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

1 увеличенное getopt доступно на большинств "bash-системах", включая Cygwin; дальше OS X try brew установить gnu-getopt или sudo port install getopt
2 в POSIX exec() соглашения не имеют надежного способа передать двоичный NULL в аргументах командной строки; эти байты преждевременно заканчивают аргумент
3 первая версия выпущена в 1997 году или раньше (я только отследил ее до 1997 года)


от : digitalpeer.com с незначительными изменениями

использование 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}

чтобы лучше понять ${i#*=} поиск "удаление подстроки" в данное руководство. Это функционально эквивалентно `sed 's/[^=]*=//' <<< "$i"` который вызывает ненужный подпроцесс или `echo "$i" | sed 's/[^=]*=//'` которых звонки два излишне подпроцессов.


getopt()/getopts() - хороший выбор. Украден из здесь:

простое использование "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

мы говорили, что любой из, - b, - c или-d будет разрешено, но за этим-c следует аргумент ("c:" говорит это).

если мы назовем это " g " и попробуем:

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

начнем с двух аргументов, и "использования getopt" разбивает варианты и каждый приводит свой аргумент. Это также добавлен."--"


рискуя добавить еще один пример для игнорирования, вот моя схема.

  • ручки -n arg и --name=arg
  • разрешает аргументы в конце
  • показывает вменяемые ошибки, если что-то неправильно написал
  • совместимы, не использовать bashisms
  • читаемо, не требует поддержания состояния в цикле

надеюсь, что это полезно кому-то.

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

более лаконичный способ

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"

использование:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify

Я около 4 лет на этот вопрос, но хочу вернуть. Я использовал более ранние ответы в качестве отправной точки для очистки моего старого синтаксического анализа adhoc param. Затем я переработал следующий код шаблона. Он обрабатывает как длинные, так и короткие параметры, используя аргументы = или пробел, а также несколько коротких параметров, сгруппированных вместе. Наконец, он повторно вставляет любые непараметрические аргументы обратно в $1,$2.. переменная. Надеюсь, это полезно.

#!/usr/bin/env bash

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

мой ответ в значительной степени основан на ответ Бруно Броноски, но я как бы размял его две чистые реализации bash в одну, которую я использую довольно часто.

# 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

Это позволяет иметь как разделенные пробелами параметры / значения, так и равные определенные значения.

таким образом, вы можете запустить свой скрипт, используя:

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

а также:

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

и оба должны иметь один и тот же конец результат.

плюсы:

  • позволяет как-arg=value и-arg value

  • работает с любым именем arg, которое вы можете использовать в bash

    • смысл-или -или-или арг арг-а-р-г или любой
  • чисто Баш. Нет необходимости изучать / использовать getopt или getopts

плюсы:

  • не удается объединить args

    • что означает no-abc. Вы должны сделать-a-b-C

Это единственные плюсы/минусы я могу думать с верхней части моей головы


Я нашел вопрос, чтобы написать портативный синтаксический анализ в скриптах так расстраивает, что я написал Argbash - генератор кода FOSS, который может генерировать аргументы-синтаксический анализ кода для вашего скрипта плюс он имеет некоторые приятные функции:

https://argbash.io


Я думаю, что этот достаточно прост в использовании:

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

расширение на отличный ответ @guneysus, вот настройка, которая позволяет пользователю использовать любой синтаксис, который они предпочитают, например

command -x=myfilename.ext --another_switch 

vs

command -x myfilename.ext --another_switch

то есть равные могут быть заменены пробелами.

эта "нечеткая интерпретация" может вам не понравиться, но если вы делаете скрипты, которые взаимозаменяемы с другими утилитами (как в случае с моим, который должен работать с ffmpeg), гибкость полезна.

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 отлично работает, если #1 у вас установлен и #2 вы собираетесь запустить его на той же платформе. OSX и Linux (например) ведут себя в этом отношении по-разному.

вот решение (не getopts), которое поддерживает флаги equals, non-equals и boolean. Например, вы можете запустить свой скрипт следующим образом:

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

Я даю вам функцию parse_params это будет анализировать параметры из командной строки.

  1. это чистое решение Bash, никаких дополнительных утилит.
  2. не загрязняет глобальную область видимости.
  3. Effortlessly возвращает вам простые в использовании переменные, на которых вы можете построить дальнейшую логику.
  4. количество тире перед params не имеет значения (--all равна -all равна all=all)

скрипт ниже это копипаст рабочая демонстрация. См.


Это то, как я делаю в функции, чтобы избежать разрыва 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
...
}

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

Я хотел бы предложить свою версию разбора вариант, который позволяет следующее:

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

также позволяет это (может быть нежелательным):

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

вы должны решить перед использованием, если = будет использоваться на опции или нет. Это должно поддерживать код в чистоте (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

отметим, что getopt(1) была короткая ошибка жизни от AT & T.

getopt был создан в 1984 году, но уже похоронен в 1986 году, потому что он не был действительно полезен.

доказательство того, что getopt очень устарел, что getopt(1) man page по-прежнему упоминает "$*" вместо "$@", который был добавлен в Bourne Shell в 1986 году вместе с getopts(1) shell builtin для того, чтобы иметь дело с аргументами с пробелами внутри.

BTW: если вы интересуясь разбором длинных опций в скриптах оболочки, может быть интересно узнать, что getopt(3) реализация от libc (Solaris) и ksh93 оба добавили единую длинную реализацию опции, которая поддерживает длинные опции в качестве псевдонимов для коротких опций. Это вызывает ksh93 и Bourne Shell реализовать единый интерфейс для длинных опций через getopts.

пример длинных опций, взятых с man-страницы Bourne Shell:

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

показывает как долго псевдонимы опций могут использоваться как в Bourne Shell, так и в ksh93.

смотрите man-страницу недавнего Bourne Shell:

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

и man-страница для getopt (3) из OpenSolaris:

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

и, наконец, man-страница getopt(1) для проверки устаревших $*:

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

--param arg (пробел разделен)

обычно яснее не смешивать --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=
ip_address=

Предположим, мы создаем скрипт с именем test_args.sh как следовать

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

после выполнения следующей команды:

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

выход такой:

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

использовать модуль "аргументы" от bash-модули

пример:

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

NAME="world"

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

info "Hello, $NAME!"

Это также может быть полезно знать, вы можете установить значение и если кто-то обеспечивает ввод, по умолчанию это значение..

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=""
    shift

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


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"

другое решение без getopt[s], POSIX, старого стиля Unix

аналогично решение Бруно Броноски опубликовано это без использования getopt(s).

основной отличительной особенностью моего решения является то, что оно позволяет иметь варианты, объединенные вместе, как tar -xzf foo.tar.gz равна tar -x -z -f foo.tar.gz. И так же, как в tar, ps etc. ведущий дефис является необязательным для блока коротких опций (но это можно легко изменить). Также поддерживаются длинные опции (но когда блок начинается с одного, требуются два ведущих дефиса).

код с Пример

#!/bin/sh

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

print_usage() {
  echo "Usage:

   {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

пример использования см. В примерах ниже.

положение опций с аргументами

для чего его стоит там варианты с аргументами не являются последними (только длинные варианты должны быть). Так, например, в tar (по крайней мере, в некоторых реализациях) в f параметры должны быть последними, потому что имя файла следует (tar xzf bar.tar.gz работает, а tar xfz bar.tar.gz нет) здесь это не так (см. Более поздние примеры).

несколько вариантов с аргументами

в качестве еще одного бонуса параметры опции потребляются в порядке опций по параметрам с необходимыми опциями. Просто посмотрите на вывод моего скрипта здесь с помощью командной строки abc X Y Z (или -abc X Y Z):

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

длинные параметры объединены как ну!--35-->

также вы также можете иметь длинные опции в блоке опций, учитывая, что они происходят последними в блоке. Таким образом, следующие командные строки эквивалентны (включая порядок, в котором параметры и их аргументы обработано):

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

все это приводит к:

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

не в этом решение

дополнительные аргументы

опции с необязательными аргументами должны быть возможны с небольшой работой, например, с нетерпением существует ли блок без дефиса; затем пользователю нужно будет поместить дефис перед каждым блоком после блока с параметром, имеющим необязательный параметр. Возможно, это слишком сложно для общения с пользователем, поэтому в этом случае лучше всего использовать ведущий дефис.

все становится еще сложнее с несколькими возможными параметрами. Я бы не советовал делать варианты, пытающиеся быть умными, определяя, может ли аргумент быть для него или нет (например, с опцией просто принимает число в качестве необязательного аргумента), потому что это может сломаться в будущем.

я лично предпочитаю дополнительные опции вместо необязательных аргументов.

аргументы опции введены со знаком равенства

как и с дополнительными аргументами, я не поклонник этого (кстати, есть ли поток для обсуждения плюсов / минусов разных стилей параметров?) но если вы хотите этого, вы, вероятно, могли бы реализовать его самостоятельно как сделано в http://mywiki.wooledge.org/BashFAQ/035#Manual_loop с --long-with-arg=?* оператор case, а затем зачистка знака равенства (это кстати сайт, который говорит, что создание конкатенации параметров возможно с некоторыми усилиями, но "оставил [это] как упражнение для читателя", что заставило меня взять их на слово, но я начал с нуля).

другие Примечания

POSIX-совместимый, работает даже на древних настройках Busybox, с которыми мне приходилось иметь дело (например, cut, head и getopts отсутствует).


решение, которое сохраняет необработанные аргументы. Демо Включены.

вот мое решение. Он очень гибкий и в отличие от других, не требует внешних пакетов и обрабатывает оставшиеся аргументы чисто.

использование: ./myscript -flag flagvariable -otherflag flagvar2

все, что вам нужно сделать, это отредактировать строку validflags. Он добавляет дефис и ищет все аргументы. Затем он определяет следующий аргумент как имя флага, например

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

основной код (краткая версия, подробный с примерами ниже, также версия с ошибками):

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

многословная версия со встроенными демонстрациями echo:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=
#   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 one, это одна ошибка, если передается недопустимый аргумент.

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

плюсы: то, что он делает, он очень хорошо справляется. Он сохраняет неиспользуемые аргументы, которых нет во многих других решениях. Он также позволяет вызывать переменные, не определяемые вручную в скрипте. Она также позволяет предустановки переменных, если соответствующий аргумент не задан. (См. подробный пример).

Минусы: Не удается проанализировать одну сложную строку arg, например-xcvf будет обрабатывать как один аргумент. Вы могли бы несколько легко написать дополнительный код в мой, который добавляет эту функциональность.


верхний ответ на этот вопрос казался немного багги, когда я попробовал его-вот мое решение, которое я нашел более надежным:

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 "") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
    exit 1
fi

в этом примере показано, как использовать getopt и eval и HEREDOC и shift для обработки коротких и длинных параметров с требуемым значением и без него. Также заявление переключателя / случая сжато и легко следовать.

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

наиболее значимыми строками сценария выше являются следующие:

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

короткий, по существу, читаемый и обрабатывает почти все (IMHO).

надеюсь, что это кому-то поможет.


Я написал помощник bash, чтобы написать хороший инструмент bash

проект дома: 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 "" -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

окажем помощь:

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

наслаждайтесь :)


вот мой подход-использование regexp.

  • нет getopts
  • он обрабатывает блок коротких параметров -qwerty
  • он обрабатывает короткие параметры -q -w -e
  • он обрабатывает длинные опции --qwerty
  • вы можете передать атрибут в короткую или длинную опцию (если вы используете блок коротких опций, атрибут прикреплен к последней опции)
  • вы можете использовать пробелы или = атрибуты, но атрибут соответствует До встречи дефиса + пробел "разделитель", поэтому в --q=qwe ty qwe ty один атрибут
  • он обрабатывает смесь всех выше так -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute действует

сценарий:

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

вот мое улучшенное решение ответа Бруно Броноски с использованием переменных массивов.

это позволяет смешивать параметры позиции и дать вам массив параметров, сохраняя порядок без опций

#!/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[@]}

выведет, например:

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP            = .c .obj
SOFT            = 1
Parameters:
parameter somefile

Я хочу представить свой проект:https://github.com/flyingangel/argparser

source argparser.sh
parse_args "$@"

просто. Среда будет заполнена переменными с тем же именем, что и аргументы