Sử dụng getopts để xử lý các tùy chọn dòng lệnh dài và ngắn


410

Tôi muốn có các hình thức tùy chọn dòng lệnh dài và ngắn được gọi bằng cách sử dụng tập lệnh shell của tôi.

Tôi biết điều đó getoptscó thể được sử dụng, nhưng giống như trong Perl, tôi đã không thể làm điều tương tự với shell.

Mọi ý tưởng về cách này có thể được thực hiện, để tôi có thể sử dụng các tùy chọn như:

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

Ở trên, cả hai lệnh đều có ý nghĩa giống nhau với shell của tôi, nhưng khi sử dụng getopts, tôi không thể thực hiện chúng?


2
IMHO, câu trả lời được chấp nhận không phải là câu trả lời tốt nhất. Nó không chỉ ra cách sử dụng getopts để xử lý cả hai đối số "-" và "-", có thể được thực hiện, như @Arvid Requate đã trình bày. Tôi đang chèn một câu trả lời khác bằng cách sử dụng khái niệm tương tự, nhưng cũng xử lý lỗi "quên" của người dùng để chèn giá trị cho các đối số cần thiết. Điểm chính: getopts có thể được thực hiện để làm việc. Người dùng nên tránh sử dụng "getopt" thay vào đó nếu cần tính di động đa nền tảng. Ngoài ra, getopts là một phần của tiêu chuẩn POSIX cho đạn pháo, vì vậy nó có khả năng là hàng xách tay.
pauljohn32

Câu trả lời:


304

Có ba triển khai có thể được xem xét:

  • Bash dựng sẵn getopts. Điều này không hỗ trợ tên tùy chọn dài với tiền tố dấu gạch ngang kép. Nó chỉ hỗ trợ các tùy chọn ký tự đơn.

  • BSD UNIX thực hiện getoptlệnh độc lập (đó là những gì MacOS sử dụng). Điều này cũng không hỗ trợ các tùy chọn dài.

  • GNU triển khai độc lập getopt. GNU getopt(3)(được sử dụng bởi dòng lệnh getopt(1)trên Linux) hỗ trợ phân tích các tùy chọn dài.


Một số câu trả lời khác cho thấy một giải pháp cho việc sử dụng bash dựng sẵn getoptsđể bắt chước các tùy chọn dài. Giải pháp đó thực sự tạo ra một tùy chọn ngắn có ký tự là "-". Vì vậy, bạn nhận được "-" là cờ. Sau đó, bất cứ điều gì tiếp theo sẽ trở thành OPTARG và bạn kiểm tra OPTARG bằng một cái lồng case.

Điều này là thông minh, nhưng nó đi kèm với hãy cẩn thận:

  • getoptskhông thể thực thi thông số opt. Nó không thể trả về lỗi nếu người dùng cung cấp tùy chọn không hợp lệ. Bạn phải tự kiểm tra lỗi khi phân tích cú pháp OPTARG.
  • OPTARG được sử dụng cho tên tùy chọn dài, làm phức tạp việc sử dụng khi bản thân tùy chọn dài của bạn có đối số. Cuối cùng, bạn phải tự viết mã đó như một trường hợp bổ sung.

Vì vậy, trong khi có thể viết thêm mã để giải quyết vấn đề thiếu hỗ trợ cho các tùy chọn dài, thì đây là công việc nhiều hơn và một phần đánh bại mục đích sử dụng trình phân tích cú pháp getopt để đơn giản hóa mã của bạn.


18
Vì thế. Giải pháp đa nền tảng, di động là gì?
troelskn

6
GNU Getopt dường như là lựa chọn duy nhất. Trên Mac, cài đặt GNU getopt từ macports. Trên Windows, tôi sẽ cài đặt GNU getopt với Cygwin.
Bill Karwin

2
Rõ ràng , ksh getopts có thể xử lý các tùy chọn dài.
Tgr

1
@Bill +1, mặc dù cũng khá đơn giản để xây dựng getopt từ nguồn ( software.frodo.looijaard.name/getopt ) trên Mac. Bạn cũng có thể kiểm tra phiên bản getopt được cài đặt trên hệ thống của bạn từ bên trong các tập lệnh với "getopt -T; echo $?".
Chinasaur

8
@Bill Karwin: "Nội dung bash getopts không hỗ trợ tên tùy chọn dài với tiền tố dấu gạch ngang kép." Nhưng getopts có thể được thực hiện để hỗ trợ các tùy chọn dài: xem stackoverflow.com/a/7680682/915044 bên dưới.
TomRoche

305

getoptgetoptslà những con thú khác nhau, và mọi người dường như có một chút hiểu lầm về những gì họ làm. getoptslà một lệnh tích hợp bashđể xử lý các tùy chọn dòng lệnh trong một vòng lặp và lần lượt gán từng tùy chọn và giá trị tìm thấy cho các biến tích hợp, do đó bạn có thể xử lý chúng thêm. getopttuy nhiên, là một chương trình tiện ích bên ngoài và nó không thực sự xử lý các tùy chọn của bạn cho bạn theo cách mà ví dụ như bash getopts, Getoptmô-đun Perl hoặc Python optparse/ argparsemô-đun làm. Tất cả những gì getoptlàm là hợp thức hóa các tùy chọn được truyền vào - tức là chuyển đổi chúng sang dạng chuẩn hơn, để kịch bản shell xử lý chúng dễ dàng hơn. Ví dụ: một ứng dụng getoptcó thể chuyển đổi như sau:

myscript -ab infile.txt -ooutfile.txt

vào đây:

myscript -a -b -o outfile.txt infile.txt

Bạn phải tự xử lý thực tế. Bạn hoàn toàn không phải sử dụng getoptnếu bạn thực hiện nhiều hạn chế khác nhau về cách bạn có thể chỉ định các tùy chọn:

  • chỉ đặt một tùy chọn cho mỗi đối số;
  • tất cả các tùy chọn đi trước bất kỳ tham số vị trí nào (tức là đối số không phải tùy chọn);
  • đối với các tùy chọn có giá trị (ví dụ -oở trên), giá trị phải đi như một đối số riêng biệt (sau một khoảng trắng).

Tại sao sử dụng getoptthay vì getopts? Lý do cơ bản là chỉ GNU getoptcung cấp cho bạn hỗ trợ cho các tùy chọn dòng lệnh được đặt tên dài. 1 (GNU getoptlà mặc định trên Linux. Mac OS X và FreeBSD đi kèm với một cơ bản và không hữu ích lắm getopt, nhưng phiên bản GNU có thể được cài đặt; xem bên dưới.)

Ví dụ: đây là một ví dụ về việc sử dụng GNU getopt, từ một tập lệnh của tôi được gọi là javawrap:

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Điều này cho phép bạn chỉ định các tùy chọn như --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"hoặc tương tự. Tác dụng của lệnh gọi getoptlà hợp thức hóa các tùy chọn để --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"bạn có thể dễ dàng xử lý chúng hơn. Việc trích dẫn xung quanh "$1""$2"rất quan trọng vì nó đảm bảo rằng các đối số có khoảng trắng trong chúng được xử lý đúng cách.

Nếu bạn xóa 9 dòng đầu tiên (mọi thứ qua eval setdòng), mã sẽ vẫn hoạt động ! Tuy nhiên, mã của bạn sẽ được lựa chọn nhiều hơn trong các loại tùy chọn mà nó chấp nhận: Cụ thể, bạn sẽ phải chỉ định tất cả các tùy chọn trong biểu mẫu "chính tắc" được mô tả ở trên. getoptTuy nhiên, với việc sử dụng , bạn có thể nhóm các tùy chọn một chữ cái, sử dụng các hình thức tùy chọn dài không mơ hồ ngắn hơn, sử dụng --file foo.txthoặc --file=foo.txtkiểu, sử dụng -m 4096hoặc -m4096kiểu, trộn tùy chọn và không tùy chọn theo bất kỳ thứ tự nào, v.v. getoptcũng đưa ra một thông báo lỗi nếu không tìm thấy tùy chọn không rõ ràng hoặc mơ hồ.

