bash getopts, chỉ các tùy chọn ngắn, tất cả đều yêu cầu giá trị, xác nhận riêng


8

Tôi đang cố gắng xây dựng một tập lệnh shell chấp nhận các tùy chọn khác nhau và getoptscó vẻ như là một giải pháp tốt vì nó có thể xử lý thứ tự biến đổi của các tùy chọn và đối số (tôi nghĩ vậy!).

Tôi sẽ chỉ sử dụng các tùy chọn ngắn và mỗi tùy chọn ngắn sẽ yêu cầu một giá trị tương ứng, ví dụ: ./command.sh -a arga -g argg -b argbnhưng tôi muốn cho phép các tùy chọn được nhập theo thứ tự không cụ thể, như cách mà hầu hết mọi người đều quen làm việc với các lệnh shell .

Điểm khác là tôi muốn tự kiểm tra các giá trị đối số tùy chọn, lý tưởng trong các casecâu lệnh. Lý do cho điều này là việc thử nghiệm :)trong casetuyên bố của tôi đã mang lại kết quả không nhất quán (có thể thông qua sự thiếu hiểu biết từ phía tôi).
Ví dụ:

#!/bin/bash
OPTIND=1 # Reset if getopts used previously
if (($# == 0)); then
        echo "Usage"
        exit 2
fi
while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                :)
                       echo "Option -$OPTARG requires an argument" >&2
                       exit 2;;
        esac
done
shift $((OPTIND-1))
echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

Đã thất bại cho những lần xuất hiện như thế này ... ./command.sh -d -h
Khi tôi muốn nó gắn cờ -d như yêu cầu một đối số nhưng tôi nhận được giá trị -d=-hkhông phải là thứ tôi cần.

Vì vậy, tôi cho rằng việc chạy xác thực của riêng mình trong các báo cáo trường hợp sẽ dễ dàng hơn để đảm bảo rằng mỗi tùy chọn được đặt và chỉ được đặt một lần.

Tôi đang cố gắng làm như sau nhưng if [ ! "$MYSQL_HOST" ]; thencác khối của tôi không được kích hoạt.

OPTIND=1 # Reset if getopts used previously

if (($# == 0)); then
        echo "Usage"
        exit 2
fi

while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        if [ ! "$MYSQL_HOST" ]; then
                                echo "host not set"
                                exit 2
                        fi
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        if [ ! "$MYSQL_USER" ]; then
                                echo "username not set"
                                exit 2
                        fi
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        if [ ! "$MYSQL_PASS" ]; then
                                echo "password not set"
                                exit 2
                        fi
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        if [ ! "$BACKUP_DIR" ]; then
                                echo "backup dir not set"
                                exit 2
                        fi
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                #:)
                #       echo "Option -$opt requires an argument" >&2
                #       exit 2;;
        esac
done
shift $((OPTIND-1))

echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

Có một lý do mà tôi không thể kiểm tra xem một cái OPTARGcó độ dài bằng không từ bên trong getopts ... while ... casekhông?

Cách tốt hơn để chạy xác thực đối số của riêng tôi getoptstrong trường hợp tôi không muốn dựa vào :). Thực hiện xác nhận đối số của tôi bên ngoài while ... case ... esac?
Sau đó, tôi có thể kết thúc với các giá trị đối số của -dvv và không bắt một tùy chọn bị thiếu.

Câu trả lời:


4

Khi bạn gọi tập lệnh thứ hai của mình (tôi đã lưu nó dưới dạng getoptit) với:

getoptit -d -h

Điều này sẽ in:

MYSQL_HOST=''  MYSQL_USER=''  MYSQL_PASS=''  BACKUP_DIR='-h' Additionals: 

Vì vậy, BACKUP_DIR được đặt và bạn đang kiểm tra if [ ! "$BACKUP_DIR" ]; thennếu nó không được đặt, do đó, thông thường mã bên trong của nó không được kích hoạt.

Nếu bạn muốn kiểm tra xem mỗi tùy chọn được đặt một lần, bạn phải thực hiện điều đó trước khi bạn thực hiện xác nhận từ giá trị $ OPTARG. Và có lẽ bạn cũng nên kiểm tra $ OPTARG để bắt đầu bằng '-'(cho -d -hlỗi) trước khi gán:

...
            d)
                    if [ ! -z "$BACKUP_DIR" ]; then
                            echo "backup dir already set"
                            exit 2
                    fi
                    if [ z"${OPTARG:0:1}" == "z-" ]; then
                            echo "backup dir starts with option string"
                            exit 2
                    fi
                    BACKUP_DIR=$OPTARG
                    ;;
...

Giải thích tuyệt vời. Làm cho cảm giác hoàn hảo khi tôi nghĩ về thực tế là thực sự có một whilevòng lặp chạy qua các tùy chọn. Đây là phương pháp tôi đã thực hiện mặc dù nó dài dòng so với các phương pháp khác, nhưng rất rõ những gì đang diễn ra trong kịch bản. Và khả năng bảo trì của người khác là quan trọng trong trường hợp này.
batfastad

3

Vì các câu trả lời khác không thực sự trả lời câu hỏi của bạn rất nhiều: Đây là hành vi được mong đợi và dựa trên cách POSIX đang được diễn giải:

Khi tùy chọn yêu cầu một đối số tùy chọn, tiện ích getopts sẽ đặt nó trong biến shell OPTARG. Nếu không tìm thấy tùy chọn nào hoặc nếu tùy chọn được tìm thấy không có đối số tùy chọn, thì OPTARG sẽ không được đặt.)

