Diğer diller gibi argüman alan bir bash işlevi?


17

Ben böyle ayarlamak için bir bash işlevi var $PATH-

assign-path()
{
    str=$1
    # if the $PATH is empty, assign it directly.
    if [ -z $PATH ]; then
        PATH=$str;
    # if the $PATH does not contain the substring, append it with ':'.
    elif [[ $PATH != *$str* ]]; then
        PATH=$PATH:$str;
    fi
}

Ama sorun şu ki, farklı değişkenler için farklı işlevler yazmak zorundayım (örneğin, $CLASSPATHbenzeri başka bir işlev assign-classpath()vb.). Bash işlevine argüman iletmenin bir yolunu bulamadım, böylece referansla erişebilirim.

Böyle bir şeyim olsaydı daha iyi olurdu -

assign( bigstr, substr )
{
    if [ -z bigstr ]; then
        bigstr=substr;
    elif [[ bigstr != *str* ]]; then
        bigstr=bigstr:substr;
    fi
}

Herhangi bir fikir, nasıl bash yukarıdaki gibi bir şey elde etmek?


"Diğer diller" nedir?
choroba

şey, Bash c gibi "referans ile bu geçiş" olması durumunda, ortalama çalıştı / Java gibi
ramgorur

1
assign-path /abcekler olmayacak /abc$ PATH zaten içeriyorsa PATH /abc/def, /abcd, /def/abcvb Özellikle ekleyemezsiniz /binYOLU zaten içeriyorsa /usr/bin.
miracle173

@ miracle173 - Bu ne yapmak gerek bölünmüş olduğu, doğrudur $PATHgibi argümanları karşı ve negate testi: add=/bin dir=/usr/bin ; [ -z "${dir%"$add"}" ] || dir="${dir}:${add}". Cevabımda bunu sadece istediğiniz kadar argümanla yapıyorum IFS=:.
mikeserv