LƯU Ý : Thực tế có hai phiên bản hoàn toàn khác nhaugetopt , cơ bản getoptvà GNU getopt, với các tính năng khác nhau và các quy ước gọi khác nhau. 2 Basic getoptkhá bị hỏng: Không chỉ không xử lý các tùy chọn dài, thậm chí nó còn không thể xử lý các không gian được nhúng bên trong các đối số hoặc đối số trống, trong khi đó getoptsthực hiện đúng. Các mã trên sẽ không hoạt động trong cơ bản getopt. GNU getoptđược cài đặt theo mặc định trên Linux, nhưng trên Mac OS X và FreeBSD, nó cần được cài đặt riêng. Trên Mac OS X, cài đặt MacPorts ( http://www.macports.org ) và sau đó thực hiện sudo port install getoptđể cài đặt GNU getopt(thường vào /opt/local/bin) và đảm bảo rằng đó /opt/local/binlà trong đường dẫn shell của bạn trước/usr/bin. Trên FreeBSD, cài đặt misc/getopt.

Hướng dẫn nhanh để sửa đổi mã ví dụ cho chương trình của riêng bạn: Trong vài dòng đầu tiên, tất cả là "mẫu soạn sẵn" nên giữ nguyên, ngoại trừ dòng gọi getopt. Bạn nên thay đổi tên chương trình sau -n, chỉ định các tùy chọn ngắn sau -ovà tùy chọn dài sau --long. Đặt dấu hai chấm sau các tùy chọn có giá trị.

Cuối cùng, nếu bạn thấy mã chỉ setthay vì eval set, nó được viết cho BSD getopt. Bạn nên thay đổi nó để sử dụng eval setkiểu, hoạt động tốt với cả hai phiên bản getopt, trong khi đồng bằng setkhông hoạt động đúng với GNU getopt.

1 Trên thực tế, getoptstrong ksh93hỗ trợ các tùy chọn được đặt tên dài, nhưng lớp vỏ này không được sử dụng thường xuyên như bash. Trong zsh, sử dụng zparseoptsđể có được chức năng này.

2 Về mặt kỹ thuật, "GNU getopt" là một cách viết sai; phiên bản này thực sự được viết cho Linux chứ không phải là dự án GNU. Tuy nhiên, nó tuân theo tất cả các quy ước GNU và thuật ngữ "GNU getopt" thường được sử dụng (ví dụ trên FreeBSD).


3
Điều này rất hữu ích, ý tưởng sử dụng getopt để kiểm tra các tùy chọn và sau đó xử lý các tùy chọn đó trong một vòng lặp rất đơn giản hoạt động thực sự tốt khi tôi muốn thêm các tùy chọn kiểu dài vào tập lệnh bash. Cảm ơn.
ianmjones

2
getopttrên Linux không phải là tiện ích GNU và truyền thống getoptban đầu không đến từ BSD mà là từ AT & T Unix. ksh93's getopts(cũng từ AT & T) hỗ trợ các tùy chọn dài kiểu GNU.
Stephane Chazelas

@StephaneChazelas - chỉnh sửa để phản ánh ý kiến ​​của bạn. Tôi vẫn thích thuật ngữ "GNU getopt" mặc dù đó là một cách gọi sai, bởi vì phiên bản này tuân theo các quy ước GNU và thường hoạt động như một chương trình GNU (ví dụ: sử dụng POSIXLY_CORRECT), trong khi "getopt được tăng cường Linux" cho rằng phiên bản này chỉ tồn tại trên Linux.
Thành phố Vagabond

1
Nó xuất phát từ gói linux-linux, do đó, Linux chỉ là gói phần mềm chỉ dành cho Linux ( getoptcó thể dễ dàng chuyển sang các Unice khác, nhưng nhiều phần mềm khác util-linuxdành riêng cho Linux). Tất cả các chương trình không phải GNU sử dụng GNU getopt (3) đều hiểu $POSIX_CORRECT. Chẳng hạn, bạn sẽ không nói đó aplaylà GNU chỉ với những lý do đó. Tôi nghi ngờ rằng khi FreeBSD đề cập đến GNU getopt, họ có nghĩa là API GNU getopt (3).
Stephane Chazelas

@StephaneChazelas - FreeBSD có thông báo lỗi "Xây dựng phụ thuộc: Vui lòng cài đặt GNU getopt" rõ ràng đề cập đến việc sử dụng getopt, không phải getopt (3).
Đô thị Vagabond

202

Hàm getopts Bash dựng sẵn có thể được sử dụng để phân tích các tùy chọn dài bằng cách đặt một ký tự gạch ngang theo sau là dấu hai chấm vào optspec:

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

Sau khi sao chép vào tên tệp thực thi = getopts_test.shtrong thư mục làm việc hiện tại , người ta có thể tạo đầu ra như

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

Rõ ràng các getopts không thực hiện OPTERRkiểm tra cũng như phân tích đối số tùy chọn cho các tùy chọn dài. Đoạn script trên cho thấy cách này có thể được thực hiện thủ công. Nguyên tắc cơ bản cũng hoạt động trong hệ vỏ Debian Almquist ("dash"). Lưu ý trường hợp đặc biệt:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

Lưu ý rằng, như GreyCat từ tại http://mywiki.wooledge.org/BashFAQ chỉ ra, thủ thuật này khai thác một hành vi không chuẩn của trình bao cho phép đối số tùy chọn (ví dụ tên tệp trong "tên tệp -f") được nối với tùy chọn (như trong "-ffilename"). Các POSIX tiêu chuẩn nói thì phải có một không gian giữa chúng, mà trong trường hợp "- longoption" sẽ chấm dứt tùy chọn-phân tích và biến tất cả longoptions vào lập luận phi tùy chọn.


2
Một câu hỏi: ngữ nghĩa của !trong là val="${!OPTIND}gì?
TomRoche

2
@TomRoche đó là Thay thế gián tiếp: unix.stackexchange.com/a/41293/84316
ecbrodie

2
@ecbrodie: Đó là bởi vì hai đối số đã thực sự được xử lý, trái ngược với chỉ một. Đối số đầu tiên là từ "loglevel" và tiếp theo là đối số cho đối số đó . Trong khi đó, getoptstự động chỉ tăng lên OPTIND1, nhưng trong trường hợp của chúng tôi, chúng tôi cần tăng lên 2, vì vậy chúng tôi tăng nó lên 1 bằng tay, sau đó getoptstự động tăng thêm 1 lần nữa cho chúng tôi.
Victor Zamanian

3
Vì chúng ta đang ở trạng thái cân bằng bash ở đây: Tên biến trần trụi được phép bên trong các biểu thức số học, không $cần thiết. OPTIND=$(( $OPTIND + 1 ))có thể chỉ OPTIND=$(( OPTIND + 1 )). Thú vị hơn nữa, bạn thậm chí có thể gán và tăng các biến trong biểu thức số học, do đó có thể viết tắt nó hơn nữa : $(( ++OPTIND ))hoặc thậm chí (( ++OPTIND ))có tính đến điều đó++OPTIND sẽ luôn tích cực, do đó, nó sẽ không gặp sự cố khi chạy -etùy chọn. :-) gnu.org/software/bash/manual/html_node/Shell-Arithatures.html
clacke

3
Tại sao không --very-badđưa ra cảnh báo?
Tom Hale

148

Lệnh tích hợp getoptsvẫn là, AFAIK, chỉ giới hạn ở các tùy chọn một ký tự.

Có (hoặc đã từng) một chương trình bên ngoài getoptsẽ tổ chức lại một tập hợp các tùy chọn sao cho dễ phân tích cú pháp hơn. Bạn có thể điều chỉnh thiết kế đó để xử lý các tùy chọn dài quá. Ví dụ sử dụng:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

Bạn có thể sử dụng một sơ đồ tương tự với một getoptlonglệnh.

Lưu ý rằng điểm yếu cơ bản với getoptchương trình bên ngoài là khó xử lý các đối số có khoảng trắng trong chúng và trong việc bảo tồn các khoảng trống đó một cách chính xác. Đây là lý do tại sao tích hợp getoptslà vượt trội, mặc dù bị hạn chế bởi thực tế là nó chỉ xử lý các tùy chọn một chữ cái.


