बैश पैरामीटर सरणी $ @ को अनुक्रमण और संशोधित करना


11

क्या इसमें अनुक्रमणिका का उल्लेख करना संभव है $@? मुझे ग्रेकैट के विकी में कहीं भी निम्नलिखित की तरह उपयोग करने के लिए कोई संदर्भ नहीं मिल सकता है , और उन्नत स्क्रिप्टिंग गाइड और अन्य इसे संशोधित करने से पहले एक अलग चर पर असाइन करते हैं।

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

लक्ष्य DRY है : पहला तर्क एक चीज़ के लिए उपयोग किया जाता है, और बाकी किसी चीज़ के लिए, और मैं कोड को सामान्य करने, $@सरणी को सामान्य बनाने या इस के लिए एक अलग फ़ंक्शन बनाने के लिए या तो डुप्लिकेट करने से बचना चाहूंगा (हालांकि इस पर) यह शायद सबसे आसान तरीका है)।

स्पष्टता: कोड को डीबग करना आसान बनाने के लिए ऑब्जेक्ट को चर-लंबाई के मूल्यों को संशोधित करना था । वर्तमान संस्करण मेरी पसंद के हिसाब से बहुत अधिक हैक है, हालाँकि यह विचित्र रास्तों के लिए भी काम करता है$@

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

अद्यतन : ऐसा लगता है कि यह संभव नहीं है। कोड अब कोड और डेटा दोहराव दोनों का उपयोग करता है, लेकिन कम से कम यह काम करता है:

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"
}

बाउंटी किसी को भी जाता है, जो कोड के दोहराव से छुटकारा पाने के लिए डुप्लिकेट स्लैश या डेटा के दोहराव को पकड़ सकता है $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

जवाबों:


16

POSIX

सभी मापदंडों में स्लैश को सामान्य करने के लिए, मैं घूर्णन तर्क चाल का उपयोग करूंगा: $1इसे बंद करें, इसे रूपांतरित करें और परिणाम को पैरामीटर सूची के अंत में डाल दें। यदि आप ऐसा करते हैं कि जितने समय के पैरामीटर हैं, आपने सभी मापदंडों को बदल दिया है, और आप उन्हें क्रम में वापस ले आए हैं।

कोड के दूसरे भाग के लिए, मैंने आपके तर्क को कम भ्रमित होने के लिए बदल दिया: बाहरी लूप मापदंडों पर पुनरावृत्त करता है, और आंतरिक लूप पथ घटकों पर पुनरावृत्त करता है। for x; do … doneस्थितिगत मापदंडों पर आधारित है, यह एक सुविधाजनक मुहावरा है। मैं एक पैटर्न के खिलाफ एक स्ट्रिंग के मिलान के POSIX- अनुरूप तरीके का उपयोग करता हूं: caseनिर्माण।

डैश 0.5.5.1, pdksh 5.2.14, bash 3.2.39, bash 4.1.5, ksh 93s +, zsh 4.3.10 के साथ परीक्षण किया गया।

