Bash parametre dizisini $ @ dizinleme ve değiştirme


11

İçindeki dizinlere atıfta bulunmak mümkün mü $@? GrayCat'in wiki'sinde aşağıdaki gibi kullanmak için herhangi bir referans bulamıyorum ve Gelişmiş Komut Dosyası Kılavuzu ve diğerleri bunu değiştirmeden önce bunu farklı bir değişkene atar.

$ echo ${@[0]}
-bash: ${@[0]}: bad substitution

Amaç DRY : İlk argüman bir şey için kullanılır ve geri kalanı başka bir şey için kullanılır ve kodun normalleştirilmesi, $@dizi veya bunun için ayrı bir işlev oluşturmak için çoğaltılmasından kaçınmak istiyorum. muhtemelen en kolay çıkış yolu).

Açıklama: Nesne değerlerini değiştirmek için olan değişken uzunluk $@ yapmak için kodu ayıklamak kolaylaştırır. Mevcut sürüm, benim gibi tuhaf yollar için bile çalışmasına rağmen benim beğenme için biraz fazla hacky

$'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'

Güncelleme : Görünüşe göre bu mümkün değil. Kod artık hem kod hem de veri çoğaltma kullanıyor, ancak en azından çalışıyor:

path_common()
{
    # Get the deepest common path.
    local common_path="$(echo -n "${1:-}x" | tr -s '/')"
    common_path="${common_path%x}"
    shift # $1 is obviously part of $1
    local path

    while [ -n "${1+defined}" ]
    do
        path="$(echo -n "${1}x" | tr -s '/')"
        path="${path%x}"
        if [[ "${path%/}/" = "${common_path%/}/"* ]]
        then
            shift
        else
            new_common_path="${common_path%/*}"
            [ "$new_common_path" = "$common_path" ] && return 1 # Dead end
            common_path="$new_common_path"
        fi
    done
    printf %s "$common_path"
}

Bounty , kodu makul bir boyutta tutar ve tüm birim testlerini başarıyla gerçekleştirirken, çift ​​eğik çizgileri veya tutulacak verilerin çoğaltılmasını veya diğer parametrelerin ya da her ikisini de çökertmek için kodun çoğaltılmasından kurtulabilen herkese gider :$1

test "$(path_common /a/b/c/d /a/b/e/f; echo x)" = /a/bx
test "$(path_common /long/names/foo /long/names/bar; echo x)" = /long/namesx
test "$(path_common / /a/b/c; echo x)" = /x
test "$(path_common a/b/c/d a/b/e/f ; echo x)" = a/bx
test "$(path_common ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
test "$(path_common $'\n/\n/\n' $'\n/\n'; echo x)" = $'\n/\n'x
test "$(path_common --/-- --; echo x)" = '--x'
test "$(path_common '' ''; echo x)" = x
test "$(path_common /foo/bar ''; echo x)" = x
test "$(path_common /foo /fo; echo x)" = x
test "$(path_common $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n' $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'; echo x)" = $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'x
test "$(path_common /foo/bar //foo//bar//baz; echo x)" = /foo/barx
test "$(path_common foo foo; echo x)" = foox
test "$(path_common /fo /foo; echo x)" = x

Yanıtlar:


16

POSIX

Tüm parametrelerdeki eğik çizgileri normalleştirmek için, döndürülen argüman hilesini kullanacağım: shift $1off, dönüştür ve sonucu parametre listesinin sonuna koy. Bunu parametreler olduğu kadar çok yaparsanız, tüm parametreleri dönüştürdünüz ve bunları sırayla geri aldınız.

Kodun ikinci kısmı için, mantığınızı daha az kafa karıştırıcı olacak şekilde değiştirdim: dış döngü parametrelerin üzerinde yinelenir ve iç döngü yol bileşenleri üzerinde yinelenir. for x; do … donekonumsal parametreler üzerinde tekrarlar, uygun bir deyimdir. Bir dize bir desene karşı eşleşen POSIX uyumlu bir yol kullanın: caseyapı.

Dash 0.5.5.1, pdksh 5.2.14, bash 3.2.39, bash 4.1.5, ksh 93s +, zsh 4.3.10 ile test edilmiştir.

