bash getopts, opsi pendek saja, semua membutuhkan nilai, validasi sendiri

8

Saya mencoba untuk membangun skrip shell yang menerima berbagai opsi dan getoptssepertinya solusi yang baik karena dapat menangani pemesanan variabel opsi dan argumen (saya pikir!).

Saya hanya akan menggunakan opsi pendek dan setiap opsi pendek akan membutuhkan nilai yang sesuai, misalnya: ./command.sh -a arga -g argg -b argbtetapi saya ingin mengizinkan opsi dimasukkan dalam urutan non-spesifik, seperti cara kebanyakan orang terbiasa bekerja dengan perintah shell .

Poin lainnya adalah bahwa saya ingin melakukan pemeriksaan nilai argumen opsi saya sendiri, idealnya dalam casepernyataan. Alasan untuk ini adalah bahwa pengujian saya :)dalam casepernyataan saya telah menghasilkan hasil yang tidak konsisten (mungkin karena kurangnya pemahaman di pihak saya).
Sebagai contoh:

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

Gagal untuk kejadian seperti ini ... ./command.sh -d -h
Ketika saya ingin flag -d sebagai membutuhkan argumen tapi saya mendapatkan nilai -d=-hyang bukan yang saya butuhkan.

Jadi saya pikir akan lebih mudah untuk menjalankan validasi saya sendiri dalam laporan kasus untuk memastikan bahwa setiap opsi diatur dan ditetapkan hanya sekali.

Saya mencoba melakukan yang berikut tetapi if [ ! "$MYSQL_HOST" ]; thenblok saya tidak terpicu.

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: $@"

Apakah ada alasan bahwa saya tidak dapat memeriksa apakah OPTARGpanjang nol dari dalam getopts ... while ... case?

Apa cara yang lebih baik untuk menjalankan validasi argumen saya sendiri getoptsdalam kasus di mana saya tidak ingin bergantung pada :). Lakukan validasi argumen saya di luar while ... case ... esac?
Lalu saya bisa berakhir dengan nilai argumen -ddll dan tidak menangkap opsi yang hilang.

batfastad
sumber

Jawaban:

4

Saat Anda memanggil skrip kedua Anda (saya simpan sebagai getoptit) dengan:

getoptit -d -h

Ini akan mencetak:

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

Jadi BACKUP_DIR diatur, dan Anda menguji dengan if [ ! "$BACKUP_DIR" ]; thenjika tidak diatur, jadi itu normal bahwa kode di dalamnya tidak dipicu.

Jika Anda ingin menguji apakah setiap opsi disetel sekali, Anda harus melakukannya sebelum Anda melakukan assigment dari nilai $ OPTARG. Dan Anda mungkin juga harus memeriksa $ OPTARG untuk memulai dengan '-'(untuk -d -hkesalahan) sebelum menetapkan:

...
            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
                    ;;
...
Anthon
sumber
Penjelasan yang bagus. Masuk akal sekali saya berpikir tentang fakta bahwa sebenarnya ada whileloop menjalankan opsi. Ini adalah metode yang telah saya gunakan seolah-olah itu verbose dibandingkan dengan yang lain, sangat jelas apa yang terjadi dalam naskah. Dan pemeliharaan oleh orang lain adalah penting dalam hal ini.
batfastad
3

Karena jawaban lain sebenarnya tidak terlalu banyak menjawab pertanyaan Anda: Ini adalah perilaku yang diharapkan dan berdasarkan pada bagaimana POSIX ditafsirkan:

Ketika opsi membutuhkan argumen-opsi, utilitas getopts akan meletakkannya di variabel shell OPTARG. Jika tidak ada opsi yang ditemukan, atau jika opsi yang ditemukan tidak memiliki argumen-opsi, OPTARG tidak akan disetel.)

Ini adalah semua yang sedang dinyatakan, dan untuk membuat cerita pendek (tidak terlalu panjang): getoptstidak secara ajaib tahu bahwa argumen yang benar-benar valid untuk opsi yang Anda butuhkan untuk memiliki argumen sebenarnya bukanlah argumen opsi tetapi opsi lain . Tidak ada yang mencegah Anda dari memiliki -hargumen yang valid ke -dopsi, yang dalam praktiknya berarti bahwa getoptshanya akan menimbulkan kesalahan jika opsi Anda yang membutuhkan argumen datang terakhir pada baris perintah, misalnya:

test.sh:

#!/bin/sh

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

Contoh:

$ ./test.sh -y -x
$

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

Alasan mengapa pendekatan kedua Anda tidak berhasil adalah karena parsing yang getoptsdilakukan masih sama, jadi setelah Anda berada di dalam loop, "kerusakan" sudah dilakukan.

Jika Anda benar-benar ingin melarang ini, seperti yang ditunjukkan Benubird, Anda harus memeriksa sendiri argumen opsi Anda dan melemparkan kesalahan jika itu sama dengan opsi yang valid.

Adrian Frühwirth
sumber
2

Saya akan terus menggunakan getoptstetapi melakukan beberapa pemeriksaan tambahan setelah: (belum diuji)

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

membutuhkan bash versi 4

glenn jackman
sumber
1

The Gnu non-shell built-in getopt melakukan sedikit pekerjaan untuk Anda tetapi memungkinkan fleksibilitas yang lebih besar karena Anda bisa menentukan apa yang terjadi setelah bendera ditemukan, termasuk pergeseran dari argumen sendiri.

Saya menemukan artikel yang membandingkan getop dan getopt yang mungkin akan membantu karena manualnya agak sulit untuk dipahami (setidaknya sebelum kopi).

msw
sumber
1

Pertama, Anda harus menggunakan if [ -z "$MYSQL_USER" ].

Kedua, tidak perlu penugasan - if [ -z "$OPTARG" ]akan berfungsi dengan baik.

Ketiga, saya curiga apa yang sebenarnya Anda inginkan if [ ${#OPTARG} = 0 ]. $ {# x} adalah hal bash mengembalikan panjang string $ x (lihat lebih lanjut di sini ).

Keempat, jika Anda melakukan validasi Anda sendiri, saya akan merekomendasikan getopt daripada getopts, karena memberikan fleksibilitas lebih.

Terakhir, untuk menjawab pertanyaan pertama Anda, Anda dapat mendeteksi kapan sebuah bendera dilewatkan sebagai argumen dengan meletakkan daftar bendera di bagian atas, seperti ini:

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

Dan kemudian memiliki tanda centang pada opsi di mana Anda ingin memeriksa apakah argumen yang diberikan adalah opsi, sesuatu seperti ini:

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

Bukan jawaban yang sempurna, tetapi berhasil! Masalah Anda adalah bahwa "-h" adalah argumen yang benar-benar valid, dan Anda tidak memberi jalan bagi shell untuk mengetahui bahwa itu hanya mencari parameter yang bukan juga flag yang valid.

Benubird
sumber