Bash 매개 변수 배열 인덱싱 및 수정 $ @


11

에서 색인을 참조 할 수 $@있습니까? 나는 어떤 참조가 어디 다음과 같이 사용 찾을 수 없습니다 GrayCat의 위키 , 그리고 고급 스크립팅 가이드다른 사람이 대신 것을 수정하기 전에 다른 변수에이를 할당합니다.

$ 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

모든 매개 변수에서 슬래시를 정규화하기 위해 회전 인수 트릭을 사용합니다. Shift $1off, 변환하고 매개 변수 목록의 끝에 결과를 씁니다. 매개 변수가있는 횟수만큼 그렇게하면 모든 매개 변수를 변환 한 후 순서대로 되돌립니다.

코드의 두 번째 부분에서는 논리가 덜 혼동되도록 변경했습니다. 외부 루프는 매개 변수를 반복하고 내부 루프는 경로 구성 요소를 반복합니다. 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으로 테스트되었습니다.

참고 사항 : bash 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. 패턴은 +(\/)하나 이상의 슬래시와 일치해야합니다. bash shopt -s extglob에서 유효해야합니다 (와 마찬가지로 bash를 시작하십시오 bash -O extglob). 구문 set ${!a[@]}은 위치 매개 변수를 배열의 아래 첨자 목록으로 설정합니다 a. 이는 배열 요소를 반복하는 편리한 방법을 제공합니다.

두 번째 부분에서는 POSIX 버전과 동일한 루프 논리를 사용합니다. 이번에 [[ … ]]는 대상으로 지정된 모든 쉘이 지원 하기 때문에 사용할 수 있습니다 .

bash 3.2.39, bash 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, 그냥 와우. 여하튼, 내가 요구 한 것 이상. 당신은 훌륭합니다.
l0b0

POSIX 토론에서 슬래시를 정규화하는 첫 번째 루프에서 "."를 추가해야하는 이유 sprintf와 함께 다음 줄에서 제거? 코드가 없으면 코드가 작동하는 것 같지만 알지 못하는 경우를 처리하고 있다고 생각합니다.
Alan De Smet

1
@AlanDeSmet 문자열은 개행 문자로 끝나는 경우입니다. 명령 대체는 후행 줄 바꿈을 제거합니다.
Gilles 'SO- 악의를 그만두십시오

6

왜 $ 1, $ 2 .. $ 9, $ {10}, $ {11}. 등을 사용하지 않습니까? 그것은 당신이하려는 것보다 훨씬 건조합니다. :)

$ 번호 와 $ @ 의 관계에 대한 추가 정보 :

$ @는 "모든 인수를 포함하는 배열의 모든 요소"의 약어로 간주 될 수 있습니다.

따라서 $ @은 $ {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

$ * number *를 사용해야하는 명백한 단점은와 같이 인덱스 변수를 사용할 수 없다는 것입니다 ${args[$i]}.
intuited

그런 다음 @intuited를 사용하여 간접; 답변을 편집하겠습니다.
pepoluan

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 등을 생성하기 위해 그 뒤에 오는 모든 작업을 수행합니다. 물론 이것은 원래 값을 무시하므로 그냥 알아 두십시오.


아 ... 그렇게함으로써 당신은 간접 참조를 의미하는 '평가'는 ... $는 {! VAR}이 구조는 내가 내 대답에 쓴 같은 안전
pepoluan

@pepoluan ... 알려 주셔서 감사합니다. 작성하는 것이 훨씬 간단합니다 ... (지금 막 내가 언급 한 웹 페이지로 되돌아갔습니다. 더 읽어 보면 거기에서도 언급 한 것을 보았을 것입니다. (....
Peter.O

ㅎ 그러나 간접적으로 왼쪽 에 발생한다면 , 평가는 필요한 악, tho ':)
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
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.