Yan not: bash 4.1.5'te (3.2'de değil) bir hata var gibi görünüyor: durum örneği ise "${common_path%/}"/*, testlerden biri başarısız olur.

posix_path_common () {
  for tmp; do
    tmp=$(printf %s. "$1" | tr -s "/")
    set -- "$@" "${tmp%.}"
    shift
  done
  common_path=$1; shift
  for tmp; do
    while case ${tmp%/}/ in "${common_path%/}/"*) false;; esac; do
      new_common_path=${common_path%/*}
      if [ "$new_common_path" = "$common_path" ]; then return 1; fi
      common_path=$new_common_path
    done
  done
  printf %s "$common_path"
}

bash, ksh

Eğer bash (veya ksh) iseniz, dizileri kullanabilirsiniz - kendinizi konumsal parametrelerle neden kısıtladığınızı anlamıyorum. İşte bir dizi kullanan bir sürüm. POSIX sürümünden daha net olmadığını itiraf etmeliyim, ancak ilk n ^ 2 karıştırmadan kaçınıyor.

Çizgi normalleştirme adıma, ksh93 yapı kullanma ${foo//PATTERN/REPLACEMENT}tüm tekrarlarını değiştirmek için yapısını PATTERNiçinde $footarafından REPLACEMENT. Desen +(\/)bir veya daha fazla eğik çizgiyle eşleşmelidir; bash altında, shopt -s extglobyürürlükte olmalıdır (eşdeğer olarak bash ile başlayın bash -O extglob). Yapı set ${!a[@]}, konum parametrelerini dizinin abonelikler listesine ayarlar a. Bu, dizinin öğeleri üzerinde yineleme yapmak için uygun bir yol sağlar.

İkinci kısımda, POSIX sürümü ile aynı döngü mantığı. Bu sefer, [[ … ]]burada hedeflenen tüm kabuklar onu desteklediğinden kullanabilirim.

Bas 3.2.39, bash 4.1.5, ksh 93s + ile test edilmiştir.

array_path_common () {
  typeset a i tmp common_path new_common_path
  a=("$@")
  set ${!a[@]}
  for i; do
    a[$i]=${a[$i]//+(\/)//}
  done
  common_path=${a[$1]}; shift
  for tmp; do
    tmp=${a[$tmp]}
    while [[ "${tmp%/}/" != "${common_path%/}/"* ]]; do
      new_common_path="${common_path%/*}"
      if [[ $new_common_path = $common_path ]]; then return 1; fi
      common_path="$new_common_path"
    done
  done
  printf %s "$common_path"
}

zsh

Ne yazık ki, zsh ${!array[@]}, ksh93 sürümünü olduğu gibi yürütme özelliğinden yoksundur . Neyse ki, zsh ilk kısmı bir esinti haline getiren iki özelliğe sahiptir. Konum parametrelerini diziymiş gibi dizine ekleyebilirsiniz @, böylece ara dizi kullanmaya gerek yoktur. Ve zsh bir dizi yineleme yapısına sahiptir : "${(@)array//PATTERN/REPLACEMENT}"sırayla her dizi öğesinde desen değiştirme gerçekleştirir ve sonuç dizisini değerlendirir (sonuç birden fazla kelime olmasına rağmen kafa karıştırıcı olarak çift tırnaklara ihtiyacınız vardır; bu bir genellemedir "$@"). İkinci kısım temelde değişmemiştir.

zsh_path_common () {
  setopt local_options extended_glob
  local tmp common_path new_common_path
  set -- "${(@)@//\/##//}"
  common_path=$1; shift
  for tmp; do
    while [[ "${tmp%/}/" != "${common_path%/}/"* ]]; do
      new_common_path="${common_path%/*}"
      if [[ $new_common_path = $common_path ]]; then return 1; fi
      common_path="$new_common_path"
    done
  done
  printf %s "$common_path"
}

Test senaryoları

Çözümlerim minimum düzeyde test edilmiş ve yorumlanmıştır. Test durumlarınızın sözdizimini, olmayan kabukları altında ayrıştırmak $'…'ve hataları daha uygun bir şekilde bildirmek için değiştirdim.

do_test () {
  if test "$@"; then echo 0; else echo $? "$@"; failed=$(($failed+1)); fi
}

run_tests () {
  function_to_test=$1; shift
  failed=0
  do_test "$($function_to_test /a/b/c/d /a/b/e/f; echo x)" = /a/bx
  do_test "$($function_to_test /long/names/foo /long/names/bar; echo x)" = /long/namesx
  do_test "$($function_to_test / /a/b/c; echo x)" = /x
  do_test "$($function_to_test a/b/c/d a/b/e/f ; echo x)" = a/bx
  do_test "$($function_to_test ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
  do_test "$($function_to_test '
/
/
' '
/
'; echo x)" = '
/
'x
  do_test "$($function_to_test --/-- --; echo x)" = '--x'
  do_test "$($function_to_test '' ''; echo x)" = x
  do_test "$($function_to_test /foo/bar ''; echo x)" = x
  do_test "$($function_to_test /foo /fo; echo x)" = x
  do_test "$($function_to_test '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
' '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'; echo x)" = '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'x
  do_test "$($function_to_test /foo/bar //foo//bar//baz; echo x)" = /foo/barx
  do_test "$($function_to_test foo foo; echo x)" = foox
  do_test "$($function_to_test /fo /foo; echo x)" = x
  if [ $failed -ne 0 ]; then echo $failed failures; return 1; fi
}

1
+50, sadece vay canına. Herhangi bir oranda istediğimden daha fazla. Siz, efendim, harikasınız.
l0b0

POSIX tartışmasında, eğik çizgileri normalleştirdiğiniz ilk döngüde neden "." sprintf ile bir sonraki satıra geçip? Kod onsuz çalışıyor gibi görünüyor, ama farkında değilim bir kenar dava ele şüpheliyim.
Alan De Smet

1
@AlanDeSmet Edge, dizenin yeni satırla bitmesi durumunda ortaya çıkar. Komut ikamesi, takip eden yeni satırları kaldırır.
Gilles 'SO- kötü olmayı bırak

6

Neden sadece 1 $, 2 $ .. 9 $, {10}, $ {11} .. vb. Kullanmıyorsunuz? Daha da KURU - ne yapmaya çalıştığınızdan daha :)

$ Number ve $ @ arasındaki ilişki hakkında daha fazla bilgi :

$ @ "tüm bağımsız değişkenleri içeren bir dizinin tüm öğeleri" için kısayol olarak düşünülebilir

Yani, $ @ bir tür $ {args [@]} kısayoludur (buradaki argümanlar, tüm değişkenleri içeren bir 'sanal' dizidir - gerçek bir değişken değil, unutmayın)

$ 1 $ {args [1]}, $ 2 $ {args [2]}, vb.

[9] tuşuna bastığınızda bir ayraç kullanın: $ {10} $ {args [10]}, $ {11} $ {args [11]}, vb.


Dolaylı olarak bir komut satırı bağımsız değişkeni kullanın

argnum=3  # You want to get the 3rd arg
do-something ${!argnum}  # Do something with the 3rd arg

Misal:

argc=$#
for (( argn=1; argn<=argc; argn++)); do
    if [[ ${!argn} == "foo" ]]; then
        echo "Argument $argn of $argc is 'foo'"
    fi
done

$ * Number * kullanmanın bariz dezavantajı, bir dizin değişkenini olduğu gibi kullanamayacağınızdır ${args[$i]}.
intuited

@intuited sonra dolaylı yol kullanın; Cevabımı düzenleyeceğim.
pepoluan

5

İlk argüman bir şey için kullanılır, geri kalanı başka bir şey için kullanılır,

Bence ne istiyorsun shift

$ set one two three four five
$ echo $@
one two three four five
$ echo $1
one
$ foo=$1
$ echo $foo
one
$ shift
$ echo $@
two three four five
$ shift 2
$ echo $@
four five
$ echo $1
four

1

Neden sadece $ 1 $ 2, vb kullanmıyorsun bilmiyorum ama .. Bu sizin ihtiyaçlarınıza uygun olabilir.

$ script "ed    it" "cat/dog"  33.2  \D  

  echo "-------- Either use 'indirect reference'"
  for ((i=1;i<=${#@};i++)) ;do
    #  eval echo \"\$$i\" ..works, but as *pepoluan* 
    #    has pointed out: echo "${!i}" ..is better.
    echo "${!i}"
  done
  echo "-------- OR use an array"
  array=("$@")
  for ((i=0;i<${#array[@]};i++)) ;do
    echo "${array[$i]}" 
  done
  echo "-------- OR use 'set'"
  set  "$@"
  echo "$1"
  echo "$2"
  echo "$3"
  echo "$4"

çıktı

  -------- Either use 'indirect reference'
  ed    it
  cat/dog
  33.2
  D
  -------- OR use an array
  ed    it
  cat/dog
  33.2
  D
  -------- OR use 'set'
  ed    it
  cat/dog
  33.2
  D

set onu takip eden herhangi bir şey, 1 $, 2 $ oluşturmak için .. vb .. Bu tabii ki orijinal değerleri geçersiz kılacaktır, bu yüzden sadece farkında olun.


ahh ... yani 'eval' ile dolaylı referans demek
istedin

@pepoluan ... Beni bu konuda uyardığın için teşekkürler. Bu .... ben :( ben daha okumak olsaydı, ben çok orada söz görürdü anılan web sayfasına sadece şimdi gitti geri ettik (... yazma çok daha basittir
Peter.O

heh. ancak eğer dolaylı gösterim sol tarafta olursa , eval gerekli bir kötülüktür, tho ':)
pepoluan

@peopluan ... tamam, işaret ettiğin için teşekkürler ... ve bir kenara: evalBazıları tarafından neden kabul edildiğini anlamıyorum evil... (belki de yazım yüzünden :) ... Eğer eval"kötü" olduğunu, daha sonra $ {! isim} eşit "kötü" olduğunu? ... Bana göre bu sadece dilin bir parçası ve yararlı bir parçası, ama ben kesinlikle $ {! Var} tercih ediyorum ...
Peter.O

1

Not Dosya adlarındaki boşlukları destekliyorum.

function SplitFilePath {
    IFS=$'/' eval "${1}"=\( \${2} \)
}
function JoinFilePath {
    IFS=$'/' eval echo -n \"\${*}\"
    [ $# -eq 1 -a "${1}" = "" ] && echo -n "/"
}
function path_common {
    set -- "${@//\/\///}"       ## Replace all '//' with '/'
    local -a Path1
    local -i Cnt=0
    SplitFilePath Path1 "${1}"
    IFS=$'/' eval set -- \${2} 
    for CName in "${Path1[@]}" ; do
        [ "${CName}" != "${1}" ] && break;
        shift && (( Cnt++ ))
    done
    JoinFilePath "${Path1[@]:0:${Cnt}}"
}

Boşluklu dosya adları için bir test durumu ekledim ve baştaki /

    do_test () {

  if test "${@}"; then echo 0; else echo $? "$@"; failed=$(($failed+1)); fi
}

run_tests () {
  function_to_test=$1; shift
  failed=0
  do_test "$($function_to_test /a/b/c/d /a/b/e/f; echo x)" = /a/bx
  do_test "$($function_to_test /long/names/foo /long/names/bar; echo x)" = /long/namesx
  do_test "$($function_to_test / /a/b/c; echo x)" = /x      
  do_test "$($function_to_test a/b/c/d a/b/e/f ; echo x)" = a/bx
  do_test "$($function_to_test ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
  do_test "$($function_to_test '
/
/
' '
/
'; echo x)" = '
/
'x
  do_test "$($function_to_test --/-- --; echo x)" = '--x'
  do_test "$($function_to_test '' ''; echo x)" = x
  do_test "$($function_to_test /foo/bar ''; echo x)" = x
  do_test "$($function_to_test /foo /fo; echo x)" = /x      ## Changed from x
  do_test "$($function_to_test '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
' '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'; echo x)" = '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'x
  do_test "$($function_to_test /foo/bar //foo//bar//baz; echo x)" = /foo/barx
  do_test "$($function_to_test foo foo; echo x)" = foox
  do_test "$($function_to_test /fo /foo; echo x)" = /x          ## Changed from x
  do_test "$($function_to_test "/fo d/fo" "/fo d/foo"; echo x)" = "/fo dx"

  if [ $failed -ne 0 ]; then echo $failed failures; return 1; fi
}
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.