11
getopt, ngoại trừ phiên bản GNU (có quy ước gọi khác), về cơ bản bị phá vỡ. Đừng sử dụng nó. Vui lòng sử dụng getopts ** thay bash-hackers.org/wiki/doku.php/howto/getopts_tutorial
Hendry

9
@hendry - từ liên kết của riêng bạn: "Lưu ý rằng các getopts không thể phân tích các tùy chọn dài theo kiểu GNU (--myoption) hoặc các tùy chọn dài theo kiểu XF86 (-myoption)!"
Tom Auger

1
Jonathan - bạn nên viết lại ví dụ để sử dụng eval setvới dấu ngoặc kép (xem câu trả lời của tôi bên dưới) để nó cũng hoạt động chính xác với GNU getopt (mặc định trên Linux) và xử lý khoảng trắng chính xác.
Thành phố Vagabond

@UrbanVagabond: Tôi không chắc tại sao tôi nên làm điều đó. Câu hỏi được gắn thẻ Unix, không phải Linux. Tôi đang cố tình hiển thị cơ chế truyền thống và nó có vấn đề với các khoảng trống trong các đối số, v.v. Bạn có thể chứng minh phiên bản dành riêng cho Linux hiện đại nếu bạn muốn và câu trả lời của bạn thực hiện điều đó. (Tôi lưu ý, passim, rằng việc sử dụng của bạn ${1+"$@"}là kỳ quặc và mâu thuẫn với những gì cần thiết trong các shell hiện đại và cụ thể với bất kỳ shell nào bạn tìm thấy trên Linux. Xem Sử dụng $ 1: + "$ @"} trong / bin / sh cho a thảo luận về ký hiệu đó.)
Jonathan Leffler

Bạn nên làm điều đó bởi vì eval setthực hiện đúng với cả GNU và BSD getopt, trong khi đơn giản setchỉ làm điều đúng với BSD getopt. Vì vậy, bạn cũng có thể sử dụng eval setđể khuyến khích mọi người có thói quen làm điều này. Cảm ơn BTW, tôi đã không nhận ra rằng ${1+"$@"}không cần thiết nữa. Tôi phải viết những thứ hoạt động cả trên Mac OS X và Linux - giữa hai trong số chúng có rất nhiều tính di động. Tôi chỉ cần kiểm tra và "$@"không thực sự làm điều đúng trên tất cả các sh, bash, ksh, và zshdưới Mac OS X; chắc chắn dưới Linux cũng vậy.
Đô thị Vagabond

78

Đây là một ví dụ thực sự sử dụng getopt với các tùy chọn dài:

aflag=no
bflag=no
cargument=none

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

1
Bạn nên viết lại ví dụ để sử dụng eval setvới dấu ngoặc kép (xem câu trả lời của tôi bên dưới) để nó cũng hoạt động chính xác với GNU getopt (mặc định trên Linux) và xử lý các khoảng trắng chính xác.
Thành phố Vagabond

2
Điều này được sử dụng getopttrong khi câu hỏi là về getoptsmặc dù.
Niklas Berglund

1
(--, (-*(*các mẫu hợp lệ? Chúng khác nhau như thế nào --, -**?
Maëlan

1
@ Maëlan - Dấu ngoặc đơn mở hàng đầu là tùy chọn, do đó (--)giống hệt --)trong khổ casethơ. Thật kỳ quặc khi thấy sự thụt lề không đồng đều và sử dụng không nhất quán của các mệnh đề hàng đầu tùy chọn đó, nhưng mã hiện tại của câu trả lời có vẻ hợp lệ đối với tôi.
Adam Katz

59

Tùy chọn dài có thể được phân tích bởi các tiêu chuẩn getoptsđược xây dựng trong là “lập luận” vào -“tùy chọn”

Đây là vỏ POSIX di động và bản địa - không cần chương trình bên ngoài hoặc bashism.

Hướng dẫn này thực hiện các tùy chọn dài làm đối số cho -tùy chọn, do đó, --alphađược xem getoptsnhư -với đối số alpha--bravo=foođược xem như -với đối số bravo=foo. Đối số thực sự có thể được thu hoạch với một sự thay thế đơn giản : ${OPTARG#*=}.

Trong ví dụ này, -b-c(và các dạng dài của chúng, --bravo--charlie) có các đối số bắt buộc. Các đối số cho các tùy chọn dài xuất hiện sau các dấu bằng, ví dụ --bravo=foo(các dấu phân cách không gian cho các tùy chọn dài sẽ khó thực hiện, xem bên dưới).

Bởi vì này sử dụng getoptsđược xây dựng trong , điều này sử dụng giải pháp hỗ trợ như cmd --bravo=foo -ac FILE(mà đã kết hợp tùy chọn -a-cvà interleaves tùy chọn dài với các tùy chọn tiêu chuẩn) trong khi hầu hết câu trả lời khác ở đây, hoặc đấu tranh hoặc không làm điều đó.

die() { echo "$*" >&2; exit 2; }  # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

while getopts ab:c:-: OPT; do
  # support long options: https://stackoverflow.com/a/28466267/519360
  if [ "$OPT" = "-" ]; then   # long option: reformulate OPT and OPTARG
    OPT="${OPTARG%%=*}"       # extract long option name
    OPTARG="${OPTARG#$OPT}"   # extract long option argument (may be empty)
    OPTARG="${OPTARG#=}"      # if long option argument, remove assigning `=`
  fi
  case "$OPT" in
    a | alpha )    alpha=true ;;
    b | bravo )    needs_arg; bravo="$OPTARG" ;;
    c | charlie )  needs_arg; charlie="$OPTARG" ;;
    ??* )          die "Illegal option --$OPT" ;;  # bad long option
    \? )           exit 2 ;;  # bad short option (error reported via getopts)
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

Khi tùy chọn là dấu gạch ngang ( -), đó là một tùy chọn dài. getoptssẽ phân tích tùy chọn dài thực tế thành $OPTARG, ví dụ như --bravo=foobộ ban đầu OPT='-'OPTARG='bravo=foo'. Các khổ ifthơ đặt $OPTthành nội dung $OPTARGtrước dấu bằng đầu tiên ( bravotrong ví dụ của chúng tôi) và sau đó loại bỏ từ đầu $OPTARG(thu được =footrong bước này hoặc một chuỗi trống nếu không có =). Cuối cùng, chúng tôi loại bỏ hàng đầu của đối số =. Tại thời điểm này, $OPTlà tùy chọn ngắn (một ký tự) hoặc tùy chọn dài (2+ ký tự).

Sau caseđó phù hợp với các tùy chọn ngắn hoặc dài. Đối với các tùy chọn ngắn, getoptstự động phàn nàn về các tùy chọn và các đối số bị thiếu, vì vậy chúng tôi phải sao chép các tùy chọn đó bằng cách sử dụng needs_arghàm, thoát ra một cách nghiêm trọng khi $OPTARGtrống. Điều ??*kiện sẽ khớp với bất kỳ tùy chọn dài nào còn lại ( ?khớp với một ký tự và *khớp từ 0 trở lên, do đó ??*khớp với 2+ ký tự), cho phép chúng tôi đưa ra lỗi "Tùy chọn bất hợp pháp" trước khi thoát.

(Lưu ý về tất cả chữ hoa tên biến:. Nói chung, lời khuyên là để dành tất cả chữ hoa biến để sử dụng hệ thống tôi đang giữ $OPTnhư tất cả chữ hoa để giữ cho nó phù hợp với $OPTARG., Nhưng điều này không phá vỡ mà ước tôi nghĩ rằng nó phù hợp bởi vì đây là điều mà hệ thống nên làm và nó phải an toàn vì không có tiêu chuẩn (afaik) nào sử dụng biến như vậy.)


Để phàn nàn về các đối số không mong muốn đối với các tùy chọn dài, hãy bắt chước những gì chúng tôi đã làm cho các đối số bắt buộc: sử dụng hàm trợ giúp. Chỉ cần lật bài kiểm tra xung quanh để phàn nàn về một cuộc tranh cãi khi người ta không mong đợi:

no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }

