it-swarm-id.com

Bagaimana cara menguraikan argumen baris perintah di Bash?

Katakanlah, saya memiliki skrip yang dipanggil dengan baris ini:

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

atau yang ini:

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

Apa cara yang diterima untuk mem-parsing ini sehingga dalam setiap kasus (atau kombinasi keduanya) $v, $f, dan $d semuanya akan diatur ke true dan $outFile akan sama dengan /fizz/someOtherFile?

1521

Metode # 1: Menggunakan bash tanpa getopt [s]

Dua cara umum untuk menyampaikan argumen pasangan kunci-nilai adalah:

Bash Space-Separated (mis., --option argument) (tanpa getopt [s])

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

Bash Equals-Separated (mis., --option=argument) (tanpa getopt [s])

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

Untuk lebih memahami pencarian ${i#*=} untuk "Penghapusan Substring" di panduan ini . Secara fungsional ini setara dengan `sed 's/[^=]*=//' <<< "$i"` yang memanggil proses yang tidak perlu atau `echo "$i" | sed 's/[^=]*=//'` yang memanggil dua subproses yang tidak perlu. 

Metode # 2: Menggunakan bash dengan getopt [s]

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

getopt (1) batasan (versi getopt yang lebih lama, relatif baru): 

  • tidak dapat menangani argumen yang merupakan string kosong
  • tidak dapat menangani argumen dengan spasi putih tertanam

Versi getopt yang lebih baru tidak memiliki batasan ini.

Selain itu, Shell POSIX (dan lainnya) menawarkan getopts yang tidak memiliki batasan ini. Berikut adalah contoh getopts sederhana:

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

Keuntungan dari getopts adalah:

  1. Ini lebih portabel, dan akan bekerja di cangkang lain seperti dash
  2. Ia dapat menangani beberapa opsi tunggal seperti -vf filename dengan cara Unix yang khas, secara otomatis.

Kerugian getopts adalah ia hanya bisa menangani opsi pendek (-h, bukan --help) tanpa kode tambahan.

Ada tutorial getopts yang menjelaskan apa arti semua sintaks dan variabel. Di bash, ada juga help getopts, yang mungkin informatif.

2173
Bruno Bronosky

Tidak ada jawaban yang menyebutkan getopt yang ditingkatkan. Dan jawaban terpilih menyesatkan: Ia mengabaikan opsi pendek gaya -⁠vfd (diminta oleh OP), opsi setelah argumen posisi (juga diminta oleh OP) dan mengabaikan kesalahan penguraian. Sebagai gantinya:

  • Gunakan getopt yang disempurnakan dari util-linux atau sebelumnya GNU glibc.1
  • Ia bekerja dengan getopt_long() fungsi C dari GNU glibc.
  • Memiliki semua fitur pembeda yang berguna (yang lain tidak memilikinya):
    • menangani spasi, mengutip karakter, dan bahkan biner dalam argumen2
    • itu dapat menangani opsi di akhir: script.sh -o outFile file1 file2 -v
    • memungkinkan =- opsi panjang gaya: script.sh --outfile=fileOut --infile fileIn
  • Sudah sangat tua3 bahwa tidak ada sistem GNU yang melewatkan ini (mis. Linux memilikinya).
  • Anda dapat menguji keberadaannya dengan: getopt --test → nilai pengembalian 4.
  • getopt atau Shell-builtin getopts lainnya terbatas penggunaannya.

Panggilan berikut

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

semua kembali

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

dengan myscript berikut

#!/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   -- "[email protected]"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "[email protected]")
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 "$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 yang disempurnakan tersedia di sebagian besar "sistem-bash", termasuk Cygwin; pada OS X coba buat instalasi gnu-getopt atau Sudo port install getopt
2 konvensi POS__ exec() tidak memiliki cara yang andal untuk meneruskan NULL biner dalam argumen baris perintah; byte tersebut sebelum waktunya mengakhiri argumen
3 versi pertama dirilis pada 1997 atau sebelumnya (saya hanya melacaknya kembali ke 1997)

419
Robert Siemer

dari: digitalpeer.com dengan sedikit modifikasi

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

Untuk lebih memahami pencarian ${i#*=} untuk "Penghapusan Substring" di panduan ini . Secara fungsional ini setara dengan `sed 's/[^=]*=//' <<< "$i"` yang memanggil proses yang tidak perlu atau `echo "$i" | sed 's/[^=]*=//'` yang memanggil dua subproses yang tidak perlu.

114
guneysus

getopt()/getopts() adalah pilihan yang baik. Dicuri dari sini :

Penggunaan sederhana "getopt" ditunjukkan dalam skrip mini ini:

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

Apa yang telah kami katakan adalah bahwa dari -a, -b, -c atau -d akan diizinkan, tetapi -c diikuti oleh argumen ("c:" mengatakan itu).

Jika kami menyebutnya "g" dan mencobanya:

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

Kami mulai dengan dua argumen, dan "getopt" memecah opsi dan menempatkan masing-masing dalam argumennya sendiri. Juga menambahkan "-".

102
Matt J

Dengan risiko menambahkan contoh lain untuk diabaikan, inilah skema saya.

  • menangani -n arg dan --name=arg
  • memungkinkan argumen di akhir
  • menunjukkan kesalahan waras jika ada yang salah eja
  • kompatibel, tidak menggunakan bashism
  • dapat dibaca, tidak perlu mempertahankan status dalam satu lingkaran

Semoga bermanfaat bagi seseorang.

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

Cara yang lebih ringkas

script.sh

#!/bin/bash

while [[ "$#" -gt 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"

Penggunaan:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify
78
Inanc Gumus

Saya terlambat sekitar 4 tahun untuk pertanyaan ini, tetapi ingin memberi kembali. Saya menggunakan jawaban sebelumnya sebagai titik awal untuk merapikan parameter parsing adhoc lama saya. Saya kemudian refactored keluar kode templat berikut. Ia menangani params panjang dan pendek, menggunakan argumen = atau spasi, serta beberapa params pendek dikelompokkan bersama. Akhirnya ia memasukkan kembali argumen non-param kembali ke variabel $ 1, $ 2 .. Semoga bermanfaat.

#!/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
39
Shane Day

Jawaban saya sebagian besar didasarkan pada jawaban oleh Bruno Bronosky , tapi saya agak tumbuk dua implementasi bash murni menjadi salah satu yang saya gunakan cukup sering.

# 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

Ini memungkinkan Anda untuk memiliki kedua opsi/nilai yang dipisahkan dengan ruang, serta nilai yang didefinisikan sama.

Jadi Anda dapat menjalankan skrip Anda menggunakan:

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

sebaik:

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

dan keduanya harus memiliki hasil akhir yang sama.

PROS:

  • Memungkinkan untuk -arg = nilai dan nilai -arg

  • Bekerja dengan nama arg apa pun yang dapat Anda gunakan di bash

    • Arti -a atau -arg atau --arg atau -a-r-g atau apa pun
  • Pesta murni. Tidak perlu belajar/menggunakan getopt atau getopt

CONS:

  • Tidak dapat menggabungkan args

    • Berarti tidak -abc. Anda harus melakukan -a -b -c

Ini adalah satu-satunya pro/kontra yang bisa saya pikirkan dari atas kepala saya

25
Ponyboy47

Saya telah menemukan masalah untuk menulis parsing portabel dalam skrip yang sangat membuat frustrasi sehingga saya telah menulis Argbash - generator kode FOSS yang dapat menghasilkan kode parsing argumen untuk skrip Anda plus memiliki beberapa fitur Nice:

https://argbash.io

24
bubla

Memperluas jawaban yang sangat bagus oleh @guneysus, berikut adalah Tweak yang memungkinkan pengguna menggunakan sintaks mana saja yang mereka sukai, mis.

command -x=myfilename.ext --another_switch 

vs.

command -x myfilename.ext --another_switch

Artinya persamaan bisa diganti dengan spasi putih. 

"Interpretasi fuzzy" ini mungkin tidak sesuai dengan keinginan Anda, tetapi jika Anda membuat skrip yang dapat dipertukarkan dengan utilitas lain (seperti halnya dengan milik saya, yang harus bekerja dengan ffmpeg), fleksibilitas berguna.

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

Saya pikir ini cukup sederhana untuk digunakan:

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

Contoh doa:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile
13
Alek

Saya memberi Anda Function parse_params yang akan mem-parsing params dari baris perintah.

  1. Ini adalah solusi Bash murni, tidak ada utilitas tambahan.
  2. Tidak mencemari ruang lingkup global. 
  3. Mudah mengembalikan Anda sederhana untuk menggunakan variabel, bahwa Anda dapat membangun logika lebih lanjut.
  4. Jumlah tanda hubung sebelum param tidak masalah (--all sama dengan -all sama dengan all=all)

Script di bawah ini adalah demonstrasi kerja copy-paste. Lihat fungsi show_use untuk memahami cara menggunakan parse_params.

Keterbatasan: 

  1. Tidak mendukung parem dibatasi ruang (-d 1)
  2. Nama param akan kehilangan tanda hubung sehingga --any-param dan -anyparam sama
  3. eval $(parse_params "[email protected]") harus digunakan di dalam bash function (ini tidak akan berfungsi dalam lingkup global)

#!/bin/bash

# Universal Bash parameter parsing
# Parses equal sign separated params into local variables (--name=bob creates variable $name=="bob")
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Parses un-named params into ${ARGV[*]} array
# Additionally puts all named params raw into ${ARGN[*]} array
# Additionally puts all standalone "option" params raw into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4 (Jun-26-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion
        _escaped=${1/\*/\'\"*\"\'}
        # If equals delimited named parameter
        if [[ "$1" =~ ^..*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # 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//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key=\"$_val\";"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        Elif [[ "$1" =~ ^\-. ]]; then
            # remove dashes
            local _key=${1//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "[email protected]")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
8

getopts berfungsi dengan baik jika # 1 Anda menginstalnya dan # 2 Anda bermaksud menjalankannya pada platform yang sama. OSX dan Linux (misalnya) berperilaku berbeda dalam hal ini.

Berikut adalah solusi (non getopts) yang mendukung flag sama dengan, tidak sama, dan boolean. Misalnya Anda dapat menjalankan skrip Anda dengan cara ini:

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

EasyOptions tidak memerlukan penguraian:

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

Inilah yang saya lakukan dalam suatu fungsi untuk menghindari kerusakan menjalankan getup pada saat yang sama di suatu tempat yang lebih tinggi di stack:

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

Saya ingin menawarkan parsing opsi versi saya, yang memungkinkan untuk yang berikut:

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

Juga memungkinkan untuk ini (bisa tidak diinginkan):

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

Anda harus memutuskan sebelum menggunakan apakah = akan digunakan pada opsi atau tidak. Ini untuk menjaga kode tetap bersih (ish).

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

Perhatikan bahwa getopt(1) adalah kesalahan hidup singkat dari AT&T.

getopt dibuat pada tahun 1984 tetapi sudah dimakamkan pada tahun 1986 karena itu tidak benar-benar dapat digunakan.

Sebuah bukti untuk fakta bahwa getopt sangat usang adalah bahwa halaman manual getopt(1) masih menyebutkan "$*" bukannya "[email protected]", yang ditambahkan ke Bourne Shell pada tahun 1986 bersama dengan getopts(1) Shell yang dibuat untuk menangani argumen dengan ruang di dalamnya.

BTW: jika Anda tertarik untuk menguraikan opsi panjang dalam skrip Shell, mungkin menarik untuk mengetahui bahwa implementasi getopt(3) dari libc (Solaris) dan ksh93 keduanya menambahkan implementasi opsi panjang yang seragam yang mendukung opsi panjang sebagai alias untuk opsi pendek. Ini menyebabkan ksh93 dan Bourne Shell untuk mengimplementasikan antarmuka yang seragam untuk opsi panjang melalui getopts.

Contoh untuk opsi panjang yang diambil dari halaman manual Bourne Shell:

getopts "f:(file)(input-file)o:(output-file)" OPTX "[email protected]"

menunjukkan berapa lama alias opsi dapat digunakan di Bourne Shell dan ksh93.

Lihat halaman manual dari Bourne Shell terbaru:

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

dan halaman manual untuk getopt (3) dari OpenSolaris:

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

dan terakhir, halaman manual getopt (1) untuk memverifikasi $ * yang sudah usang:

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

4
schily

Asumsikan kita membuat skrip Shell bernama test_args.sh sebagai berikut

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

Setelah kami menjalankan perintah berikut:

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

Outputnya adalah:

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

Menggabungkan argumen berbasis posisi dan bendera

--param = arg (sama dengan dibatasi)

Mencampur bendera dengan bebas di antara argumen posisi:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

dapat dicapai dengan pendekatan yang cukup ringkas:

# 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 (dibatasi ruang)

Biasanya lebih jelas untuk tidak mencampur gaya --flag=value dan --flag value.

./script.sh dumbo 127.0.0.1 --environment production -q -d

Ini agak tidak pasti untuk dibaca, tetapi masih valid

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

Sumber

# 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
2
Mark Fox

Saya telah menulis bash helper untuk menulis alat bash yang bagus

rumah proyek: https://gitlab.mbedsys.org/mbedsys/bashopts

contoh:

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

akan memberi bantuan:

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

nikmati :)

2
Emeric Verschuur

Inilah pendekatan saya - menggunakan regexp.

  • tidak ada getto
  • ini menangani blok parameter pendek -qwerty
  • ini menangani parameter pendek -q -w -e
  • ini menangani opsi panjang --qwerty
  • anda bisa meneruskan atribut ke opsi pendek atau panjang (jika Anda menggunakan blok opsi pendek, atribut dilampirkan ke opsi terakhir)
  • anda dapat menggunakan spasi atau = untuk memberikan atribut, tetapi atribut cocok hingga menemukan tanda hubung + spasi "pembatas", jadi di --q=qwe tyqwe ty adalah salah satu atribut
  • ini menangani semua campuran di atas sehingga -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute valid

naskah:

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

Solusi yang mempertahankan argumen tidak tertangani. Demo Termasuk.

Ini solusinya. Ini SANGAT fleksibel dan tidak seperti yang lain, seharusnya tidak memerlukan paket eksternal dan menangani argumen sisa dengan bersih.

Penggunaannya adalah: ./myscript -flag flagvariable -otherflag flagvar2

Yang harus Anda lakukan adalah mengedit baris tanda valid. Ini menambahkan tanda hubung dan mencari semua argumen. Itu kemudian mendefinisikan argumen berikutnya sebagai nama bendera mis.

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

Kode utama (versi singkat, verbose dengan contoh lebih jauh ke bawah, juga versi dengan kesalahan):

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

Versi verbose dengan demo gema bawaan:

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

Yang terakhir, kesalahan yang satu ini terjadi jika -argumen yang tidak valid dilewatkan.

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

Pro: Apa yang dilakukannya, itu menangani dengan sangat baik. Ini mempertahankan argumen yang tidak digunakan yang mana banyak solusi lain di sini tidak. Hal ini juga memungkinkan variabel dipanggil tanpa harus didefinisikan dengan tangan dalam skrip. Ini juga memungkinkan pra-populasi variabel jika tidak ada argumen yang sesuai diberikan. (Lihat contoh verbose).

Cons: Tidak dapat menguraikan string arg kompleks tunggal mis. -xcvf akan diproses sebagai argumen tunggal. Anda agak bisa dengan mudah menulis kode tambahan ke tambang yang menambahkan fungsi ini. 

2
Noah

Solusi lain tanpa getopt [POS], POSIX, gaya Unix lama

Mirip dengan solusi yang diposting Bruno Bronosky ini di sini adalah salah satu tanpa penggunaan getopt(s).

Fitur pembeda utama dari solusi saya adalah memungkinkan untuk memiliki opsi yang disatukan bersamaan seperti tar -xzf foo.tar.gz sama dengan tar -x -z -f foo.tar.gz. Dan seperti dalam tar, ps dll. Tanda hubung utama adalah opsional untuk blok opsi pendek (tetapi ini dapat diubah dengan mudah). Opsi panjang juga didukung (tetapi ketika sebuah blok dimulai dengan satu maka dua tanda hubung utama diperlukan).

Kode dengan opsi contoh

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

Untuk contoh penggunaan, silakan lihat contoh lebih lanjut di bawah ini.

Posisi opsi dengan argumen

Untuk apa nilainya di sana, opsi dengan argumen tidak menjadi yang terakhir (hanya opsi panjang yang diperlukan). Jadi, mis. dalam tar (setidaknya dalam beberapa implementasi) opsi f perlu yang terakhir karena nama file berikut (tar xzf bar.tar.gz berfungsi tetapi tar xfz bar.tar.gz tidak), ini tidak terjadi di sini (lihat contoh selanjutnya).

Opsi berganda dengan argumen

Sebagai bonus lain, parameter opsi dikonsumsi dalam urutan opsi oleh parameter dengan opsi yang diperlukan. Lihat saja hasil skrip saya di sini dengan baris perintah abc X Y Z (atau -abc X Y Z):

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

Opsi panjang juga digabungkan

Anda juga dapat memiliki opsi panjang di blok opsi mengingat bahwa mereka terjadi terakhir di blok. Jadi baris perintah berikut semuanya setara (termasuk urutan di mana opsi dan argumennya sedang diproses):

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

Semua ini mengarah ke:

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

Tidak dalam solusi ini

Argumen opsional

Opsi dengan argumen opsional harus dimungkinkan dengan sedikit kerja, mis. dengan melihat ke depan apakah ada blok tanpa tanda hubung; pengguna kemudian harus meletakkan tanda hubung di depan setiap blok mengikuti blok dengan parameter yang memiliki parameter opsional. Mungkin ini terlalu rumit untuk berkomunikasi dengan pengguna jadi lebih baik hanya memerlukan tanda hubung terkemuka sama sekali dalam kasus ini.

Banyak hal menjadi lebih rumit dengan beberapa parameter yang mungkin. Saya menyarankan agar Anda tidak membuat opsi yang mencoba menjadi pintar dengan menentukan apakah argumennya cocok atau tidak (mis. Dengan opsi hanya menggunakan angka sebagai argumen opsional) karena ini mungkin pecah di masa mendatang.

Saya pribadi menyukai opsi tambahan daripada argumen opsional.

Argumen opsi diperkenalkan dengan tanda sama dengan

Sama seperti dengan argumen opsional, saya bukan penggemar ini (BTW, apakah ada utas untuk membahas pro/kontra dari berbagai gaya parameter?) Tetapi jika Anda menginginkan ini, Anda mungkin dapat menerapkannya sendiri seperti yang dilakukan di http: //mywiki.wooledge.org/BashFAQ/035#Manual_loop dengan pernyataan kasus --long-with-arg=?* dan kemudian membuang tanda sama dengan (ini adalah situs BTW yang mengatakan bahwa membuat penggabungan parameter dimungkinkan dengan beberapa upaya tetapi "biarkan [sebagai] latihan untuk pembaca "yang membuat saya membawa mereka pada Firman mereka tetapi saya mulai dari awal).

Catatan lain

Sesuai dengan POSIX, bekerja bahkan pada pengaturan Busybox kuno yang harus saya tangani (mis. cut, head dan getopts hilang).

1
phk

Ini juga mungkin berguna untuk diketahui, Anda dapat menetapkan nilai dan jika seseorang memberikan input, ganti default dengan nilai itu. 

myscript.sh -f ./serverlist.txt atau hanya ./myscript.sh (dan perlu default)

    #!/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}"
1
Mike Q

Inilah solusi saya yang lebih baik dari jawaban Bruno Bronosky menggunakan array variabel.

itu memungkinkan Anda mencampur posisi parameter dan memberi Anda array parameter mempertahankan pesanan tanpa opsi

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

Akan menampilkan misalnya:

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

Memperluas jawaban @ bruno-bronosky, saya menambahkan "preprocessor" untuk menangani beberapa format umum:

  • Perluas --longopt=val menjadi --longopt val
  • Perluas -xyz menjadi -x -y -z
  • Mendukung -- untuk mengindikasikan akhir flag
  • Menunjukkan kesalahan untuk opsi yang tidak terduga
  • Beralih opsi yang ringkas dan mudah dibaca
#!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename $0) [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "$1" ]; then
    exit "$1"
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg="$1"; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg"); END_OF_OPT=1 ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}${1}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD="$1" ;;
    -u|--username)  shift; USERNAME="$1" ;;
    -n|--name)      shift; names+=("$1") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "$1" ;;
    *)              POSITIONAL+=("$1") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"
1
jchook

Contoh ini menunjukkan cara menggunakan getopt dan eval dan HEREDOC dan shift untuk menangani parameter pendek dan panjang dengan dan tanpa nilai yang diperlukan berikut. Juga pernyataan switch/casenya ringkas dan mudah diikuti.

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

Baris paling penting dari skrip di atas adalah ini:

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

Singkat, to the point, dapat dibaca, dan menangani hampir semua hal (IMHO).

Semoga itu bisa membantu seseorang.

1
phyatt

Gunakan "argumen" modul dari bash-modules

Contoh:

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

Saya ingin mengirimkan proyek saya: https://github.com/flyingangel/argparser

source argparser.sh
parse_args "[email protected]"

Sederhana seperti itu. Lingkungan akan diisi dengan variabel dengan nama yang sama dengan argumen

1
Thanh Trung

Jawaban teratas untuk pertanyaan ini tampak agak bermasalah ketika saya mencobanya - inilah solusi saya yang saya temukan lebih kuat:

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
1
Daniel Bigham

Sederhana dan mudah dimodifikasi, parameter bisa dalam urutan apa pun. ini dapat dimodifikasi untuk mengambil parameter dalam bentuk apa pun (-a, --a, a, dll).

for arg in "[email protected]"
do
   key=$(echo $arg | cut -f1 -d=)`
   value=$(echo $arg | cut -f2 -d=)`
   case "$key" in
        name|-name)      read_name=$value;;
        id|-id)          read_id=$value;;
        *)               echo "I dont know what to do with this"
   ease
done
0
terijo001