İki$PATH nokta üst üste işaretli listelere değer ekleme özel (örnek) sorunuyla ilgili: Nasıl temiz bir şekilde ekleyebilirim  ?  ve orada $PATHdeğilse dizini ekle ( Süper Kullanıcı'da ).
Scott

Yanıtlar:


17

İçinde başka bir içeriğin başvurduğu değişkeni genişletmek için bashkullanabilirsiniz ${!varname}. Örneğin:

$ var=hello
$ foo () { echo "${!1}"; }
$ foo var
hello

Man sayfasından:

${!prefix*}
${!prefix@}
       Names matching prefix.  Expands to the names of variables whose names
       begin with prefix, separated by the first character of the IFS special
       variable.  When @ is used  and the expansion appears within double quotes,
       each variable name expands to a separate word.

Ayrıca, içerik tarafından başvurulan bir değişkeni ayarlamak için (tehlikeleri olmadan eval) kullanabilirsiniz declare. Örneğin:

$ var=target
$ declare "$var=hello"
$ echo "$target"
hello

Bu nedenle, işlevinizi şöyle yazabilirsiniz ( declarebir işlevde kullanırsanız, vermeniz gerekir -gveya değişken yerel olacaktır):

shopt -s extglob

assign()
{
  target=$1
  bigstr=${!1}
  substr=$2

  if [ -z "$bigstr" ]; then
    declare -g -- "$target=$substr"
  elif [[ $bigstr != @(|*:)$substr@(|:*) ]]; then
    declare -g -- "$target=$bigstr:$substr"
  fi
}

Ve şöyle kullanın:

assign PATH /path/to/binaries

Ayrıca substr, zaten iki nokta üst üste ayrılmış üyelerinden birinin bir alt dize ise bigstr, ancak kendi üyesi değilse , bir hata düzeltildi unutmayın . Örneğin, bu zaten içeren /binbir PATHdeğişkenin eklenmesine izin verir /usr/bin. extglobDizeleri ya dizenin başlangıcını / sonunu ya da iki nokta üst üste işaretini ya da başka bir şeyi eşleştirmek için kullanır . Olmasaydı extglob, alternatif:

[[ $bigstr != $substr && $bigstr != *:$substr &&
   $bigstr != $substr:* && $bigstr != *:$substr:* ]]

-gin declarebash'ın eski sürümünde mevcut değil, bu geriye dönük uyumlu yapabilmemin bir yolu var mı?
ramgorur

2
@ramgorur, exportortamınıza (önemli bir şeyin üzerine yazma riskiyle karşı karşıya) veya eval(dikkatli değilseniz güvenlik dahil çeşitli sorunları ) koymak için kullanabilirsiniz . Eğer kullanıyorsanız, evalisterseniz bunu tamam olmalıdıreval "$target=\$substr" . \ Gerçi unutursanız , içeriğinde boşluk varsa, muhtemelen bir komut yürütür substr.
Graeme

9

Bash 4.3 Yeni olduğu -niçin opsiyon declare& local:

func() {
    local -n ref="$1"
    ref="hello, world"
}

var='goodbye world'
func var
echo "$var"

Bu çıktı hello, world.


Bash'deki namerefs ile ilgili tek sorun, nameref ile aynı ada sahip bir değişkene (fonksiyonun dışında) atıfta bulunan bir nameref'iniz (örneğin bir fonksiyonda) olamayacağınızdır. Ancak bu sürüm 4.5 için düzeltilebilir.
Kusalananda

2

evalBir parametre ayarlamak için kullanabilirsiniz . Bu komutun açıklaması burada bulunabilir . Aşağıdaki kullanımı evalyanlış:

yanlış(){
  eval $ 1 = 2 $
}

Ek değerlendirme ile ilgili olarak evalkullanmanız gereken gelmez

atamak(){
  eval $ 1 = '2 $'
}

Bu işlevleri kullanmanın sonuçlarını kontrol edin:

$ X1 = 'X2 $'
$ X2 = 'X3 $'
$ X3 = 'xxx'
$ 
echo: $ X1:
: $ X2:
echo: $ X2:
: $ X3:
echo: $ X3:
: Xxx:
$ 
$ yanlış Y $ X1
echo: $ Y:
: $ X3:
$ 
$ Y $ X1 atar
echo: $ Y:
: $ X2:
$ 
$ Y "hallo dünyası" atar
echo: $ Y:
: merhaba dünya:
$ # aşağıdakiler beklenmedik olabilir
$ Z $ Y atar
$ echo ": $ Z:"
: Hallo:
$ # yani bir değişkense ikinci argümanı alıntılamanız gerekir.
$ Z "$ Y" ata
$ echo ": $ Z:"
: merhaba dünya:

Ama amacınızı kullanmadan gerçekleştirebilirsiniz eval. Bu yolu daha basit tercih ederim.

Aşağıdaki işlev yerine koymayı doğru şekilde yapar (umarım)

augment () {
  yerel CURRENT = 1 ABD doları
  yerel AUGMENT = 2 $
  yerel YENİ
  eğer [[-z $ CURRENT]]; sonra
    YENİ = $ güçlendirmek
  elif [[! (($ CURRENT = $ AUGMENT) || ($ CURRENT = $ AUGMENT: *) || \
    (CURRENT $ = *: $ AUGMENT) || (AKIM $ = *: $ AUGMENT: *))]]; sonra
    YENİ = $ AKIM: $ güçlendirmek
  Başka
    YENİ $ CURRENT =
    fi
  echo "$ NEW"
}

Aşağıdaki çıktıyı kontrol edin

augment / usr / bin / bin
/ Usr / bin: / bin

augment / usr / bin: / bin / bin
/ Usr / bin: / bin

augment / usr / bin: / bin: / usr / local / bin / bin
/ Usr / bin: / bin: / usr / local / bin

augment / bin: / usr / bin / bin
/ Bin: / usr / bin

büyütme / bin / bin
/çöp Kutusu


augment / usr / bin: / bin
/ Usr / bin :: / bin

augment / usr / bin: / bin: / bin
/ Usr / bin: / bin:

augment / usr / bin: / bin: / usr / local / bin: / bin
/ Usr / bin: / bin: / usr / local / bin:

augment / bin: / usr / bin: / bin
/ Bin: / usr / bin:

augment / bin: / bin
/çöp Kutusu:


büyütme: / bin
::/çöp Kutusu


büyütme "/ usr lib" "/ usr bin"
/ usr lib: / usr bin

büyütme "/ usr lib: / usr bin" "/ usr bin"
/ usr lib: / usr bin

Artık augmentbir değişkeni ayarlamak için işlevi aşağıdaki şekilde kullanabilirsiniz:

PATH = `artır PATH / bin`
CLASSPATH = `artır CLASSPATH / bin`
LD_LIBRARY_PATH = `LD_LIBRARY_PATH / usr / lib’i artır

Değerlendirme ifadeniz bile yanlış. Bu, örneğin: v='echo "OHNO!" ; var' ; l=val ; eval $v='$l' - var atamadan önce "OHNO!" =.
mikeserv

@mikeserv Yorumunuz için teşekkür ederim, ancak bunun geçerli bir örnek olmadığını düşünüyorum. Atama komut dosyasının ilk bağımsız değişkeni, değişken adı veya =atama deyiminin sol tarafında kullanılan bir değişken adı içeren bir değişken olmalıdır . Senaryomun argümanını kontrol etmediğini iddia edebilirsiniz. Bu doğru. Herhangi bir argüman olup olmadığını veya argüman sayısının geçerli olup olmadığını bile kontrol etmiyorum. Ama bu niyetti. OP isterse bu tür kontrolleri ekleyebilir.
miracle173

@mikeserv: Bence ilk argümanı sessizce geçerli bir değişken adına dönüştürme teklifiniz iyi bir fikir değil: 1) kullanıcı tarafından tasarlanmamış bazı değişkenler ayarlanmış / üzerine yazılmıştır. 2) hata fonksiyonun kullanıcıdan gizlenir. Bu asla iyi bir fikir değil. Bu gerçekleşirse bir hata ortaya çıkar.
miracle173

@mikeserv: Birisi değişkeninizi vatama işlevinin ikinci argümanı olarak kullanmak istediğinde (değerini daha iyi) ilginçtir . Dolayısıyla değeri bir görevin sağ tarafında olmalıdır. İşlevin argümanını alıntılamak gerekir assign. Bu inceliği yazıma ekledim.
miracle173

Muhtemelen doğrudur - ve son örneğinizde aslında eval kullanmıyorsunuz - ki bu akıllıca - bu gerçekten önemli değil. Ama diyorum ki eval kullanan ve kullanıcı girişi alan herhangi bir kod doğası gereği riskli - ve eğer örneğini kullandıysanız, ben az çaba ile benim yol rm için benim yol değiştirmek için tasarlanmış işlevi yapabilir.
mikeserv

2

Birkaç hile ile adlandırılmış parametreleri fonksiyonlara, dizilere (bash 3 ve 4'te test edilmiştir) birlikte iletebilirsiniz.

Geliştirdiğim yöntem, aşağıdaki gibi bir işleve geçirilen parametrelere erişmenizi sağlar:

testPassingParams() {

    @var hello
    l=4 @array anArrayWithFourElements
    l=2 @array anotherArrayWithTwo
    @var anotherSingle
    @reference table   # references only work in bash >=4.3
    @params anArrayOfVariedSize

    test "$hello" = "$1" && echo correct
    #
    test "${anArrayWithFourElements[0]}" = "$2" && echo correct
    test "${anArrayWithFourElements[1]}" = "$3" && echo correct
    test "${anArrayWithFourElements[2]}" = "$4" && echo correct
    # etc...
    #
    test "${anotherArrayWithTwo[0]}" = "$6" && echo correct
    test "${anotherArrayWithTwo[1]}" = "$7" && echo correct
    #
    test "$anotherSingle" = "$8" && echo correct
    #
    test "${table[test]}" = "works"
    table[inside]="adding a new value"
    #
    # I'm using * just in this example:
    test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct
}

fourElements=( a1 a2 "a3 with spaces" a4 )
twoElements=( b1 b2 )
declare -A assocArray
assocArray[test]="works"

testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..."

test "${assocArray[inside]}" = "adding a new value"

Başka bir deyişle, parametrelerinizi isimlerine göre (daha okunabilir bir çekirdeği oluşturan) arayamaz, dizileri geçebilirsiniz (ve değişkenlere referanslar - bu özellik sadece bash 4.3'te çalışır)! Ayrıca, eşlenen değişkenlerin tümü yerel kapsamda, tıpkı 1 $ (ve diğerleri) gibi.

Bu işi yapan kod oldukça hafif ve hem bash 3 hem de bash 4'te çalışıyor (bunlar test ettiğim tek sürümler). Eğer bash ile geliştirmeyi daha güzel ve kolay hale getiren daha fazla hile ile ilgileniyorsanız, Bash Infinity Framework'üme göz atabilirsiniz , aşağıdaki kod bu amaç için geliştirilmiştir.

Function.AssignParamLocally() {
    local commandWithArgs=( $1 )
    local command="${commandWithArgs[0]}"

    shift

    if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]]
    then
        paramNo+=-1
        return 0
    fi

    if [[ "$command" != "local" ]]
    then
        assignNormalCodeStarted=true
    fi

    local varDeclaration="${commandWithArgs[1]}"
    if [[ $varDeclaration == '-n' ]]
    then
        varDeclaration="${commandWithArgs[2]}"
    fi
    local varName="${varDeclaration%%=*}"

    # var value is only important if making an object later on from it
    local varValue="${varDeclaration#*=}"

    if [[ ! -z $assignVarType ]]
    then
        local previousParamNo=$(expr $paramNo - 1)

        if [[ "$assignVarType" == "array" ]]
        then
            # passing array:
            execute="$assignVarName=( \"\${@:$previousParamNo:$assignArrLength}\" )"
            eval "$execute"
            paramNo+=$(expr $assignArrLength - 1)

            unset assignArrLength
        elif [[ "$assignVarType" == "params" ]]
        then
            execute="$assignVarName=( \"\${@:$previousParamNo}\" )"
            eval "$execute"
        elif [[ "$assignVarType" == "reference" ]]
        then
            execute="$assignVarName=\"\$$previousParamNo\""
            eval "$execute"
        elif [[ ! -z "${!previousParamNo}" ]]
        then
            execute="$assignVarName=\"\$$previousParamNo\""
            eval "$execute"
        fi
    fi

    assignVarType="$__capture_type"
    assignVarName="$varName"
    assignArrLength="$__capture_arrLength"
}

Function.CaptureParams() {
    __capture_type="$_type"
    __capture_arrLength="$l"
}

alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\$@\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; '
alias @param='@trapAssign local'
alias @reference='_type=reference @trapAssign local -n'
alias @var='_type=var @param'
alias @params='_type=params @param'
alias @array='_type=array @param'

1
assign () 
{ 
    if [ -z ${!1} ]; then
        eval $1=$2
    else
        if [[ ${!1} != *$2* ]]; then
            eval $1=${!1}:$2
        fi
    fi
}

$ echo =$x=
==
$ assign x y
$ echo =$x=
=y=
$ assign x y
$ echo =$x=
=y=
$ assign x z
$ echo =$x=
=y:z=

Bu uygun mu?


merhaba, seninki gibi yapmaya çalıştım, ama çalışmıyor, acemi olduğum için üzgünüm. Bana bu senaryoda neyin yanlış olduğunu söyler misiniz ?
ramgorur

1
Kullanımınız, evalkeyfi komut yürütmeye duyarlıdır.
Chris Down

Zhe evalçizgilerini daha güvenli hale getirme fikriniz mi var? Normalde, eval'e ihtiyacım olduğunu düşündüğümde, * sh kullanmamaya karar veririm ve bunun yerine farklı bir dile geçmeye karar veririm. Öte yandan, bazı PATH benzeri değişkenlere giriş eklemek için komut dosyalarında bu kullanarak, kullanıcı girişi değil, dize sabitleri ile çalışır ...

1
evalgüvenle yapabilirsiniz - ama çok düşünmek gerekiyor. Sadece bir parametreye başvurmaya çalışıyorsanız, böyle bir şey yapmak istersiniz: eval "$1=\"\$2\""bu şekilde eval'silk geçişte sadece 1 $ değerlendirir ve ikincisinde değer = "$ 2" olur. Ama başka bir şey yapmalısın - bu burada gereksiz.
mikeserv

Aslında yukarıdaki yorumum da yanlış. Yapmalısın "${1##*[;"$IFS"]}=\"\$2\""- ve hatta garanti olmadan geliyor. Veya eval "$(set -- $1 ; shift $(($#-1)) ; echo $1)=\"\$2\"". Kolay değil.
mikeserv

1

Adlandırılmış argümanlar sadece Bash'in sözdiziminin tasarlanma şekli değildir. Bash, Bourne kabuğunda yinelemeli bir gelişme olarak tasarlandı. Bu nedenle, iki mermi arasında belirli şeylerin mümkün olduğunca çalışmasını sağlamak gerekir. Değil mi Yani demek genel içeren komut dosyalarının daha kolay olması için, sadece üzerinde bir Bourne ortamından bir komut dosyası alırsak sağlarken Bourne daha iyi olması gerekiyordu bashmümkün olduğunca kolay bulunuyor. Birçok kabuk hala Bourne'u fiili bir standart olarak ele aldığı için bu önemsiz değil. İnsanlar senaryolarını Bourne uyumlu olarak yazdıkları için (bu taşınabilirlik için) ihtiyaç hala yürürlüktedir ve değişme olasılığı düşüktür.

Muhtemelen pythontamamen farklıysa , farklı bir kabuk komut dosyasına (gibi veya bir şey) bakmaktan daha iyidir . Bir dilin sınırlamalarına karşı koşuyorsanız, yeni bir dil kullanmaya başlamanız gerekir.


Belki bashhayatının başlarında bu doğruydu. Ancak şimdi özel hükümler getirildi. Tam referans değişkenleri artık mevcuttur bash 4.3- derobert'in cevabına bakınız .
Graeme

Ve buraya bakarsanız, bu tür bir şeyi sadece POSIX taşınabilir koduyla bile gerçekten kolayca yapabileceğinizi göreceksiniz: unix.stackexchange.com/a/120531/52934
mikeserv

1

Standart shsözdizimi ile ( bashyalnızca içinde değil, içinde de çalışır bash) şunları yapabilirsiniz:

assign() {
  eval '
    case :${'"$1"'}: in
      (::) '"$1"'=$2;;   # was empty, copy
      (*:"$2":*) ;;      # already there, do nothing
      (*) '"$1"'=$1:$2;; # otherwise, append with a :
    esac'
}

Çözümler kullanmak için gibi bash's declare, bu kadar güvenli uzun olduğunca $1geçerli bir değişken adını içerir.


0

İSİMLİ ARGALARDA:

Bu çok basit bir şekilde yapılır ve bashhiç gerekli değildir - bu, parametre genişletme yoluyla POSIX tarafından belirlenen temel atama davranışıdır:

: ${PATH:=this is only assigned to \$PATH if \$PATH is null or unset}

@Graeme ile benzer bir şekilde, ancak taşınabilir bir şekilde demo yapmak için:

_fn() { echo "$1 ${2:-"$1"} $str" ; }

% str= ; _fn "${str:=hello}"
> hello hello hello

Ve orada sadece str=boş bir değere sahip olduğundan emin olmak için yapıyorum , çünkü parametre genişletme zaten ayarlanmışsa kabuk ortamını yerinde yeniden atamaya karşı yerleşik korumaya sahiptir.

ÇÖZÜM:

Özel probleminiz için, kesinlikle mümkün olsa da, adlandırılmış argümanların gerekli olduğuna inanmıyorum. $IFSBunun yerine kullanın :

assign() { oFS=$IFS ; IFS=: ; add=$* 
    set -- $PATH ; for p in $add ; do { 
        for d ; do [ -z "${d%"$p"}" ] && break 
        done ; } || set -- $* $p ; done
    PATH= ; echo "${PATH:="$*"}" ; IFS=$oFS
}

İşte ben çalıştırmak ne olsun:

% PATH=/usr/bin:/usr/yes/bin
% assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/nope/bin \
    /usr/bin \
    /nope/usr/bin \
    /usr/nope/bin

> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin

% echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin

% dir="/some crazy/dir"
% p=`assign /usr/bin /usr/bin/new "$dir"`
% echo "$p" ; echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin:/some crazy/dir:/usr/bin/new
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin:/some crazy/dir:/usr/bin/new

Yalnızca henüz girilmemiş $PATHveya daha önce gelen argümanları eklediğine dikkat edin ? Ya da birden fazla argüman alsa bile? $IFSkullanışlıdır.


merhaba, takip etmeyi başaramadım, lütfen biraz daha ayrıntılı olarak açıklayabilir misiniz? Teşekkürler.
ramgorur

Bunu zaten yapıyorum ... Lütfen birkaç dakika daha ...
mikeserv

@ramgorur Daha iyisi var mı? Üzgünüm, ama gerçek hayat izinsiz girdi ve yazımı bitirmeyi beklediğimden biraz daha uzun sürdü.
mikeserv

Burada da aynısı, gerçek hayata yenik düştü. Bu şeyi kodlamak için birçok farklı yaklaşım gibi görünüyor, bana en iyisine yerleşmek için bir süre vereyim.
ramgorur

@ramgorur - elbette, seni asılı bırakmadığımdan emin olmak istedim. Bu konuda - ne istersen seç, adamım. Burada gördüğüm diğer cevapların hiçbirini kısa, taşınabilir veya sağlam bir çözüm olarak göremeyeceğim assign. Nasıl çalıştığı hakkında sorularınız varsa, cevaplamaktan memnuniyet duyarım. Ve bu arada, gerçekten adlandırılmış argümanlar istiyorsanız, başka bir işlevin argümanları için adlandırılmış bir işlevi nasıl bildireceğimi gösteren
bakmak isteyebilirsiniz

-2

Yakut, piton gibi bir şey bulamıyorum ama bu bana daha yakın geliyor

foo() {
  BAR="$1"; BAZ="$2"; QUUX="$3"; CORGE="$4"
  ...
}

Bence okunabilirlik daha iyidir, parametre adlarınızı bildirmek için 4 satır aşırıdır. Modern dillere de daha yakın görünüyor.


(1) Soru kabuk fonksiyonları ile ilgilidir. Cevabınız bir iskelet kabuğu işlevi sunuyor. Bunun ötesinde, cevabınızın soru ile ilgisi yoktur. (2) Ayrı satırlar almanın ve bunları noktalı virgül ayırıcılar olarak tek bir satıra birleştirmenin okunabilirliğini artırdığını mı düşünüyorsunuz ? Geriye aldığını düşünüyorum; tek satırlık stilinizin çok satırlı stilden daha  az okunabilir olması.
Scott

Gereksiz kısımları azaltmak önemliyse, neden tırnak işaretleri ve noktalı virgüller? a=$1 b=$2 ...aynı şekilde çalışır.
ilkkachu
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.