"var name"을 함수에 전달하고, 함수가 "var name"을 사용하여 변수에 포함된 값을 변환하도록 한 다음 변환된 개체를 원래 "var name"으로 참조할 수 있도록 하려고 합니다.
예를 들어, 구분된 목록을 배열로 변환하는 함수가 있고 "animal_list"라는 구분된 목록이 있다고 가정해 보겠습니다. 목록 이름을 함수에 전달한 다음 현재 배열을 "animal_list"로 참조하여 목록을 배열로 변환하고 싶습니다.
코드 예:
function delim_to_array() {
local list=$1
local delim=$2
local oifs=$IFS;
IFS="$delim";
temp_array=($list);
IFS=$oifs;
# Now I have the list converted to an array but it's
# named temp_array. I want to reference it by its
# original name.
}
# ----------------------------------------------------
animal_list="anaconda, bison, cougar, dingo"
delim_to_array ${animal_list} ","
# After this point I want to be able to deal with animal_name as an array.
for animal in "${animal_list[@]}"; do
echo "NAME: $animal"
done
# And reuse this in several places to converted lists to arrays
people_list="alvin|baron|caleb|doug"
delim_to_array ${people_list} "|"
# Now I want to treat animal_name as an array
for person in "${people_list[@]}"; do
echo "NAME: $person"
done
답변1
설명하다
이것을 이해하려면 약간의 노력이 필요합니다. 인내심을 가지십시오. 이 솔루션은 bash에서 잘 작동합니다. "바심"이 필요합니다.
첫째, 변수에 대한 "간접" 액세스를 사용해야 합니다 ${!variable}
. $variable
문자열이 포함된 경우 animal_name
"매개변수 확장": ${!variable}
으로 확장됩니다 $animal_name
.
이 아이디어가 실제로 실행되는 모습을 살펴보겠습니다. 이해하기 쉽도록 여러분이 사용한 이름과 값을 최대한 유지했습니다.
#!/bin/bash
function delim_to_array() {
local VarName=$1
local IFS="$2";
printf "inside IFS=<%s>\n" "$IFS"
echo "inside var $VarName"
echo "inside list = ${!VarName}"
echo a\=\(${!VarName}\)
eval a\=\(${!VarName}\)
printf "in <%s> " "${a[@]}"; echo
eval $VarName\=\(${!VarName}\)
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "out <%s> " "${animal_list[@]}"; echo
printf "outside IFS=<%s>\n" "$IFS"
# Now we can use animal_name as an array
for animal in "${animal_list[@]}"; do
echo "NAME: $animal"
done
전체 스크립트를 실행하면(so-setvar.sh라고 가정) 다음이 표시됩니다.
$ ./so-setvar.sh
inside IFS=<,>
inside var animal_list
inside list = anaconda, bison, cougar, dingo
a=(anaconda bison cougar dingo)
in <anaconda> in <bison> in <cougar> in <dingo>
out <anaconda> out <bison> out <cougar> out <dingo>
outside IFS=<
>
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo
"내부"는 "함수 내부"를 의미하고 "외부"는 그 반대를 의미한다는 것을 이해하십시오.
내부 값은 문자열로 된 $VarName
var:의 이름입니다 .animal_list
값은 ${!VarName}
목록으로 표시됩니다.anaconda, bison, cougar, dingo
이제 솔루션이 어떻게 구축되었는지 보여주기 위해 echo가 포함된 줄이 있습니다.
echo a\=\(${!VarName}\)
eval
다음 줄이 수행하는 작업을 보여줍니다 .
a=(anaconda bison cougar dingo)
일단 그건평가하다예, 변수는 a
동물 목록을 포함하는 배열입니다. 이 경우 var a는 eval이 어떻게 영향을 미치는지 정확하게 보여주기 위해 사용됩니다.
그런 다음 의 각 요소 값 a
이 로 인쇄됩니다 <in> val
. 그리고 다음 두 줄에 표시된
대로 함수의 바깥 부분에서도 동일한 작업을 수행합니다 .<out> val
in <anaconda> in <bison> in <cougar> in <dingo>
out <anaconda> out <bison> out <cougar> out <dingo>
실제 변경 사항은 함수의 마지막 평가에서 수행됩니다.
그게 다입니다. var에는 이제 값 배열이 있습니다.
실제로 이 함수의 핵심은 단 한 줄입니다.eval $VarName\=\(${!VarName}\)
또한 IFS의 값은 함수의 로컬 값으로 설정되므로 추가 작업 없이 함수가 실행되기 전의 값으로 돌아갈 수 있습니다. 감사해요피터 코르데스독창적인 아이디어에 대한 의견입니다.
이것으로 설명이 끝났습니다. 명확해지기를 바랍니다.
실제 기능
불필요한 행을 모두 제거하고 핵심 평가만 남겨두고 IFS에 대한 새 변수만 생성하면 함수를 최소한의 표현으로 줄입니다.
delim_to_array() {
local IFS="${2:-$' :|'}"
eval $1\=\(${!1}\);
}
IFS 값을 지역 변수로 설정하면 함수에 대한 "기본" 값을 설정할 수도 있습니다. IFS에서 요구하는 값이 두 번째 매개변수로 함수에 전송되지 않는 한 로컬 IFS는 "기본값" 값을 가정합니다. 기본값은 space( )(항상 유용한 분할 값임), colon(:) 및 vertical line(|)가 되어야 한다고 생각합니다. 이 세 가지 중 하나는 값을 분할합니다. 물론 기본값은 필요에 따라 다른 값으로 설정할 수 있습니다.
사용하도록 편집됨 read
:
eval에서 참조되지 않은 값의 위험을 줄이기 위해 다음을 사용할 수 있습니다.
delim_to_array() {
local IFS="${2:-$' :|'}"
# eval $1\=\(${!1}\);
read -ra "$1" <<<"${!1}"
}
test="fail-test"; a="fail-test"
animal_list='bison, a space, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'
delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo
$ so-setvar.sh
<bison>< a space>< {1..3}><~/><${a}><$a><$((2+2))><$(echo "fail")><./*><*><*>
위의 var에 설정된 대부분의 값은 animal_list
eval에서 실패합니다.
하지만 문제없이 읽어보세요.
- 참고: eval 옵션을 사용해 보는 것은 완전히 안전합니다.이 코드에서는함수를 호출하기 전에 변수 값이 일반 텍스트 값으로 설정되었기 때문입니다. 실제로 실행되더라도 그것은 단지 텍스트일 뿐입니다. 경로명 확장이 마지막 확장이므로 파일 이름이 잘못되어도 문제가 발생하지 않으므로 경로명 확장 시 변수 확장이 다시 수행되지 않습니다. 다시,있는 그대로의 코드이는 결코 범용 유효성 검사가 아닙니다
eval
.
예
이 함수의 기능과 작동 방식을 실제로 이해하기 위해 이 함수를 사용하여 게시한 코드를 다시 작성했습니다.
#!/bin/bash
delim_to_array() {
local IFS="${2:-$' :|'}"
# printf "inside IFS=<%s>\n" "$IFS"
# eval $1\=\(${!1}\);
read -ra "$1" <<<"${!1}";
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\t " "${animal_list[@]}"; echo
people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\t " "${people_list[@]}"; echo
$ ./so-setvar.sh
NAME: anaconda NAME: bison NAME: cougar NAME: dingo
NAME: alvin NAME: baron NAME: caleb NAME: doug
보시다시피 IFS는 함수 내부에서만 설정되며 영구적으로 변경되지 않으므로 이전 값으로 재설정할 필요가 없습니다. 또한 "people_list" 함수에 대한 두 번째 호출은 두 번째 매개변수를 설정하지 않고 IFS의 기본값을 활용합니다.
경고 01:
(eval) 함수를 구성할 때 var가 따옴표 없이 쉘 구문 분석에 노출되는 지점이 있습니다. 이를 통해 IFS 값을 사용하여 "단어 분할"을 완료할 수 있습니다. 그러나 이는 또한 var의 값을 "중괄호 확장", "물결표 확장", "인수, 변수 및 산술 확장", "명령 대체" 및 "경로 이름 확장"에 노출합니다. 여기서 명령은 다음과 같습니다. <() >()
이를 지원하는 시스템에서 프로세스 교체를 수행합니다 .
모든 예제(마지막 예제 제외)는 다음과 같은 간단한 에코로 래핑됩니다(주의하세요).
a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*
즉, 파일 {~$`<>
이름으로 시작하거나 파일 이름과 일치하거나 파일 이름을 포함할 수 있는 문자열은 ?*[]
잠재적인 문제가 될 수 있습니다.
변수에 그러한 문제가 있는 값이 포함되어 있지 않다고 확신한다면 안전합니다. 그러한 값을 갖는 것이 가능하다면, 귀하의 질문에 대답하는 방법은 더 복잡하고 더 많은(또는 더 긴) 설명과 설명이 필요합니다. 사용은 read
대안입니다.
경고 02:
예, read
자체 "용"이 있습니다.
- 항상 -r 옵션을 사용하세요. 이 옵션이 필요하지 않은 상황은 생각나지 않습니다.
- 이
read
명령은 한 줄만 가져올 수 있습니다. 옵션을 설정하더라도 여러 줄을 사용하려면-d
특별한 주의가 필요합니다. 또는 전체 입력을 변수에 할당합니다. - 값에 공백이 포함되어 있으면
IFS
선행 및 후행 공백이 제거됩니다. 글쎄요, 전체 설명에는 그에 대한 몇 가지 세부정보가 포함되어야tab
하지만 생략하겠습니다. - 파이프를 통해
|
데이터를 읽지 마십시오. 이렇게 하면 서브셸에서 읽기가 발생합니다. 하위 셸에 설정된 모든 변수는 상위 셸로 돌아온 후에 유지되지 않습니다. 글쎄, 몇 가지 해결 방법이 있지만 다시 세부 사항은 건너뛰겠습니다.
원래는 읽기에 대한 경고와 문제를 포함하고 싶지 않았지만 대중의 요구에 따라 포함해야 했습니다. 죄송합니다.
답변2
Bash FAQ에는 참조/간접 호출에 대한 완전한 항목이 있습니다..
간단한 경우 이것은 eval
인용을 만드는 다른 답변에서 제안하는 더 나은 대안 입니다.많은더 쉬워졌습니다.
func() { # set the caller's simple non-array variable
local retvar=$1
printf -v "$retvar" '%s ' "${@:2}" # concat all the remaining args
}
배쉬 완성(Tab을 누를 때 실행되는 코드) 는 더 읽기 쉽고 잠재적으로 더 빠르기 때문에 내부 기능 printf -v
대신에 전환되었습니다 .eval
배열을 반환하려면배쉬 FAQread -a
배열 변수를 읽으려면 순차 배열 인덱싱을 사용하는 것이 좋습니다 .
# Bash
aref=realarray
IFS=' ' read -d '' -ra "$aref" <<<'words go into array elements'
Bash 4.3에는 참조로 호출하는 것을 더욱 편리하게 만드는 기능이 도입되었습니다. Bash 4.3은 여전히 매우 새로운 버전입니다(2014).
func () { # return an array in a var named by the caller
typeset -n ref1=$1 # ref1 is a nameref variable.
shift # remove the var name from the positional parameters
echo "${!ref1} = $ref1" # prints the name and contents of the real variable
ref1=( "foo" "bar" "$@" ) # sets the caller's variable.
}
Bash 매뉴얼 페이지의 표현은 약간 혼란스럽습니다. -n
배열변수에는 속성을 적용할 수 없다고 나옵니다 . 이는 참조 배열을 가질 수 없지만 참조는 가질 수 있음을 의미합니다.도착하다정렬.
답변3
내용만 전달하기 때문에 함수 내에서 변수(또는 이 경우 배열)를 변경할 수 없습니다. 함수는 어떤 변수가 전달되었는지 알 수 없습니다.
해결 방법으로 다음을 통과할 수 있습니다.이름변수의 값을 함수 내에서 사용하여 eval
내용을 가져옵니다.
#!/bin/bash
function delim_to_array() {
local list=$1
local delim=$2
local oifs=$IFS;
IFS="$delim"
temp_array=($(eval echo '"${'"$list"'}"'))
IFS=$oifs;
eval "$list=("${temp_array[@]}")"
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\n" "${animal_list[@]}"
people_list="alvin|baron|caleb|doug"
delim_to_array "people_list" "|"
printf "NAME: %s\n" "${people_list[@]}"
eval
사용된 줄 내의 따옴표에 세심한 주의를 기울이십시오. 표현식의 일부는 작은따옴표로 묶어야 하고 다른 부분은 큰따옴표로 묶어야 합니다. 또한 최종 인쇄에서는 루프를 for
더 간단한 명령으로 대체했습니다.printf
산출:
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo
NAME: alvin
NAME: baron
NAME: caleb
NAME: doug
답변4
function delim_to_array() {
local list=$1
local delim=$2
local oifs=$IFS;
IFS="$delim";
temp_array=($list);
IFS=$oifs;
}
따라서 이 기능을 사용하면 아주 간단한 세부 사항을 건너뛰고 있는 것 같습니다. 호출 수신자가 반복적인 처리를 수행하고 호출자가 샷을 호출하면 항상 더 쉽습니다. 해당 함수에서는 호출 수신자가 모든 호출을 수행하도록 했습니다. 이름을 이런 방식으로 처리하면 안 됩니다.
isName()
case "${1##[0-9]*}" in
(${IFS:+*}|*[!_[:alnum:]]*)
IFS= "${IFS:+isName}" "$1"|| ! :
esac 2>/dev/null
setSplit(){
isName "$1" ||
set "" "setSplit(): bad name: '$1'"
eval "shift; set -f
${1:?"$2"}=(\$*)
set +f -$-"
}
이는 배열 이름의 유효성을 안전하게 검사하고, stderr에서 의미 있는 오류 출력을 생성하며, 유효하지 않은 인수로 호출할 때 적절하게 종료를 중지합니다. 오류 출력은 다음과 같습니다.
bash: 1: setSplit(): bad name: 'arr@yname'
...어디bash
은 shell 의 현재 값입니다 $0
.arr@yname
내가 그것을 호출하고 메시지를 쓸 때, 그것은 setSplit()
첫 번째 매개변수입니다.
또한 두 개의 함수이므로 호출자는 isName()
함수를 수정하지 않고도 자체 재량에 따라 테스트를 동적으로 재정의할 수 있습니다 setSplit()
.
또한 분할 시 실수로 확장되는 것을 방지하기 위해 쉘 파일 이름 생성 glob을 안전하게 비활성화합니다. 이는 인수에 문자가 포함된 경우 기본적으로 발생할 수 있습니다 [*?
. 반환하기 전에 모든 셸 옵션을 복원하고 이를 통해 해당 옵션을 발견 당시의 상태로 변경할 수 있습니다. 즉, 셸 파일 이름 글로빙을 활성화 또는 비활성화하여 호출할 수 있으며 반환하는 것 외에도 이 설정에 영향을 미칩니다.
그러나 여기에는 한 가지 중요한 사항이 누락되어 있습니다. 즉, $IFS
구성이 없습니다. 이 함수는 패턴의 POSIX 대괄호 표현식 내용에 적용되는 isName()
다소 우려스러운 bash
버그 에 대한 해결 방법을 구현합니다.$IFS
case
(진지하게: 대체 뭐죠?)$IFS
반환하기 전에 전역 값이 존재하지 않는 경우 별도의 자체 재귀 호출로 로컬 값을 취소합니다. 그러나 이는 배열 분할과 완전히 직교합니다. 그렇지 않으면 setSplit()
아무 작업도 수행되지 않습니다 $IFS
. 이것이 바로 그 방법입니다. 그렇게 할 필요는 없습니다.
이것방문객다음과 같이 설정해야 합니다.
IFS=aBc setSplit arrayname 'xyzam*oBabc' x y z
printf '<%q>\n' "$IFS" "${arrayname[@]}"
<$' \t\n'>
<xyz>
<m\*o>
<''>
<b>
<''>
<x>
<y>
<z>
위 코드는 호출된 함수의 로컬 값을 bash
설정하여 $IFS
셸에서 작동합니다 .
POSIX적으로:
IFS=aBc command eval "setSplit arrayname 'xyzam*oBabc' x y z"
...동일한 목적을 달성할 것입니다. 차이점은 bash
특수 내장 기능과 함수에 대한 영구 환경에 관한 표준을 위반한다는 것입니다. 그렇지 않으면 명령줄에 설정된 변수가 현재 쉘 환경에 영향을 미쳐야 한다고 지정합니다.(어느 쪽이든 얻을 수 있으므로 이것이 바람직할 것입니다).
선호하는 것이 무엇이든 중요한 점은 발신자가 여기에서 샷을 호출하고 수신자는 단순히 샷을 실행한다는 것입니다.