Herhangi bir sipariş verilmezse bash komut dosyasında isteğe bağlı bağımsız değişkenleri nasıl ayrıştırırım?


13

Aşağıdaki program için bir bash betiği yazarken isteğe bağlı bağımsız değişkenler / bayraklar eklemek nasıl karıştı:

Program iki argüman gerektirir:

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

Ancak, birkaç isteğe bağlı bayrak vardır:

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

Ben kullanıcı argüman alır böylece bash komut dosyasını çalıştırmak istiyorum. Kullanıcılar sırayla yalnızca iki bağımsız değişken girerse, şöyle olur:

#!/bin/sh

run_program --flag1 $1 --flag2 $2

Ancak, isteğe bağlı bağımsız değişkenlerden herhangi biri dahil edilirse ne olur? Olacağını düşünürdüm

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

Peki ya 4 dolar verilirse 3 dolar verilmezse ne olur?


getoptsistediğin şey bu. Bu olmadan, her bayrağı algılamak için switch deyimli bir döngü kullanabilirsiniz, isteğe bağlı veya değil.
orion


@orion ile getopts, her bir argüman kombinasyonunu belirtmem gerekir mi? 3 & 4, 3 & 5, 3 & 4 & 5, vb.
ShanZhengYang

Hayır, sadece onları alırsanız ayarlarsınız, aksi takdirde bulunmadığını bildirir, bu nedenle temel olarak her seçeneği, hangi sırayla "alırsınız" ve varsa, herhangi bir sırayla belirtirsiniz. Ama bash man sayfasını okuyun, hepsi orada.
orion

@orion Üzgünüm, ama hala tam olarak anlamıyorum getopts. : Diyelim tüm argümanlarla komut dosyasını çalıştırmak için kullanıcıların zorlamak demek run_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSEolarak programı çalıştırır program --flag1 VAL --flag2 VAL. Eğer koştuysanız run_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE, program olarak çalışır program --flag1 VAL --flag2 VAL --optflag2 10. Böyle bir davranışı nasıl elde edebilirsiniz getopts?
ShanZhengYang

Yanıtlar:


25

Bu makale iki farklı yol göstermektedir - shiftve getopts(ve iki yaklaşımın avantaj ve dezavantajlarını tartışmaktadır).

İle shiftadresinden komut görünüyor $1, aksiyon ardından yürütür almak ve konuya ilişkin bir karar shifthareketli, $2için $1, $3için $2, vs.

Örneğin:

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

İfadedeki getopts(kısa) seçenekleri tanımladığınızda while:

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

Açıkçası, bunlar sadece kod parçacıklarıdır ve onaylamayı bıraktım - zorunlu bayraklar1 ve bayrak2'nin ayarlandığını kontrol ediyorum.

Kullandığınız yaklaşım bir dereceye kadar zevk meselesidir - betiğinizin ne kadar taşınabilir olmasını istediğiniz, yalnızca kısa (POSIX) seçeneklerle yaşayabileceğiniz veya uzun (GNU) seçenekler isteyip istemediğiniz vb.