Đây là tất cả những gì đang được nêu, và để viết một câu chuyện (không quá dài): getoptskhông biết một cách kỳ diệu rằng đối số hoàn toàn hợp lệ mà nó thấy cho tùy chọn mà bạn yêu cầu để có một đối số thực sự không phải là một đối số tùy chọn mà là một tùy chọn khác . Không có gì ngăn bạn có -hmột đối số hợp lệ cho -dtùy chọn, trong thực tế có nghĩa là getoptssẽ chỉ đưa ra một lỗi nếu tùy chọn của bạn yêu cầu một đối số xuất hiện cuối cùng trên dòng lệnh, ví dụ:

test.sh:

#!/bin/sh

while getopts ":xy:" o; do
    case "${o}" in
        :) echo "${OPTARG} requires an argument"; exit 1;
    esac
done

Thí dụ:

$ ./test.sh -y -x
$

$ ./test.sh -x -y
y requires an argument

Lý do tại sao cách tiếp cận thứ hai của bạn không hoạt động là vì phân tích cú pháp getoptsvẫn giống nhau, vì vậy một khi bạn ở trong vòng lặp, "thiệt hại" đã được thực hiện.

Nếu bạn hoàn toàn muốn cấm điều này sau đó, như Benubird đã chỉ ra, bạn sẽ phải tự kiểm tra các đối số tùy chọn của mình và đưa ra lỗi nếu chúng bằng một tùy chọn hợp lệ.


2

Tôi sẽ tiếp tục sử dụng getoptsnhưng kiểm tra thêm sau: (chưa được kiểm tra)

errs=0
declare -A option=(
    [MYSQL_HOST]="-h"
    [MYSQL_USER]="-u"
    [MYSQL_PASS]="-p"
    [BACKUP_DIR]="-d" 
)
for var in "${!option[@]}"; do
    if [[ -z "${!var}" ]]; then
        echo "error: specify a value for $var with ${option[var]}"
        ((errs++))
    fi
done
((errs > 0)) && exit 1

yêu cầu bash phiên bản 4


1

Các Gnu phi vỏ built-in getopt không ít công việc cho bạn, nhưng cho phép linh hoạt hơn khi bạn nhận được để xác định những gì xảy ra sau khi một lá cờ được tìm thấy, trong đó có chuyển ra khỏi các đối số cho mình.

Tôi tìm thấy một bài viết so sánh getopts và getopt có thể sẽ hữu ích vì hướng dẫn sử dụng hơi khó hiểu (ít nhất là trước khi uống cà phê).


1

Đầu tiên, bạn nên sử dụng if [ -z "$MYSQL_USER" ].

Thứ hai, không cần chuyển nhượng - if [ -z "$OPTARG" ]sẽ hoạt động tốt.

Thứ ba, tôi nghi ngờ những gì bạn thực sự muốn là if [ ${#OPTARG} = 0 ]. $ {# x} là một điều bash trả về độ dài của chuỗi $ x (xem thêm tại đây ).

Thứ tư, nếu bạn đang thực hiện xác nhận của riêng mình, tôi sẽ khuyên bạn nên sử dụng getopt thay vì getopts, vì nó cung cấp sự linh hoạt hơn rất nhiều.

Cuối cùng, để trả lời câu hỏi đầu tiên của bạn, bạn có thể phát hiện khi cờ được chuyển qua làm đối số bằng cách đặt danh sách các cờ ở trên cùng, như sau:

args=( -h -u -p -d )

Và sau đó có một kiểm tra trong tùy chọn mà bạn muốn kiểm tra xem đối số được cung cấp có phải là một tùy chọn hay không, đại loại như sau:

d)
    BACKUP_DIR=$OPTARG
    if [ "$(echo ${args[@]/$OPTARG/})" = "$(echo ${args[@]})" ]
    then
        echo "Argument is an option! Error!"
    fi

Không phải là một câu trả lời hoàn hảo, nhưng nó hoạt động! Vấn đề của bạn là "-h" là một đối số hoàn toàn hợp lệ và bạn không có cách nào để trình bao biết rằng nó chỉ tìm kiếm các tham số không phải là cờ hợp lệ.

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.