Làm cách nào để phân tích các đối số tùy chọn trong tập lệnh bash nếu không có thứ tự nào được đưa ra?


13

Tôi bối rối làm thế nào để bao gồm các đối số / cờ tùy chọn khi viết tập lệnh bash cho chương trình sau:

Chương trình yêu cầu hai đối số:

run_program --flag1 <value> --flag2 <value>

Tuy nhiên, có một số cờ tùy chọn:

run_program --flag1 <value> --flag2 <value> --optflag1 <value> --optflag2 <value> --optflag3 <value> --optflag4 <value> --optflag5 <value> 

Tôi muốn chạy tập lệnh bash sao cho nó lấy đối số người dùng. Nếu người dùng chỉ nhập hai đối số theo thứ tự, thì đó sẽ là:

#!/bin/sh

run_program --flag1 $1 --flag2 $2

Nhưng nếu có bất kỳ đối số tùy chọn nào được đưa vào thì sao? Tôi nghĩ rằng nó sẽ được

if [ --optflag1 "$3" ]; then
    run_program --flag1 $1 --flag2 $2 --optflag1 $3
fi

Nhưng nếu $ 4 được đưa ra nhưng không phải $ 3 thì sao?


getoptslà những gì bạn muốn. Nếu không có điều đó, bạn có thể sử dụng một vòng lặp với câu lệnh chuyển đổi để phát hiện từng cờ, tùy chọn hoặc không.
orion


@orion Với getopts, tôi sẽ cần chỉ định từng kết hợp đối số? 3 & 4, 3 & 5, 3 & 4 & 5, v.v.?
ShanZhengYang

Không, bạn chỉ đặt chúng nếu bạn nhận được chúng, nếu không nó báo cáo nó không được tìm thấy, vì vậy về cơ bản, bạn chỉ "nhận" từng tùy chọn, theo bất kỳ thứ tự nào và chỉ định chúng theo bất kỳ thứ tự nào, nếu có. Nhưng chỉ cần đọc trang bash man, tất cả đều ở đó.
orion

@orion Tôi xin lỗi, nhưng tôi vẫn không hiểu lắm getopts. Giả sử tôi buộc người dùng chạy tập lệnh với tất cả các đối số: run_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSEchạy chương trình dưới dạng program --flag1 VAL --flag2 VAL. Nếu bạn chạy run_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE, chương trình sẽ chạy như program --flag1 VAL --flag2 VAL --optflag2 10. Làm thế nào bạn có thể có được hành vi như vậy với getopts?
ShanZhengYang

Câu trả lời:


25

Bài viết này cho thấy hai cách khác nhau - shiftgetopts(và thảo luận về ưu điểm và nhược điểm của hai phương pháp).

Với shiftkịch bản của bạn xem xét $1, quyết định hành động nào sẽ thực hiện, và sau đó thực thi shift, di chuyển $2đến $1, $3đến $2, v.v.

Ví dụ:

while :; do
    case $1 in
        -a|--flag1) flag1="SET"            
        ;;
        -b|--flag2) flag2="SET"            
        ;;
        -c|--optflag1) optflag1="SET"            
        ;;
        -d|--optflag2) optflag2="SET"            
        ;;
        -e|--optflag3) optflag3="SET"            
        ;;
        *) break
    esac
    shift
done

Với getoptsbạn xác định các tùy chọn (ngắn) trong whilebiểu thức:

while getopts abcde opt; do
    case $opt in
        a) flag1="SET"
        ;;
        b) flag2="SET"
        ;;
        c) optflag1="SET"
        ;;
        d) optflag2="SET"
        ;;
        e) optflag3="SET"
        ;;
    esac
done

Rõ ràng, đây chỉ là các đoạn mã và tôi đã bỏ qua xác thực - kiểm tra xem các đối số bắt buộc là cờ1 và cờ2 đã được đặt, v.v.

Cách tiếp cận bạn sử dụng là ở một mức độ nào đó là vấn đề sở thích - bạn muốn tập lệnh của mình di động như thế nào, liệu bạn có thể sống với các tùy chọn (POSIX) ngắn hay bạn muốn tùy chọn dài (GNU), v.v.