Một phiên bản cũ hơn của câu trả lời này đã cố gắng chấp nhận các tùy chọn dài với các đối số được phân tách bằng dấu cách, nhưng nó không đáng tin cậy; getoptscó thể chấm dứt sớm với giả định rằng đối số nằm ngoài phạm vi của nó và việc tăng thủ công $OPTINDkhông hoạt động trong tất cả các hệ vỏ.

Điều này sẽ được thực hiện bằng một trong những kỹ thuật sau:

và sau đó kết luận với một cái gì đó như [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))


Giải pháp khép kín rất đẹp. Một câu hỏi: Vì letter-ckhông cần tranh luận, nên nó không đủ để sử dụng letter-c)?; sự *dư thừa
Philip Kearns

1
@Arne Đối số vị trí là UX xấu; chúng khó hiểu và các đối số tùy chọn khá lộn xộn. getoptsdừng lại ở đối số vị trí đầu tiên vì nó không được thiết kế để đối phó với chúng. Điều này cho phép các lệnh phụ có các đối số của riêng chúng, ví dụ git diff --color, vì vậy tôi diễn giải command --foo=moo bar --baz wazlà có --foomột đối số command--baz waznhư là một đối số (có tùy chọn) cho lệnh barphụ. Điều này có thể được thực hiện với mã trên. Tôi từ chối --bravo -blah--bravoyêu cầu một đối số và không rõ đó -blahkhông phải là một lựa chọn khác.
Adam Katz

1
Tôi không đồng ý về UX: đối số vị trí rất hữu ích và dễ dàng, miễn là bạn giới hạn số lượng của chúng (tối đa là 2 hoặc 1 cộng với N-of-the-type). Có thể xen kẽ chúng với các đối số từ khóa, bởi vì người dùng sau đó có thể xây dựng một lệnh từng bước (tức là ls abc -la).
Arne Babenhauserheide

1
@AdamKatz: Tôi đã viết một bài viết nhỏ với điều này: Drainketo.de/english/free-software/shell-argument-parsing - bao gồm đọc lặp lại các đối số còn lại để bắt các tùy chọn theo dõi.
Arne Babenhauserheide

1
@ArneBabenhauserheide: Tôi đã cập nhật câu trả lời này để hỗ trợ các đối số được phân tách bằng dấu cách. Bởi vì nó yêu cầu evaltrong vỏ POSIX, nó được liệt kê bên dưới phần còn lại của câu trả lời.
Adam Katz

33

Hãy xem shFlags là một thư viện shell di động (có nghĩa là: sh, bash, dash, ksh, zsh trên Linux, Solaris, v.v.).

Nó làm cho việc thêm các cờ mới đơn giản như thêm một dòng vào tập lệnh của bạn và nó cung cấp chức năng sử dụng được tạo tự động.

Đây là một cách đơn giản Hello, world!bằng cách sử dụng shFlag :

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

Đối với các hệ điều hành có getopt nâng cao hỗ trợ các tùy chọn dài (ví dụ: Linux), bạn có thể thực hiện:

$ ./hello_world.sh --name Kate
Hello, Kate!

Đối với phần còn lại, bạn phải sử dụng tùy chọn ngắn:

$ ./hello_world.sh -n Kate
Hello, Kate!

Thêm một cờ mới cũng đơn giản như thêm một cờ mới DEFINE_ call.


2
Điều này thật tuyệt vời nhưng thật không may, getopt của tôi (OS X) không hỗ trợ các khoảng trắng trong các đối số: / tự hỏi liệu có cách nào khác không.
Alastair Stuart

@AlastairStuart - thực sự có một sự thay thế trên OS X. Sử dụng MacPorts để cài đặt GNU getopt (nó thường sẽ được cài đặt vào / opt / local / bin / getopt).
Urban Vagabond

3
@UrbanVagabond - việc cài đặt các công cụ mặc định không phải là hệ thống không may là một yêu cầu chấp nhận được đối với một công cụ di động đủ.
Alastair Stuart

@AlastairStuart - xem câu trả lời của tôi cho một giải pháp di động sử dụng getopts dựng sẵn thay vì GNU getopt. Nó giống như cách sử dụng getopts cơ bản nhưng có thêm một lần lặp cho các tùy chọn dài.
Adam Katz

31

Sử dụng getoptsvới các tùy chọn và đối số ngắn / dài


Hoạt động với tất cả các kết hợp, ví dụ:

  • foobar -f - thanh
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB --argument = longhorn
  • foobar -fA "văn bản rút gọn" -B --argument = "văn bản longhorn"
  • bash foobar -F --barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

Một số tuyên bố cho ví dụ này

Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

Chức năng sử dụng sẽ trông như thế nào

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
  $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                  -f   --foo            Set foo to yes    ($bart)
                  -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

getops với cờ dài / ngắn cũng như đối số dài

while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

Đầu ra

##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"

Kết hợp những điều trên thành một kịch bản gắn kết

#!/bin/bash
# foobar: getopts with short and long options AND arguments

function _cleanup ()
{
  unset -f _usage _cleanup ; return 0
}

## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN

###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
   $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                    -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

##################################################################    
#######  "getopts" with: short options  AND  long options  #######
#######            AND  short/long arguments               #######
while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
               --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

Điều này không hoạt động với nhiều hơn một đối số dài (-). Nó dường như chỉ đọc cái đầu tiên cho tôi.
Sinaesthetic

@ Thẩm mỹ - Vâng, tôi đã chơi với evalcách tiếp cận các đối số cách nhau trên các tùy chọn dài và thấy nó không đáng tin cậy với các shell nhất định (mặc dù tôi hy vọng nó sẽ hoạt động với bash, trong trường hợp bạn không phải sử dụng eval). Xem câu trả lời của tôi để biết cách chấp nhận các đối số tùy chọn dài với =và các nỗ lực được lưu ý của tôi để sử dụng không gian. Giải pháp của tôi không thực hiện cuộc gọi bên ngoài trong khi cuộc gọi này sử dụng cutmột vài lần.
Adam Katz

24

Cách khác...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done

1
Điều này có cần một không gian trong mỗi lần $argsgán lại không? Điều này thậm chí có thể được thực hiện với các bashism, nhưng mã này sẽ mất khoảng trắng trong các tùy chọn và đối số (tôi không nghĩ $delimthủ thuật sẽ hoạt động). Thay vào đó có thể chạy set bên trong các forvòng lặp nếu bạn đủ cẩn thận để trống nó trên chỉ phiên đầu tiên. Đây là một phiên bản an toàn hơn mà không có bashism.
Adam Katz

18

Tôi đã giải quyết theo cách này:

# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

Tôi bị câm hay sao? getoptgetoptsrất khó hiểu


1
Điều này có vẻ hiệu quả với tôi, tôi không biết vấn đề của phương pháp này là gì, nhưng nó có vẻ đơn giản, vì vậy phải có một lý do mà mọi người khác không sử dụng nó.
Billy Moon

1
@Billy Có, điều này đơn giản vì tôi không sử dụng bất kỳ tập lệnh nào để quản lý các tham số của mình và v.v. Về cơ bản, tôi chuyển đổi chuỗi đối số ($ @) thành một mảng và tôi lặp qua nó. Trong vòng lặp, giá trị hiện tại sẽ là khóa và giá trị tiếp theo sẽ là giá trị. Đơn giản như thế.

1
@Theodore Tôi rất vui vì điều này hữu ích cho bạn! Đó là một nỗi đau với tôi là tốt. Nếu bạn quan tâm, bạn có thể xem ví dụ về hoạt động tại đây: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh

2
Chắc chắn là cách dễ nhất tôi từng thấy. Tôi đã thay đổi nó một chút, chẳng hạn như sử dụng i = $ (($ i + 1)) thay vì expr nhưng khái niệm này rất kín.
Thomas Dignan

6
Bạn hoàn toàn không ngu ngốc, nhưng bạn có thể thiếu một tính năng: getopt (s) có thể nhận ra các tùy chọn được trộn lẫn (ví dụ: -ltrhoặc -lt -rcũng như -l -t -r). Và nó cũng cung cấp một số xử lý lỗi và một cách dễ dàng để chuyển các tham số đã xử lý đi sau khi điều trị tùy chọn kết thúc.
Olivier Dulac

14