Genellikle yapmak while (( $# ))yerine while :;ve genellikle bir hata ile çıkmak *durumunda
Jasen

bu başlığa cevap verir ama soruyu cevaplamaz.
Jasen

@Jasen Dün gece baktım ve hayatım boyunca nedenini anlayamadım. Bu sabah çok daha net (ve şimdi orion'un cevabını da gördüm). Bu cevabı bugün daha sonra sileceğim (önce yorumunuzu onaylamak istedim ve bu bunu yapmanın en kolay yolu gibi görünüyordu).
John N

sorun değil, hala iyi bir cevap, sadece soru kaçtı.
Jasen

1
Not: set -o nounsetHerhangi bir parametre verilmezse, ilk çözüm hata verirse. Düzeltme:case ${1:-} in
Raphael

3

dizi kullanın.

#!/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[@]}"

bu, içindeki boşluklara sahip argümanları doğru şekilde ele alacaktır.

Kabaca eşdeğer sözdizimini kullanıyordum args=( "${args[@]}" --optflag1 "$3" )ama G-Man daha iyi bir yol önerdi.


1
Bunu söyleyerek biraz akıcı hale getirebilirsiniz args+=( --optflag1 "$3" ). Referans çalışmamıza cevabımı , bash / POSIX kabuklarında bir değişken teklif etmemenin Güvenlik sonuçlarını, bu tekniği tartıştığım (şartlı olarak isteğe bağlı argümanlar ekleyerek bir dizide bir komut satırı oluşturmanın) görmek isteyebilirsiniz .
G-Man,

@ G-Man Teşekkürler, dokümantasyonda, [@]problemimi çözmek için yeterli bir araca sahip olduğumu anlayarak görmedim .
Jasen

1

Kabuk betiğinde, bağımsız değişkenler "$ 1", "$ 2", "$ 3" vb. Şeklindedir. Bağımsız değişken sayısı $ #.
Betiğiniz seçenekleri tanımıyorsa, seçenek algılamayı dışarıda bırakabilir ve tüm bağımsız değişkenleri işlenen olarak ele alabilirsiniz.
Seçenekleri tanımak için yerleşik getopts kullanın


0

Senin Eğer giriş seçenekleri konumsal olan (eğer bunlar en hangi yerlerde biliyorum) ve bayraklarla belirtilmemiş, o zaman ne istediğiniz gibi bir komut satırı oluşturmaktır. Sadece komut argümanlarını hepsi için hazırlayın:

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

Parametreler belirtilmezse, karşılık gelen dizeler boştur ve hiçbir şeye genişlemez. Son satırda tırnak olmadığını unutmayın. Bunun nedeni, kabuğun parametreleri kelimelere bölmesini istemenizdir ( programınıza bağımsız değişkenler vermek --flag1ve vermek $1için). Tabii ki, orijinal parametreleriniz boşluk içeriyorsa bu yanlış olur. Bunu çalıştıran sizseniz, bırakabilirsiniz, ancak bu genel bir komut dosyasıysa, kullanıcı boşluklu bir şey girerse beklenmedik davranışlara sahip olabilir. Bunu yapmak için kodu biraz daha çirkin hale getirmeniz gerekir.

xİçinde önek []testinde durumda olduğunu $3veya $4boş. Bu durumda, bash genişletecektir [ FALSE != $3 ]içine [ FALSE != ]başka keyfi karakteri buna karşı orada korumak için yani, bir sözdizimi hatası olduğu. Bu çok yaygın bir yol, birçok komut dosyasında göreceksiniz.

Ben OPTFLAG1ve geri kalanlarını ""başlangıçta sadece emin olmak için ayarladım (daha önce bir şeye ayarlandıkları takdirde), ancak çevrede gerçekten beyan edilmemişlerse, kesinlikle bunu yapmanız gerekmez.

Birkaç ek açıklama:

  • Aslında parametreleri sadece aynı şekilde alabilirsiniz runprogram: with flags. Bundan John Nbahsediyor. İşte bu noktada getoptsyararlı olur.
  • Bu amaçla FALSE kullanımı biraz standart dışı ve oldukça uzundur. Genellikle, -boş bir değeri belirtmek için tek bir karakter (muhtemelen ) kullanılır veya ""parametrenin geçerli bir değeri değilse , boş bir dize iletilir. Boş dize ayrıca testi kısaltır, sadece kullanın if [ -n "$3" ].
  • Yalnızca bir isteğe bağlı bayrak varsa veya bağımlılarsa, OPTFLAG2'ye sahip olamayacaksınız, ancak OPTFLAG1'e sahip olamayacaksanız, ayarlamak istemediklerinizi atlayabilirsiniz. ""Yukarıda önerildiği gibi boş parametreler için kullanırsanız , yine de tüm izleyen boşalmaları atlayabilirsiniz.

Bu yöntem, seçeneklerin Makefiles'deki derleyicilere aktarılma yöntemidir.

Ve tekrar - girişler boşluk içeriyorsa, çirkinleşir.


Hayır, kabuğun parametreleri kelimelere ayırmasını istemezsiniz. Olmamanız için iyi bir nedeniniz olmadıkça ve ne yaptığınızı bildiğinizden emin olmadığınız sürece kabuk değişkenlerine yapılan tüm referansları her zaman alıntılamalısınız. Bilmiyorsanız , bash / POSIX mermilerinde bir değişkeni alıntılamamanın güvenlik üzerindeki etkileri konusuna bakın ve bu soruna ( Jasen'ın kullandığı aynı teknikle) değindiğim yanıtı aşağı kaydırın .
G-Man
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.