साइड नोट: बास 4.1.5 में एक बग प्रतीत होता है (3.2 में नहीं): यदि मामला पैटर्न है "${common_path%/}"/*, तो परीक्षणों में से एक विफल रहता है।

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

यदि आप bash (या ksh) में हैं, तो आप सरणियों का उपयोग कर सकते हैं - मुझे समझ में नहीं आता है कि आप अपने आप को स्थितिगत मापदंडों में सीमित क्यों करते हैं। यहाँ एक संस्करण है जो एक सरणी का उपयोग करता है। मुझे मानना ​​होगा कि यह POSIX संस्करण की तुलना में विशेष रूप से स्पष्ट नहीं है, लेकिन यह प्रारंभिक n ^ 2 फेरबदल से बचता है।

स्लैश सामान्य भाग के लिए, मैं ksh93 निर्माण का उपयोग ${foo//PATTERN/REPLACEMENT}निर्माण की सभी घटनाओं को बदलने के लिए PATTERNमें $fooसे REPLACEMENT। पैटर्न +(\/)एक या एक से अधिक स्लैश से मेल खाता है; बैश के तहत, shopt -s extglobप्रभाव में होना चाहिए (समतुल्य, साथ बैश शुरू bash -O extglob)। निर्माण set ${!a[@]}सरणी के ग्राहकों की सूची में स्थितीय मापदंडों को निर्धारित करता है a। यह सरणी के तत्वों पर पुनरावृति करने के लिए एक सुविधाजनक तरीका प्रदान करता है।

दूसरे भाग के लिए, मैं पोसिक्स संस्करण के समान लूप लॉजिक हूं। इस बार, मैं उपयोग कर सकता हूँ [[ … ]]क्योंकि यहाँ लक्षित सभी गोले इसका समर्थन करते हैं।

बैश 3.2.39, बैश 4.1.5, ksh 93s + के साथ परीक्षण किया गया।

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

अफसोस की बात है, zsh ${!array[@]}के पास ksh93 संस्करण को निष्पादित करने के लिए सुविधा का अभाव है। सौभाग्य से, zsh में दो विशेषताएं हैं जो पहले भाग को एक हवा बनाते हैं। आप स्थितीय मापदंडों को अनुक्रमित कर सकते हैं जैसे कि वे @सरणी थे , इसलिए मध्यवर्ती सरणी का उपयोग करने की कोई आवश्यकता नहीं है। और zsh में एक सरणी पुनरावृत्ति निर्माण होता है : "${(@)array//PATTERN/REPLACEMENT}"बदले में प्रत्येक सरणी तत्व पर पैटर्न प्रतिस्थापन करता है और परिणामों की सरणी का मूल्यांकन करता है (भ्रामक रूप से, आपको कई शब्दों के बावजूद दोहरे उद्धरण चिह्नों की आवश्यकता है; यह एक सामान्यीकरण है "$@")। दूसरा भाग अनिवार्य रूप से अपरिवर्तित है।

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"
}

परीक्षण के मामलों

मेरे समाधानों का न्यूनतम परीक्षण और टिप्पणी की जाती है। मैंने आपके परीक्षण मामलों के सिंटैक्स को गोले के नीचे पार्स करने के लिए बदल दिया है जो कि नहीं है $'…'और विफलताओं को अधिक सुविधाजनक तरीके से रिपोर्ट करता है।

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, बस वाह। इससे ज्यादा मैंने किसी भी दर पर मांगा। आप, सर, कमाल हैं।
lbb0

पोसिक्स चर्चा में, पहले लूप में जहां आप स्लैश को सामान्य करते हैं, क्यों "।" स्प्रिंटफ के साथ फिर अगली पंक्ति में इसे उतार दें? कोड इसके बिना काम करने लगता है, लेकिन मुझे संदेह है कि आप एक किनारे मामले को संभाल रहे हैं जिससे मैं अनजान हूं।
एलन डी स्मेट

1
@AlanDeSmet किनारे का मामला है अगर स्ट्रिंग एक नई रेखा के साथ समाप्त होती है। कमान प्रतिस्थापन प्रतिस्थापन newlines अनुगामी।
गिल्स एसओ- बुराई को रोकना '

6

आप केवल $ 1, $ 2 .. $ 9, $ {10}, $ {11} .. इत्यादि का उपयोग क्यों नहीं करते हैं? यह और भी DR -er है जो आप करने की कोशिश कर रहे हैं :)

$ संख्या और $ @ के बीच संबंध पर अधिक :

$ @ को "सभी तर्कों वाले एक सरणी के सभी तत्वों" के लिए शॉर्टहैंड माना जा सकता है

तो, $ @ एक तरह का शॉर्टहैंड है $ {args [@]} (यहाँ पर एक 'वर्चुअल' एरे है जिसमें सभी तर्क हैं - एक असली वैरिएबल नहीं, माइंड यू)

$ 1 $ {args [1]} है, $ 2 $ {args [2]} है, और इसी तरह।

जब आप [9] से टकराते हैं, तो ब्रेस का उपयोग करें: $ {10} $ {args [10]}, $ {11} $ {args [11]} है, और इसी तरह।


अप्रत्यक्ष रूप से कमांड लाइन तर्क का उपयोग करें

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

उदाहरण:

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

$ * संख्या * का उपयोग करने का स्पष्ट नकारात्मक पहलू यह है कि आप किसी अनुक्रमणिका चर का उपयोग नहीं कर सकते ${args[$i]}
intuited

@intuited तो अप्रत्यक्ष उपयोग करें; मैं अपना उत्तर संपादित करूँगा।
पेपोलुआन

5

पहले तर्क का उपयोग एक चीज़ के लिए किया जाता है, और बाकी चीज़ों के लिए,

मुझे लगता है कि तुम क्या चाहते हो 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

मुझे नहीं पता कि आप $ 1 $ 2 का उपयोग क्यों नहीं करते, आदि .. लेकिन .. यह आपकी आवश्यकताओं के अनुरूप हो सकता है।

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

उत्पादन

  -------- 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 $ 1, $ 2 .. आदि बनाने के लिए कुछ भी जो इसका अनुसरण करता है, .. यह निश्चित रूप से मूल मूल्यों को ओवरराइड करेगा, इसलिए बस इसके बारे में पता होना चाहिए।


आह ... तो 'eval' से आपका तात्पर्य अप्रत्यक्ष संदर्भ से है ... $ {! var} निर्माण अधिक सुरक्षित है, जैसे मैंने अपने उत्तर में क्या लिखा है
pepoluan

@pepoluan ... मुझे उस के लिए चेतावनी देने के लिए धन्यवाद। यह लिखना बहुत आसान है ... (मैंने अभी जिस वेबपृष्ठ का उल्लेख किया है, अगर मैं उसे आगे पढ़ता हूं, तो मैंने इसे आगे भी देखा होगा :( ....
पीटर.ओ।

हे। लेकिन अगर परोक्ष बाईं ओर होता है , तो eval एक आवश्यक बुराई है, ':)
pepoluan

@peopluan ... ठीक है, कि बाहर इशारा करने के लिए धन्यवाद ... और बस एक तरफ के रूप में: मुझे समझ में नहीं आता कि क्यों eval, कुछ द्वारा माना जाता है, evil... (शायद यह वर्तनी के कारण है :) ... यदि eval"बुरा" है, तो $ {! Var} समान रूप से "बुरा" है? ... मेरे लिए यह सिर्फ भाषा का हिस्सा है, और एक उपयोगी हिस्सा है, उस पर .. लेकिन मैं निश्चित रूप से $ {! Var} पसंद करता हूं ...
Peter.O

1

नोट मैं फ़ाइल नाम में रिक्त स्थान का समर्थन करता हूं।

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}}"
}

मैंने रिक्त स्थान के साथ फ़ाइलनाम के लिए एक परीक्षण मामला जोड़ा और 2 परीक्षण निर्धारित किए जो एक प्रमुख / लापता थे

    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
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.