Trong trường hợp bạn không muốn getoptphụ thuộc, bạn có thể làm điều này:

while test $# -gt 0
do
  case $1 in

  # Normal option processing
    -h | --help)
      # usage and help
      ;;
    -v | --version)
      # version info
      ;;
  # ...

  # Special cases
    --)
      break
      ;;
    --*)
      # error unknown (long) option $1
      ;;
    -?)
      # error unknown (short) option $1
      ;;

  # FUN STUFF HERE:
  # Split apart combined short options
    -*)
      split=$1
      shift
      set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

Tất nhiên, sau đó bạn không thể sử dụng các tùy chọn kiểu dài với một dấu gạch ngang. Và nếu bạn muốn thêm các phiên bản rút gọn (ví dụ: --verbos thay vì --verbose), thì bạn cần thêm các phiên bản thủ công.

Nhưng nếu bạn đang tìm kiếm để có được getoptschức năng cùng với các tùy chọn dài, đây là một cách đơn giản để làm điều đó.

Tôi cũng đặt đoạn trích này trong một ý chính .


Điều này dường như chỉ hoạt động với một tùy chọn dài tại một thời điểm, nhưng nó đáp ứng nhu cầu của tôi. Cảm ơn bạn!
kingjeffrey

Trong trường hợp đặc biệt --)dường như có một shift ;mất tích. Tại thời điểm này --sẽ vẫn là đối số tùy chọn đầu tiên.
dgw

Tôi nghĩ rằng đây thực sự là câu trả lời tốt hơn, mặc dù như dgw chỉ ra --tùy chọn cần có shifttrong đó. Tôi nói điều này tốt hơn bởi vì các lựa chọn thay thế là phiên bản phụ thuộc nền tảng getopthoặc getopts_longhoặc bạn phải buộc các tùy chọn ngắn chỉ được sử dụng khi bắt đầu lệnh (nghĩa là - bạn sử dụng getoptssau đó xử lý các tùy chọn dài sau đó), trong khi điều này đưa ra bất kỳ thứ tự nào và kiểm soát hoàn toàn.
Harastak

Câu trả lời này khiến tôi tự hỏi tại sao chúng ta có một chuỗi các câu trả lời để thực hiện công việc không thể làm gì hơn là giải pháp hoàn toàn rõ ràng và đơn giản này , và nếu có bất kỳ lý do nào cho hàng tỷ trường hợp sử dụng getopt ngoài việc chứng minh chính mình
Florian Heigl

11

Việc tích hợp getoptskhông thể làm điều này. Có một chương trình getopt bên ngoài (1) có thể thực hiện việc này, nhưng bạn chỉ nhận được nó trên Linux từ gói linux-linux . Nó đi kèm với một kịch bản ví dụ getopt-parse.bash .

Ngoài ra còn có một getopts_longvăn bản như là một hàm shell.


3
Bản getoptnày đã được đưa vào FreeBSD phiên bản 1.0 vào năm 1993 và là một phần của FreeBSD kể từ đó. Do đó, nó đã được thông qua từ FreeBSD 4.x để đưa vào dự án Darwin của Apple. Kể từ OS X 10.6.8, trang man được Apple đưa vào vẫn là một bản sao chính xác của trang man FreeBSD. Vì vậy, có, nó được bao gồm trong OS X và gobs của các hệ điều hành khác bên cạnh Linux. -1 về câu trả lời này cho thông tin sai lệch.
ghoti

8
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

.

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit

2
Một lời giải thích sẽ tốt đẹp. Tập lệnh đầu tiên chỉ chấp nhận các tùy chọn ngắn trong khi tập lệnh thứ hai có lỗi trong phân tích đối số tùy chọn dài; biến của nó phải "${1:0:1}"dành cho đối số # 1, chuỗi con ở chỉ số 0, độ dài 1. Điều này không cho phép trộn các tùy chọn ngắn và dài.
Adam Katz

7

Trong ksh93, getoptskhông hỗ trợ tên dài ...

while getopts "f(file):s(server):" flag
do
    echo "$flag" $OPTIND $OPTARG
done

Hoặc vì vậy các hướng dẫn tôi đã tìm thấy đã nói. Hãy thử nó và xem.


4
Đây là bản dựng sẵn của ksh93. Ngoài cú pháp này, nó còn có một cú pháp phức tạp hơn cũng cho phép các tùy chọn dài mà không có tương đương ngắn và hơn thế nữa.
jilles

2
Một câu trả lời hợp lý. OP không chỉ định vỏ WHAT.
ghoti

6

Tôi chỉ viết kịch bản shell bây giờ và sau đó ra khỏi thực tế, vì vậy bất kỳ thông tin phản hồi đều được đánh giá cao.

Sử dụng chiến lược được đề xuất bởi @Arvid Requate, chúng tôi nhận thấy một số lỗi người dùng. Người dùng quên bao gồm một giá trị sẽ vô tình có tên của tùy chọn tiếp theo được coi là một giá trị:

./getopts_test.sh --loglevel= --toc=TRUE

sẽ khiến giá trị của "loglevel" được xem là "--toc = TRUE". Điều này có thể tránh được.

Tôi đã điều chỉnh một số ý tưởng về việc kiểm tra lỗi người dùng cho CLI từ http://mwiki.wooledge.org/BashFAQ/035 thảo luận về phân tích cú pháp thủ công. Tôi đã chèn kiểm tra lỗi để xử lý cả hai đối số "-" và "-".

Sau đó, tôi bắt đầu loay hoay với cú pháp, vì vậy bất kỳ lỗi nào ở đây đều là lỗi của tôi, không phải của các tác giả gốc.

Cách tiếp cận của tôi giúp người dùng thích nhập lâu có hoặc không có dấu bằng. Đó là, nó sẽ có cùng phản hồi với "--loglevel 9" là "--loglevel = 9". Trong phương thức - / dấu cách, không thể biết chắc chắn liệu người dùng có quên đối số hay không, do đó cần có một số phỏng đoán.

  1. nếu người dùng có định dạng dấu dài / bằng (--opt =), thì khoảng trắng sau = sẽ gây ra lỗi vì không cung cấp đối số.
  2. nếu người dùng có các đối số dài / không gian (--opt), tập lệnh này gây ra lỗi nếu không có đối số nào theo sau (kết thúc lệnh) hoặc nếu đối số bắt đầu bằng dấu gạch ngang)

Trong trường hợp bạn đang bắt đầu về điều này, có một sự khác biệt thú vị giữa các định dạng "--opt = value" và "--opt value". Với dấu bằng, đối số dòng lệnh được xem là "opt = value" và công việc để xử lý đó là phân tích chuỗi, để phân tách tại "=". Ngược lại, với "--opt value", tên của đối số là "opt" và chúng tôi gặp khó khăn trong việc lấy giá trị tiếp theo được cung cấp trong dòng lệnh. Đó là nơi @Arvid Yêu cầu đã sử dụng $ {! OPTIND}, tham chiếu gián tiếp. Tôi vẫn không hiểu rằng, tất cả, và các bình luận trong BashFAQ dường như cảnh báo chống lại phong cách đó ( http://mywiki.wooledge.org/BashFAQ/006 ). BTW, tôi không nghĩ những nhận xét của người đăng trước về tầm quan trọng của OPTIND = $ (($ OPTIND + 1)) là chính xác. Ý tôi là nói

Trong phiên bản mới nhất của tập lệnh này, cờ -v có nghĩa là bản in ĐỘNG TỪ.

Lưu nó trong một tệp có tên "cli-5.sh", thực hiện và bất kỳ thứ nào trong số này sẽ hoạt động hoặc thất bại theo cách mong muốn

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

Dưới đây là ví dụ đầu ra của việc kiểm tra lỗi trên intpu người dùng

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

Bạn nên xem xét bật -v, vì nó in ra các phần bên trong của OPTIND và OPTARG

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


die() {
    printf '%s\n' "$1" >&2
    exit 1
}

printparse(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
    fi
}

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "$1" >&2;
    fi
}


VERBOSE=0
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"