Tôi thường làm while (( $# ))thay vì while :;và thường bỏ cuộc với một lỗi trong *trường hợp
Jasen

Điều này trả lời tiêu đề nhưng không phải là câu hỏi.
Jasen

@Jasen Tôi đã xem xét điều này tối qua và không thể cho cuộc sống của tôi xem tại sao. Sáng nay nó rõ ràng hơn nhiều (và tôi cũng đã thấy câu trả lời của orion bây giờ). Tôi sẽ xóa câu trả lời này sau hôm nay (tôi muốn thừa nhận nhận xét của bạn trước và đây có vẻ là cách dễ nhất để làm điều đó).
John N

không vấn đề gì, nó vẫn là một câu trả lời hay, chỉ là câu hỏi né tránh nó.
Jasen

1
NB: Trong trường hợp set -o nounsetgiải pháp đầu tiên sẽ báo lỗi nếu không có tham số nào được đưa ra. Khắc phục:case ${1:-} in
Raphael

3

sử dụng một mảng.

#!/bin/bash

args=( --flag1 "$1" --flag2 "$2" )
[  "x$3" = xFALSE ] ||  args+=( --optflag1 "$3" )
[  "x$4" = xFALSE ] ||  args+=( --optflag2 "$4" )
[  "x$5" = xFALSE ] ||  args+=( --optflag3 "$5" )
[  "x$6" = xFALSE ] ||  args+=( --optflag4 "$6" )
[  "x$7" = xFALSE ] ||  args+=( --optflag5 "$7" )

program_name "${args[@]}"

điều này sẽ xử lý các đối số có khoảng trắng trong chúng một cách chính xác.

[sửa] Tôi đã sử dụng cú pháp gần như hiệu quả args=( "${args[@]}" --optflag1 "$3" )nhưng G-Man đã đề xuất một cách tốt hơn.


1
Bạn có thể hợp lý hóa điều này một chút bằng cách nói args+=( --optflag1 "$3" ). Bạn có thể muốn xem câu trả lời của tôi cho công việc tham khảo của chúng tôi, Ý nghĩa bảo mật của việc không trích dẫn một biến trong các bash / POSIX , trong đó tôi thảo luận về kỹ thuật này (về việc xây dựng một dòng lệnh trong một mảng bằng cách bổ sung các điều kiện tùy chọn có điều kiện).
G-Man nói 'Phục hồi Monica'

@ G-Man Cảm ơn, tôi đã không thấy rằng trong tài liệu, hiểu rằng [@]tôi có một công cụ đủ để giải quyết vấn đề của mình.
Jasen

1

Trong tập lệnh shell, các đối số là "$ 1", "$ 2", "$ 3", v.v ... Số lượng đối số là $ #.
Nếu tập lệnh của bạn không nhận ra các tùy chọn, bạn có thể bỏ qua phát hiện tùy chọn và coi tất cả các đối số là toán hạng.
Để nhận ra các tùy chọn, hãy sử dụng nội dung getopts


0

Nếu các tùy chọn đầu vào của bạn là vị trí (bạn biết vị trí của chúng) và không được chỉ định bằng cờ, thì điều bạn muốn chỉ là xây dựng dòng lệnh. Chỉ cần chuẩn bị các đối số lệnh cho tất cả chúng:

FLAG1="--flag1 $1"
FLAG2="--flag2 $2"
OPTFLAG1=""
OPTFLAG2=""
OPTFLAG3=""
if [ xFALSE != x"$3" ]; then
   OPTFLAG1="--optflag1 $3"
fi
if [ xFALSE != x"$4" ]; then
   OPTFLAG2="--optflag2 $4"
fi
#the same for other flags

#now just run the program
runprogram $FLAG1 $FLAG2 $OPTFLAG1 $OPTFLAG2 $OPTFLAG3

Nếu các tham số không được chỉ định, thì các chuỗi sửa lỗi sẽ trống và mở rộng thành không có gì. Lưu ý rằng trong dòng cuối cùng, không có dấu ngoặc kép. Đó là bởi vì bạn muốn shell chia các tham số thành các từ (để đưa ra --flag1$1làm đối số riêng cho chương trình của bạn). Tất nhiên, điều này sẽ sai nếu các tham số ban đầu của bạn chứa khoảng trắng. Nếu bạn là người chạy cái này, thì bạn có thể bỏ nó, nhưng nếu đó là một tập lệnh chung, nó có thể có hành vi không mong muốn nếu người dùng nhập nội dung nào đó có khoảng trắng. Để xử lý điều đó, bạn sẽ cần làm cho mã xấu hơn một chút.

Các xtiền tố trong []thử nghiệm là có trong trường hợp $3hoặc $4là rỗng. Trong trường hợp đó, bash sẽ mở rộng [ FALSE != $3 ]thành [ FALSE != ]lỗi cú pháp, do đó, một ký tự tùy ý khác sẽ ở đó để bảo vệ chống lại điều này. Đây là một cách rất phổ biến, bạn sẽ thấy nó trong nhiều tập lệnh.

Tôi đặt ra OPTFLAG1và phần còn lại của chúng ""ngay từ đầu chỉ để chắc chắn (trong trường hợp chúng được đặt thành thứ gì đó trước đó), nhưng nếu chúng không thực sự được tuyên bố trong môi trường, thì bạn không nhất thiết phải làm điều đó.

Một vài lưu ý bổ sung:

  • Bạn thực sự có thể nhận các tham số theo cách tương tự như runprogram: với cờ. Đó là những gì John Nđang nói về. Đó là nơi getoptstrở nên hữu ích.
  • Sử dụng FALSE cho mục đích đó là một chút không chuẩn và khá dài. Thông thường, người ta sẽ sử dụng một ký tự (có thể -) để báo hiệu một giá trị trống hoặc chỉ truyền một chuỗi trống, như "", nếu không thì đó không phải là giá trị hợp lệ của tham số. Chuỗi rỗng cũng làm cho bài kiểm tra ngắn hơn, chỉ cần sử dụng if [ -n "$3" ].
  • Nếu chỉ có một cờ tùy chọn hoặc nếu chúng phụ thuộc để bạn không bao giờ có thể có OPTFLAG2, nhưng không phải là OPTFLAG1, thì bạn có thể chỉ cần bỏ qua những cờ bạn không muốn đặt. Nếu bạn sử dụng ""cho các tham số trống, như được đề xuất ở trên, bạn vẫn có thể bỏ qua tất cả các trống rỗng.

Phương pháp này khá nhiều cách các tùy chọn được chuyển đến trình biên dịch trong Makefiles.

Và một lần nữa - nếu đầu vào có thể chứa khoảng trắng, nó sẽ trở nên xấu xí.


Không, bạn không muốn trình bao chia các tham số thành các từ. Bạn phải luôn trích dẫn tất cả các tham chiếu đến các biến shell trừ khi bạn có lý do chính đáng để không và bạn chắc chắn rằng bạn biết bạn đang làm gì. Xem Hàm ý bảo mật của việc không trích dẫn một biến trong shell bash / POSIX , trong trường hợp bạn không quen với nó và cuộn xuống câu trả lời của tôi , nơi tôi giải quyết chính xác vấn đề này (với cùng một kỹ thuật mà Jasen sử dụng ).
G-Man nói 'Phục hồi Monica'
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.