Làm thế nào để viết một kịch bản làm việc có hoặc không có đối số?


13

Tôi có một kịch bản bash giống như thế này:

#!/bin/bash

if [ $1 = "--test" ] || [ $1 = "-t" ]; then
    echo "Testing..."
    testing="y"

else
    testing="n"
    echo "Not testing."

fi

Vì vậy, những gì tôi muốn có thể làm không chỉ chạy nó với ./script --testhoặc ./script -t, mà còn không có đối số (chỉ ./script), mà dường như nếu tôi làm điều đó với mã hiện tại thì đầu ra chỉ là:

./script: line 3: [: =: unary operator expected
./script: line 3: [: =: unary operator expected
Not testing.

Vì vậy, làm thế nào để tôi lập trình nó để chạy nó mà không có đối số nào sẽ chỉ làm elsemà không ném lỗi? Tôi đang làm gì sai?


1
Bạn cần dấu ngoặc kép xung quanh séc của bạn. [[]]
Terrance


@Terrance bạn không cần chúng, mặc dù bạn nên sử dụng chúng nếu bạn nhắm mục tiêu bash.
Ruslan

Câu trả lời:


1

"Cách thích hợp" của việc sử dụng các tùy chọn dài trong kịch bản lệnh shell là thông qua tiện ích GNU getopt . Ngoài ra còn có các getopts được tích hợp sẵn , nhưng nó chỉ cho phép các tùy chọn ngắn như -t. Một vài ví dụ về getoptviệc sử dụng có thể được tìm thấy ở đây .

Đây là một kịch bản thể hiện cách tôi tiếp cận câu hỏi của bạn. Giải thích về hầu hết các bước được thêm vào dưới dạng nhận xét trong chính kịch bản.

#!/bin/bash

# GNU getopt allows long options. Letters in -o correspond to
# comma-separated list given in --long.

opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened

set -- $opts # some would prefer using eval set -- "$opts"
# if theres -- as first argument, the script is called without
# option flags
if [ "$1" = "--"  ]; then
    echo "Not testing"
    testing="n"
    # Here we exit, and avoid ever getting to argument parsing loop
    # A more practical case would be to call a function here
    # that performs for no options case
    exit 1
fi

# Although this question asks only for one 
# option flag, the proper use of getopt is with while loop
# That's why it's done so - to show proper form.
while true; do
    case "$1" in
        # spaces are important in the case definition
        -t | --test ) testing="y"; echo "Testing" ;;
    esac
    # Loop will terminate if there's no more  
    # positional parameters to shift
    shift  || break
done

echo "Out of loop"

Với một vài đơn giản hóa và xóa bình luận, điều này có thể được cô đọng thành:

#!/bin/bash
opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened
set -- $opts    
case "$1" in
    -t | --test ) testing="y"; echo "Testing";;
    --) testing="n"; echo "Not testing";;
esac

16

Một số cách; Hai thứ rõ ràng nhất là:

  • Đặt $1trong dấu ngoặc kép:if [ "$1" = "--test" ]
  • Kiểm tra số lượng đối số bằng $ #

Tốt hơn nữa, sử dụng getopts.


2
+1 để sử dụng getopts. Đó là cách tốt nhất để đi.
Seth

3
Một thành ngữ phổ biến khác là [ "x$1" = "x--test" ]bảo vệ lại các đối số dòng lệnh là các toán tử hợp lệ cho [lệnh. (Đây là một lý do khác tại sao [[ ]]được ưa thích: đó là một phần của cú pháp shell, không chỉ là lệnh dựng sẵn.)
Peter Cordes

7

Bạn cần trích dẫn các biến của bạn bên trong điều kiện if. Thay thế:

if [ $1 = "--test" ] || [ $1 = "-t" ]; then

với:

if [ "$1" = '--test' ] || [ "$1" = '-t' ]; then  

Sau đó, nó sẽ hoạt động:

➜ ~ ./test.sh
Không thử nghiệm.

Luôn luôn báo giá gấp đôi các biến của bạn!


Là dấu ngoặc đơn là cần thiết hay thay vào đó người ta có thể sử dụng dấu ngoặc kép?

Câu trả lời chính xác phụ thuộc vào loại vỏ bạn đang sử dụng, nhưng từ câu hỏi của bạn, tôi cho rằng bạn đang sử dụng một hậu duệ vỏ Bourne. Sự khác biệt chính giữa dấu ngoặc đơn và dấu ngoặc kép là sự mở rộng biến đổi xảy ra bên trong dấu ngoặc kép, nhưng không nằm trong dấu ngoặc đơn: $ FOO = bar $ echo "$ FOO" bar $ echo '$ FOO' $ FOO (hmm .... can ' mã định dạng t trong các bình luận ...)
JayEye

@ParanoidPanda Bạn không cần phải sử dụng dấu ngoặc đơn, không, nhưng tốt nhất là sử dụng chúng trên chuỗi ký tự. Bằng cách đó, bạn sẽ không ngạc nhiên về sau nếu dữ liệu chuỗi của bạn có biểu tượng bash muốn mở rộng.
Seth

@ParanoidPanda Những gì @JayEye đã nói: Với các trích dẫn đơn lẻ, không có foo=bar echo '$foo'bản in mở rộng biến đổi $foocho đầu ra, nhưng thay vào đó foo=bar echo "$foo"in bar.
EKons

4

Bạn đang sử dụng bash. Bash có một sự thay thế tuyệt vời cho [: [[. Với [[, bạn không phải lo lắng về việc trích dẫn và bạn có được nhiều nhà khai thác hơn không [, bao gồm hỗ trợ phù hợp cho ||:

if [[ $1 = "--test" || $1 = "-t" ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Hoặc bạn có thể sử dụng các biểu thức thông thường:

if [[ $1 =~ ^(--test|-t) ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Tất nhiên, luôn luôn phải trích dẫn thích hợp, nhưng không có lý do gì để [sử dụng khi sử dụng bash.


3

Để kiểm tra xem có đối số hay không (nói chung và thực hiện các hành động tương ứng) Điều này sẽ hoạt động:

#!/bin/bash

check=$1

if [ -z "$check" ]; then
  echo "no arguments"
else echo "there was an argument"
fi

Tại sao không [ -z "$1" ]?
EKons

@ ΈρκΚω call call Quyết định giữ giao thức trong mẫu.
Jacob Vlijm

Trong mẫu này bạn dường như sử dụng $checkmột lần, trước bất kỳ shifts nào , vì vậy sẽ tốt hơn khi sử dụng $1thay thế.
EKons

@ ΈρκΚω Tất nhiên là không, như đã đề cập, nó là một mẫu , được sử dụng trong các tập lệnh. Trong các kịch bản, thực tế xấu là lặp đi lặp lại nhiều lần.
Jacob Vlijm

Tôi hiểu ý của bạn là gì, nhưng tốt hơn hết là không sử dụng shebang #!khi cung cấp mẫu (bạn nên mô tả lớp vỏ bên ngoài mã).
EKons
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.