OPTIND=$(( $OPTIND + 1 )): nó là cần thiết bất cứ khi nào bạn 'gobble' tham số của OPTIND (ví dụ: khi một --toc value giá trị được sử dụng : giá trị nằm trong số tham số $ OPTIND. Khi bạn truy xuất nó cho giá trị của toc, bạn nên thông báo cho getopts rằng tham số tiếp theo để phân tích không phải là giá trị, nhưng cái sau nó (vì vậy: OPTIND=$(( $OPTIND + 1 )) và tập lệnh của bạn (cũng như tập lệnh mà bạn đề cập) bị thiếu, sau khi thực hiện xong: shift $(( $OPTIND -1 ))(khi getopts thoát ra sau khi phân tích parameterrs 1 sang OPTIND-1, bạn cần chuyển chúng ra $@hiện là bất kỳ tham số "không tùy chọn" nào còn lại
Olivier Dulac

ồ, khi bạn tự thay đổi, bạn "dịch chuyển" các tham số bên dưới getopts, vì vậy, OPTIND luôn chỉ ra điều đúng ... nhưng tôi thấy nó rất khó hiểu. Tôi tin rằng (không thể kiểm tra tập lệnh của bạn ngay bây giờ) rằng bạn vẫn cần thay đổi $ (($ OPTIND - 1)) sau vòng lặp while, do đó, $ 1 bây giờ không trỏ đến $ 1 ban đầu (một tùy chọn) nhưng đến đầu tiên của các đối số còn lại (các đối số đến sau tất cả các tùy chọn và giá trị của chúng). ví dụ: myrm -foo -bar = baz thisarg thenthisone thenanother
Olivier Dulac

5

Phát minh ra một phiên bản khác của bánh xe ...

Chức năng này là một sự thay thế vỏ bourne đồng bằng tương thích POSIX (hy vọng) cho GNU getopt. Nó hỗ trợ các tùy chọn ngắn / dài có thể chấp nhận các đối số bắt buộc / tùy chọn / không có và cách thức mà các tùy chọn được chỉ định gần giống với GNU getopt, vì vậy chuyển đổi là không đáng kể.

Tất nhiên đây vẫn là một đoạn mã lớn để thả vào tập lệnh, nhưng nó chỉ bằng một nửa dòng của hàm shell getopt_long nổi tiếng và có thể thích hợp hơn trong trường hợp bạn chỉ muốn thay thế việc sử dụng GNU getopt hiện có.

Đây là mã khá mới, vì vậy YMMV (và chắc chắn vui lòng cho tôi biết nếu đây không thực sự tương thích POSIX vì bất kỳ lý do nào - tính di động là ý định ngay từ đầu, nhưng tôi không có môi trường kiểm tra POSIX hữu ích).

Sử dụng mã và ví dụ sau:

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

Ví dụ sử dụng:

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi

4

Câu trả lời được chấp nhận thực hiện một công việc rất hay là chỉ ra tất cả những thiếu sót của bash tích hợp getopts. Câu trả lời kết thúc bằng:

Vì vậy, trong khi có thể viết thêm mã để giải quyết vấn đề thiếu hỗ trợ cho các tùy chọn dài, thì đây là công việc nhiều hơn và một phần đánh bại mục đích sử dụng trình phân tích cú pháp getopt để đơn giản hóa mã của bạn.

Và mặc dù tôi đồng ý về nguyên tắc với tuyên bố đó, tôi cảm thấy rằng số lần tất cả chúng ta thực hiện tính năng này trong các tập lệnh khác nhau đều biện minh cho việc nỗ lực tạo ra một giải pháp được "chuẩn hóa".

Như vậy, tôi đã "nâng cấp" bash được xây dựng getoptsbằng cách triển khai getopts_longtrong bash thuần túy, không có phụ thuộc bên ngoài. Việc sử dụng chức năng tương thích 100% với tích hợpgetopts .

Bằng cách bao gồm getopts_long(được lưu trữ trên GitHub ) trong một tập lệnh, câu trả lời cho câu hỏi ban đầu có thể được thực hiện đơn giản như:

source "${PATH_TO}/getopts_long.bash"

while getopts_long ':c: copyfile:' OPTKEY; do
    case ${OPTKEY} in
        'c'|'copyfile')
            echo 'file supplied -- ${OPTARG}'
            ;;
        '?')
            echo "INVALID OPTION -- ${OPTARG}" >&2
            exit 1
            ;;
        ':')
            echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
            exit 1
            ;;
        *)
            echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
            exit 1
            ;;
    esac
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift

3

Tôi chưa có đủ đại diện để bình luận hoặc bình chọn giải pháp của anh ấy, nhưng câu trả lời của sme rất hiệu quả với tôi. Vấn đề duy nhất tôi gặp phải là các đối số cuối cùng được gói trong các dấu ngoặc đơn (vì vậy tôi có một dải chúng ra).

Tôi cũng đã thêm một số cách sử dụng ví dụ và văn bản TRỢ GIÚP. Tôi sẽ bao gồm phiên bản hơi mở rộng của tôi ở đây:

#!/bin/bash

# getopt example
# from: /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done

3

Ở đây bạn có thể tìm thấy một vài cách tiếp cận khác nhau để phân tích tùy chọn phức tạp trong bash: http://mywiki.wooledge.org/ComplexOptionParsing

Tôi đã tạo ra cái sau và tôi nghĩ nó là một cái tốt, bởi vì nó là mã tối thiểu và cả hai tùy chọn dài và ngắn đều hoạt động. Một tùy chọn dài cũng có thể có nhiều đối số với phương pháp này.

#!/bin/bash
# Uses bash extensions.  Not portable as written.

declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
    case "${opt}" in
        -) #OPTARG is name-of-long-option or name-of-long-option=value
            if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
            then
                opt=${OPTARG/=*/}
                OPTARG=${OPTARG#*=}
                ((OPTIND--))    
            else #with this --key value1 value2 format multiple arguments are possible
                opt="$OPTARG"
                OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
            fi
            ((OPTIND+=longoptspec[$opt]))
            continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
            ;;
        loglevel)
          loglevel=$OPTARG
            ;;
        h|help)
            echo "usage: $0 [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file

2

Tôi đã làm việc về chủ đề đó trong một thời gian dài ... và tạo ra thư viện của riêng mình mà bạn sẽ cần phải lấy nguồn trong kịch bản chính của mình. Xem libopt4shellcd2mpc để biết ví dụ. Hy vọng nó giúp !


2

Một giải pháp cải tiến:

# translate long options to short
# Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after  "--" in option fields.
for ((i=1;$#;i++)) ; do
    case "$1" in
        --)
            # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
            EndOpt=1 ;;&
        --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
        # default case : short option use the first char of the long option:
        --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
        # pass through anything else:
        *) args[$i]="$1" ;;
    esac
    shift
done
# reset the translated args
set -- "${args[@]}"

function usage {
echo "Usage: $0 [options] files" >&2
    exit $1
}

# now we can process with getopt
while getopts ":hvVc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        V)  echo $Version ; exit ;;
        c)  source $OPTARG ;;
        \?) echo "unrecognized option: -$opt" ; usage -1 ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage -1
        ;;
    esac
done

shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift

2

Có lẽ việc sử dụng ksh đơn giản hơn, chỉ dành cho phần getopts, nếu cần các tùy chọn dòng lệnh dài, vì nó có thể được thực hiện dễ dàng hơn ở đó.

# Working Getopts Long => KSH

#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done

+1 - Lưu ý rằng điều này được giới hạn ở ksh93 - từ dự án AST nguồn mở (Nghiên cứu AT & T).
Henk Langeveld

2

Tôi muốn một cái gì đó mà không phụ thuộc bên ngoài, với sự hỗ trợ bash nghiêm ngặt (-u) và tôi cần nó để hoạt động ngay cả các phiên bản bash cũ hơn. Điều này xử lý các loại params khác nhau:

  • phân ngắn (-h)
  • tùy chọn ngắn (-i "image.jpg")
  • bool dài (- trợ giúp)
  • bằng các tùy chọn (--file = "filename.ext")
  • tùy chọn không gian (--file "filename.ext")
  • phân kết hợp (-hvm)

Chỉ cần chèn phần sau vào đầu tập lệnh của bạn:

# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
  for param in $1 ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "$2" ]] ; then
        # Update the key to match the long version
        local arr=(${param//\|/ })
        let last=${#arr[@]}-1
        key="${arr[$last]}"
        return 0
      fi
    done
  done
  return 1
}

# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
#     # First, set your defaults
#     param_help=false
#     param_path="."
#     param_file=false
#     param_image=false
#     param_image_lossy=true
#     # Define allowed parameters
#     allowed_params="h|?|help p|path f|file i|image image-lossy"
#     # Get parameters from the arguments provided
#     _get_params $*
#
# Parameters will be converted into safe variable names like:
#     param_help,
#     param_path,
#     param_file,
#     param_image,
#     param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
#     -i "path/goes/here"
#     --image "path/goes/here"
#     --image="path/goes/here"
#     --image=path/goes/here
# These would all result in effectively the same thing:
#     param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
#     -vhm is the same as -v -h -m
_get_params(){

  local param_pair
  local key
  local value
  local shift_count

  while : ; do
    # Ensure we have a valid param. Allows this to work even in -u mode.
    if [[ $# == 0 || -z $1 ]] ; then
      break
    fi

    # Split the argument if it contains "="
    param_pair=(${1//=/ })
    # Remove preceeding dashes
    key="${param_pair[0]#--}"

    # Check for concatinated boolean short parameters.
    local nodash="${key#-}"
    local breakout=false
    if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
      # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
      local short_param_count=${#nodash}
      let new_arg_count=$#+$short_param_count-1
      local new_args=""
      # $str_pos is the current position in the short param string $nodash
      for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
        # The first character becomes the current key
        if [ $str_pos -eq 0 ] ; then
          key="${nodash:$str_pos:1}"
          breakout=true
        fi
        # $arg_pos is the current position in the constructed arguments list
        let arg_pos=$str_pos+1
        if [ $arg_pos -gt $short_param_count ] ; then
          # handle other arguments
          let orignal_arg_number=$arg_pos-$short_param_count+1
          local new_arg="${!orignal_arg_number}"
        else
          # break out our one argument into new ones
          local new_arg="-${nodash:$str_pos:1}"
        fi
        new_args="$new_args \"$new_arg\""
      done
      # remove the preceding space and set the new arguments
      eval set -- "${new_args# }"
    fi
    if ! $breakout ; then
      key="$nodash"
    fi

    # By default we expect to shift one argument at a time
    shift_count=1
    if [ "${#param_pair[@]}" -gt "1" ] ; then
      # This is a param with equals notation
      value="${param_pair[1]}"
    else
      # This is either a boolean param and there is no value,
      # or the value is the next command line argument
      # Assume the value is a boolean true, unless the next argument is found to be a value.
      value=true
      if [[ $# -gt 1 && -n "$2" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "$2" ]; then
          # The next argument has NO preceding dash so it is a value
          value="$2"
          shift_count=2
        fi
      fi
    fi

    # Check that the param being passed is one of the allowed params
    if _param_variant "$allowed_params" "$key" ; then
      # --key-name will now become param_key_name
      eval param_${key//-/_}="$value"
    else
      printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
    fi
    shift $shift_count
  done
}

Và sử dụng nó như vậy:

# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85

# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

# Get the params from arguments provided
_get_params $*

1

Để duy trì khả năng tương thích đa nền tảng và tránh sự phụ thuộc vào các tệp thực thi bên ngoài, tôi đã chuyển một số mã từ ngôn ngữ khác.

Tôi thấy nó rất dễ sử dụng, đây là một ví dụ:

ArgParser::addArg "[h]elp"    false    "This list"
ArgParser::addArg "[q]uiet"   false    "Supress output"
ArgParser::addArg "[s]leep"   1        "Seconds to sleep"
ArgParser::addArg "v"         1        "Verbose mode"

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

ArgParser::isset "quiet" \
   && echo "Quiet!" \
   || echo "Noisy!"

local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
   && echo "Sleep for $__sleep seconds" \
   || echo "No value passed for sleep"

# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"

BASH yêu cầu dài hơn một chút so với có thể, nhưng tôi muốn tránh phụ thuộc vào mảng kết hợp của BASH 4. Bạn cũng có thể tải xuống trực tiếp từ http://nt4.com/bash/argparser.inc.sh

#!/usr/bin/env bash

# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh

# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh

unset EXPLODED
declare -a EXPLODED
function explode 
{
    local c=$# 
    (( c < 2 )) && 
    {
        echo function "$0" is missing parameters 
        return 1
    }

    local delimiter="$1"
    local string="$2"
    local limit=${3-99}

    local tmp_delim=$'\x07'
    local delin=${string//$delimiter/$tmp_delim}
    local oldifs="$IFS"

    IFS="$tmp_delim"
    EXPLODED=($delin)
    IFS="$oldifs"
}


# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "$1" && upvar $1 "value(s)"
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
        fi
    fi
}

function decho
{
    :
}

function ArgParser::check
{
    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        matched=0
        explode "|" "${__argparser__arglist[$i]}"
        if [ "${#1}" -eq 1 ]
        then
            if [ "${1}" == "${EXPLODED[0]}" ]
            then
                decho "Matched $1 with ${EXPLODED[0]}"
                matched=1

                break
            fi
        else
            if [ "${1}" == "${EXPLODED[1]}" ]
            then
                decho "Matched $1 with ${EXPLODED[1]}"
                matched=1

                break
            fi
        fi
    done
    (( matched == 0 )) && return 2
    # decho "Key $key has default argument of ${EXPLODED[3]}"
    if [ "${EXPLODED[3]}" == "false" ]
    then
        return 0
    else
        return 1
    fi
}

function ArgParser::set
{
    key=$3
    value="${1:-true}"
    declare -g __argpassed__$key="$value"
}

function ArgParser::parse
{

    unset __argparser__argv
    __argparser__argv=()
    # echo parsing: "$@"

    while [ -n "$1" ]
    do
        # echo "Processing $1"
        if [ "${1:0:2}" == '--' ]
        then
            key=${1:2}
            value=$2
        elif [ "${1:0:1}" == '-' ]
        then
            key=${1:1}               # Strip off leading -
            value=$2
        else
            decho "Not argument or option: '$1'" >& 2
            __argparser__argv+=( "$1" )
            shift
            continue
        fi
        # parameter=${tmp%%=*}     # Extract name.
        # value=${tmp##*=}         # Extract value.
        decho "Key: '$key', value: '$value'"
        # eval $parameter=$value
        ArgParser::check $key
        el=$?
        # echo "Check returned $el for $key"
        [ $el -eq  2 ] && decho "No match for option '$1'" >&2 # && __argparser__argv+=( "$1" )
        [ $el -eq  0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true "${EXPLODED[@]}"
        [ $el -eq  1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of '$2'"   >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
        shift
    done
}

function ArgParser::isset
{
    declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0
    return 1
}

function ArgParser::getArg
{
    # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
    varname="__argpassed__$1"
    echo "${!varname}"
}

##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
    local __varname="__argpassed__$1"
    local __value="${!__varname}"
    test -z "$__value" && return 1
    local "$3" && upvar $3 "$__value"
    return 0
}

function ArgParser::__construct
{
    unset __argparser__arglist
    # declare -a __argparser__arglist
}

##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
    # check for short arg within long arg
    if [[ "$1" =~ \[(.)\] ]]
    then
        short=${BASH_REMATCH[1]}
        long=${1/\[$short\]/$short}
    else
        long=$1
    fi
    if [ "${#long}" -eq 1 ]
    then
        short=$long
        long=''
    fi
    decho short: "$short"
    decho long: "$long"
    __argparser__arglist+=("$short|$long|$1|$2|$3")
}

## 
# @brief show available command line arguments
##
function ArgParser::showArgs
{
    # declare -p | grep argparser
    printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
    printf "Defaults for the options are specified in brackets.\n\n";

    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        local shortname=
        local fullname=
        local default=
        local description=
        local comma=

        explode "|" "${__argparser__arglist[$i]}"

        shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: 
        fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
        test -n "$shortname" \
            && test -n "$fullname" \
            && comma=","

        default="${EXPLODED[3]}"
        case $default in
            false )
                default=
                ;;
            "" )
                default=
                ;;
            * )
                default="[$default]"
        esac

        description="${EXPLODED[4]}"

        printf "  %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
    done
}

function ArgParser::test
{
    # Arguments with a default of 'false' do not take paramaters (note: default
    # values are not applied in this release)

    ArgParser::addArg "[h]elp"      false       "This list"
    ArgParser::addArg "[q]uiet" false       "Supress output"
    ArgParser::addArg "[s]leep" 1           "Seconds to sleep"
    ArgParser::addArg "v"           1           "Verbose mode"

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset "quiet" \
        && echo "Quiet!" \
        || echo "Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
        && echo "Sleep for $__sleep seconds" \
        || echo "No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo "Sleep set to: $( ArgParser::getArg sleep )"

    echo "Remaining command line: ${__argparser__argv[@]}"

}

if [ "$( basename "$0" )" == "argparser.inc.sh" ]
then
    ArgParser::test "$@"
fi

1

Nếu tất cả các tùy chọn dài của bạn có duy nhất và khớp, các ký tự đầu tiên là các tùy chọn ngắn, chẳng hạn,

./slamm --chaos 23 --plenty test -quiet

Giống như

./slamm -c 23 -p test -q

Bạn có thể sử dụng điều này trước khi getopts để viết lại $ args:

# change long options to short options

for arg; do 
    [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
    if [ "${arg:0:2}" == "--" ]; 
       then args="${args} -${arg:2:1}" 
       else args="${args} ${delim}${arg}${delim}"
    fi
done

# reset the incoming args
eval set -- $args

# proceed as usual
while getopts ":b:la:h" OPTION; do
    .....

Cảm ơn mtvee cho cảm hứng ;-)


Tôi không nhận được tầm quan trọng của eval ở đây
user.friendly

1

nếu chỉ đơn giản đây là cách bạn muốn gọi kịch bản

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"

sau đó bạn có thể làm theo cách đơn giản nhất này để đạt được nó với sự trợ giúp của getopt và --longoptions

Hãy thử nó, hy vọng điều này là hữu ích

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
)


echo $opts

eval set --$opts

while true; do
    case "$1" in
    --input1)  
        shift
        empId=$1
        ;;
    --input2)  
        shift
        fromDate=$1
        ;;
    --input3)  
        shift
        toDate=$1
        ;;
      --)
        shift
        break
        ;;
    esac
    shift
done

0

getopts "có thể được sử dụng" để phân tích các tùy chọn dài miễn là bạn không mong đợi chúng có đối số ...

Đây là cách để:

$ cat > longopt
while getopts 'e:-:' OPT; do
  case $OPT in
    e) echo echo: $OPTARG;;
    -) #long option
       case $OPTARG in
         long-option) echo long option;;
         *) echo long option: $OPTARG;;
       esac;;
  esac
done

$ bash longopt -e asd --long-option --long1 --long2 -e test
echo: asd
long option
long option: long1
long option: long2
echo: test

Nếu bạn cố gắng sử dụng OPTIND để nhận tham số cho tùy chọn dài, getopts sẽ coi nó là tham số vị trí tùy chọn đầu tiên và sẽ ngừng phân tích bất kỳ tham số nào khác. Trong trường hợp như vậy, bạn sẽ xử lý thủ công tốt hơn bằng một câu lệnh đơn giản.

Điều này sẽ "luôn luôn" hoạt động:

$ cat >longopt2
while (($#)); do
    OPT=$1
    shift
    case $OPT in
        --*) case ${OPT:2} in
            long1) echo long1 option;;
            complex) echo comples with argument $1; shift;;
        esac;;

        -*) case ${OPT:1} in
            a) echo short option a;;
            b) echo short option b with parameter $1; shift;;
        esac;;
    esac
done


$ bash longopt2 --complex abc -a --long -b test
comples with argument abc
short option a
short option b with parameter test

Mặc dù không linh hoạt như getopts và bạn phải tự mình thực hiện nhiều lỗi kiểm tra mã trong các trường hợp ...

Nhưng nó là một lựa chọn.


Nhưng các tùy chọn dài thường không mong đợi các đối số. Và bạn có thể làm nhiều hơn với - để làm cho nó hoạt động mặc dù đó là một thứ gì đó của hack. Cuối cùng, người ta có thể lập luận rằng nếu nó không hỗ trợ về mặt bản chất thì mọi cách để thực hiện nó là một thứ gì đó của hack nhưng tuy nhiên bạn cũng có thể mở rộng - quá. Và có sự thay đổi là rất hữu ích nhưng tất nhiên nếu nó mong đợi một đối số thì nó có thể kết thúc rằng đối số tiếp theo (nếu không được chỉ định bởi người dùng) là một phần của đối số dự kiến.
Pryftan

vâng, đây là một poc cho tên đối số dài không có đối số, để phân biệt giữa hai bạn cần một số loại cấu hình, như getops. Và liên quan đến sự thay đổi, bạn luôn có thể "đặt nó trở lại" với thiết lập. Trong mọi trường hợp, nó phải được cấu hình nếu tham số được mong đợi hoặc không. Bạn thậm chí có thể sử dụng một số phép thuật cho nó, nhưng sau đó bạn sẽ buộc người dùng sử dụng - để báo hiệu rằng phép thuật dừng lại và các tham số vị trí bắt đầu, điều này tệ hơn imho.
estani

Đủ công bằng. Điều đó còn hơn cả hợp lý. Tbh tôi thậm chí không nhớ những gì tôi đã xảy ra và tôi đã hoàn toàn quên mất câu hỏi này. Tôi chỉ mơ hồ nhớ làm thế nào tôi tìm thấy nó thậm chí. Chúc mừng. Oh và có +1 cho ý tưởng. Bạn đã trải qua nỗ lực và bạn cũng làm rõ những gì bạn đang nhận được. Tôi tôn trọng những người trải qua nỗ lực để đưa ra ý tưởng cho người khác, v.v.
Pryftan

0

Được xây dựng trong getopts chỉ phân tích các tùy chọn ngắn (ngoại trừ trong ksh93), nhưng bạn vẫn có thể thêm một vài dòng kịch bản để làm cho getopts xử lý các tùy chọn dài.

Đây là một phần của mã được tìm thấy trong http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

  #== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
  #== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
    [foo]=f
    [bar]=b
    [foobar]=F
    [barfoo]=B
    [help]=h
    [man]=h
)

  #== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
    #== translate long options to short ==#
    if [[ "x$OPTION" == "x-" ]]; then
        LONG_OPTION=$OPTARG
        LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
        LONG_OPTIND=-1
        [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
        [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
        OPTION=${ARRAY_OPTS[$LONG_OPTION]}
        [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

        if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
            if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                OPTION=":" OPTARG="-$LONG_OPTION"
            else
                OPTARG="$LONG_OPTARG";
                if [[ $LONG_OPTIND -ne -1 ]]; then
                    [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                    shift $OPTIND
                    OPTIND=1
                fi
            fi
        fi
    fi

    #== options follow by another option instead of argument ==#
    if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
        OPTARG="$OPTION" OPTION=":"
    fi

    #== manage options ==#
    case "$OPTION" in
        f  ) foo=1 bar=0                    ;;
        b  ) foo=0 bar=1                    ;;
        B  ) barfoo=${OPTARG}               ;;
        F  ) foobar=1 && foobar_name=${OPTARG} ;;
        h ) usagefull && exit 0 ;;
        : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
        ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
    esac
done
shift $((${OPTIND} - 1))

Đây là một bài kiểm tra:

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

Nếu không trong Korn Shell gần đây ksh93, getoptstự nhiên có thể phân tích tùy chọn dài và thậm chí hiển thị một trang người đàn ông như nhau. (Xem http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )


0

Getopt OS X (BSD) tích hợp không hỗ trợ các tùy chọn dài, nhưng phiên bản GNU thì : brew install gnu-getopt. Sau đó, một cái gì đó tương tự như : cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.


0

EasyOptions xử lý các tùy chọn ngắn và dài:

## Options:
##   --verbose, -v   Verbose mode
##   --logfile=NAME  Log filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "log file: ${logfile}"
    echo "arguments: ${arguments[@]}"
fi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.