Bash의 동적 변수 이름


159

나는 bash 스크립트에 대해 혼란스러워한다.

다음 코드가 있습니다.

function grep_search() {
    magic_way_to_define_magic_variable_$1=`ls | tail -1`
    echo $magic_variable_$1
}

명령의 첫 번째 인수를 포함하고 예를 들어 마지막 줄의 값을 포함하는 변수 이름을 만들 수 있기를 원합니다 ls.

그래서 내가 원하는 것을 설명하기 위해 :

$ ls | tail -1
stack-overflow.txt

$ grep_search() open_box
stack-overflow.txt

그렇다면 어떻게 정의 / 선언 $magic_way_to_define_magic_variable_$1하고 스크립트 내에서 어떻게 호출해야합니까?

내가 시도 eval, ${...}, \$${...},하지만, 난 여전히 혼란 스러워요.


3
하지마 연관 배열을 사용하여 명령 이름을 데이터에 맵핑하십시오.
chepner

3
VAR = A; VAL = 333; "$ VAR"<<< "$ VAL"을 읽습니다. echo "A = $ A"
Grigory K

답변:


150

명령 이름을 키로 사용하여 연관 배열을 사용하십시오.

# Requires bash 4, though
declare -A magic_variable=()

function grep_search() {
    magic_variable[$1]=$( ls | tail -1 )
    echo ${magic_variable[$1]}
}

연관 배열을 사용할 수없는 경우 (예 : bash3을 지원해야 함 ) declare동적 변수 이름을 작성하는 데 사용할 수 있습니다 .

declare "magic_variable_$1=$(ls | tail -1)"

간접 매개 변수 확장을 사용하여 값에 액세스하십시오.

var="magic_variable_$1"
echo "${!var}"

BashFAQ : 간접-간접 / 참조 변수 평가를 참조하십시오 .


5
@DeaDEnD -a는 연관 배열이 아닌 인덱스 배열을 선언합니다. 인수가 grep_search숫자가 아닌 한, 숫자 값을 가진 매개 변수로 취급됩니다 (매개 변수가 설정되지 않은 경우 기본값은 0입니다).
chepner

1
흠. 나는 bash를 사용 4.2.45(2)하고 있으며 선언은 옵션으로 나열하지 않습니다 declare: usage: declare [-afFirtx] [-p] [name[=value] ...]. 그러나 제대로 작동하는 것 같습니다.
fent

declare -h나를 위해 4.2.45 (2)에서 보여줍니다 declare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]. 실제로 3.2가 아닌 4.x를 실행하고 있는지 다시 확인할 수 있습니다.
chepner

5
왜 안돼 declare $varname="foo"?
벤 데이비스

1
${!varname}훨씬 간단하고 광범위하게 호환됩니다
Brad Hein

227

최근에 더 나은 방법을 찾고있었습니다. 연관 배열이 과도하게 들렸습니다. 내가 찾은 것을보십시오 :

suffix=bzz
declare prefix_$suffix=mystr

...그리고...

varname=prefix_$suffix
echo ${!varname}

함수 내에서 전역을 선언하려면 bash> = 4.2에서 "declare -g"를 사용할 수 있습니다. 이전 bash에서는 나중에 값을 변경하지 않으려는 경우 "declare"대신 "readonly"를 사용할 수 있습니다. 구성이나 무엇을 할 수 있습니다.
Sam Watkins

7
최고의 변수 형식을 캡슐화 사용하려면 prefix_${middle}_postfix(즉, 당신은을위한 것하지하지 작업을 포맷. varname=$prefix_suffix)
msciwoj

1
나는 bash 3에 갇혀 있었고 연관 배열을 사용할 수 없었다. 따라서 이것은 생명의 은인이었습니다. $ {! ...}는 Google에서 쉽지 않습니다. var 이름 만 확장한다고 가정합니다.
닐 맥길

10
@NeilMcGill : "man bash"참조 gnu.org/software/bash/manual/html_node/… : 매개 변수 확장의 기본 형식은 $ {parameter}입니다. <...> 매개 변수의 첫 문자가 느낌표 (!) 인 경우 가변 간접 레벨이 도입됩니다. Bash는 나머지 매개 변수에서 형성된 변수의 값을 변수의 이름으로 사용합니다. 그런 다음이 변수가 확장되고 해당 값은 매개 변수 자체의 값이 아닌 나머지 대체에 사용됩니다.
Yorik.sar

1
@syntaxerror : 위의 "declare"명령으로 원하는만큼 값을 지정할 수 있습니다.
Yorik.sar

48

연관 배열 외에도 Bash에서 동적 변수를 달성하는 몇 가지 방법이 있습니다. 이 모든 기술은이 답변의 끝에서 논의되는 위험을 나타냅니다.

다음 예제에서는 초기 값이 i=37이라는 변수에 별칭을 지정 한다고 가정합니다 .var_37lolilol

방법 1. "포인터"변수 사용

C 포인터와 달리 변수 이름을 간접 변수에 간단히 저장할 수 있습니다. 배시는 신택스 갖는 판독 에일리어싱 변수 : ${!name}이름 변수의 값이 변수의 값으로 확장된다 name. 이를 2 단계 확장으로 생각할 수 있습니다.으로 ${!name}확장하면 $var_37으로 확장됩니다 lolilol.

name="var_$i"
echo "$name"         # outputs “var_37”
echo "${!name}"      # outputs “lolilol”
echo "${!name%lol}"  # outputs “loli”
# etc.

불행하게도, 앨리어싱 된 변수 를 수정 하기 위한 구문이 없습니다 . 대신 다음 트릭 중 하나를 사용하여 과제를 수행 할 수 있습니다.

1a. 로 할당eval

eval사악하지만, 우리 목표를 달성하는 가장 단순하고 휴대하기 쉬운 방법이기도합니다. 과제가 두 번 평가되므로 과제의 오른쪽을 조심스럽게 탈출해야합니다 . 이 작업을 수행하는 쉽고 체계적인 방법은 오른쪽을 미리 평가하거나 사용하는 것 printf %q입니다.

그리고 왼쪽이 유효한 변수 이름인지 또는 색인이있는 이름인지 (? 인 경우) 수동으로 확인해야합니다 evil_code #. 대조적으로, 아래의 다른 모든 방법은 자동으로 적용됩니다.

# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit

value='babibab'
eval "$name"='$value'  # carefully escape the right-hand side!
echo "$var_37"  # outputs “babibab”

단점 :

  • 변수 이름의 유효성을 검사하지 않습니다.
  • eval 악하다.
  • eval 악하다.
  • eval 악하다.

1b. 로 할당read

read내장 당신이 이름, 여기 - 문자열과 함께 이용 될 수있는 사실을주고있는 변수에 값을 할당 할 수 있습니다 :

IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37"  # outputs “babibab\n”

IFS부분과 옵션은 -r그대로 옵션 동안 값이 할당되어 있는지 확인합니다 -d ''여러 줄의 값을 할당 할 수 있습니다. 이 마지막 옵션으로 인해 명령은 0이 아닌 종료 코드와 함께 리턴됩니다.

here-string을 사용 하므로 개행 문자가 값에 추가됩니다.

단점 :

  • 다소 모호한;
  • 0이 아닌 종료 코드로 리턴합니다.
  • 값에 개행을 추가합니다.

1c. 로 할당printf

Bash 3.1 (2005 릴리스) 이후로 printf내장 기능은 이름이 지정된 변수에 결과를 할당 할 수도 있습니다. 이전 솔루션과 달리, 작동하기 때문에, 물건을 피하기 위해 추가적인 노력이 필요하지 않으며, 분할 등을 방지 할 수 있습니다.

printf -v "$name" '%s' 'babibab'
echo "$var_37"  # outputs “babibab”

단점 :

  • 휴대 성이 떨어집니다.

방법 2. "참조"변수 사용

Bash 4.3 (2014 릴리스) 이후로 declare내장 기능에는 -nC ++ 참조와 같이 다른 변수에 대한 "이름 참조"인 변수를 작성 하는 옵션 이 있습니다. 방법 1에서와 같이 참조는 앨리어싱 된 변수의 이름을 저장하지만 참조에 액세스 할 때마다 (읽기 또는 할당을 위해) Bash는 자동으로 간접 지시를 해결합니다.

또한 Bash에는 참조 자체의 가치를 얻는 데있어 특별하고 매우 혼란스러운 구문이 ${!ref}있습니다.

declare -n ref="var_$i"
echo "${!ref}"  # outputs “var_37”
echo "$ref"     # outputs “lolilol”
ref='babibab'
echo "$var_37"  # outputs “babibab”

이것은 아래에 설명 된 함정을 피하지 않지만 적어도 구문을 간단하게 만듭니다.

단점 :

  • 휴대용이 아닙니다.

위험

이러한 모든 앨리어싱 기술은 몇 가지 위험을 초래합니다. 첫 번째 는 간접 지시를 읽을 때마다 (읽거나 할당하기 위해) 임의의 코드를 실행하는 것 입니다. 실제로와 같은 스칼라 변수 이름 대신과 같은 var_37배열 첨자를 별칭으로 지정할 수도 있습니다 arr[42]. 그러나 Bash는 필요할 때마다 대괄호의 내용을 평가하므로 앨리어싱 arr[$(do_evil)]은 예기치 않은 영향을 미칩니다. 따라서 앨리어스의 출처를 제어 할 때만이 기술을 사용하십시오 .

function guillemots() {
  declare -n var="$1"
  var="«${var}»"
}

arr=( aaa bbb ccc )
guillemots 'arr[1]'  # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]'  # writes twice into date.out
            # (once when expanding var, once when assigning to it)

두 번째 위험은 주기적 별명을 작성하는 것입니다. Bash 변수는 해당 범위가 아닌 이름으로 식별되므로 실수로 별칭을 만들 수 있습니다. 이는 공통 변수 이름 (과 같은 var)을 사용할 때 특히 발생할 수 있습니다 . 결과적으로 별명 변수의 이름을 제어 할 때만이 기술을 사용하십시오 .

function guillemots() {
  # var is intended to be local to the function,
  # aliasing a variable which comes from outside
  declare -n var="$1"
  var="«${var}»"
}

var='lolilol'
guillemots var  # Bash warnings: “var: circular name reference”
echo "$var"     # outputs anything!

출처:


1
특히이 ${!varname}기법은에 대한 중간 변수가 필요하기 때문에 가장 좋은 대답 입니다 varname.
RichVel

이 답변이 높게 평가되지 않았다는 것을 이해하기 어렵습니다
Marcos

18

아래 예제는 $ name_of_var의 값을 반환합니다.

var=name_of_var
echo $(eval echo "\$$var")

4
echo명령 대체 (따옴표가 누락 됨)로 두을 중첩 할 필요는 없습니다. 또한에 옵션 -n이 제공 되어야합니다 echo. 그리고 항상 그렇듯이 eval안전하지 않습니다. 그러나 Bash는이 목적을 위해 더 안전하고 명확하며 짧은 구문을 가지고 있기 때문에이 모든 것이 필요하지 ${!var}않습니다.
Maëlan

4

이것은 작동해야합니다 :

function grep_search() {
    declare magic_variable_$1="$(ls | tail -1)"
    echo "$(tmpvar=magic_variable_$1 && echo ${!tmpvar})"
}
grep_search var  # calling grep_search with argument "var"

4

이것도 작동합니다

my_country_code="green"
x="country"

eval z='$'my_"$x"_code
echo $z                 ## o/p: green

당신의 경우

eval final_val='$'magic_way_to_define_magic_variable_"$1"
echo $final_val

3

BashFAQ / 006에 따라 간접 변수를 지정하기 위해 here 문자열 구문read 과 함께 사용할 수 있습니다 .

function grep_search() {
  read "$1" <<<$(ls | tail -1);
}

용법:

$ grep_search open_box
$ echo $open_box
stack-overflow.txt

3

사용하다 declare

다른 답변과 마찬가지로 접두사를 사용할 필요가 없으며 배열도 없습니다. 단지 사용 declare, 따옴표매개 변수 확장을 .

나는 종종 다음과 같은 트릭을 사용하여 다음과 같이 one to n형식이 지정된 인수를 포함하는 인수 목록을 구문 분석합니다 key=value otherkey=othervalue etc=etc.

# brace expansion just to exemplify
for variable in {one=foo,two=bar,ninja=tip}
do
  declare "${variable%=*}=${variable#*=}"
done
echo $one $two $ninja 
# foo bar tip

그러나 argv 목록을 확장하면

for v in "$@"; do declare "${v%=*}=${v#*=}"; done

추가 팁

# parse argv's leading key=value parameters
for v in "$@"; do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
done
# consume argv's leading key=value parameters
while (( $# )); do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
  shift
done

1
이것은 매우 깨끗한 솔루션처럼 보입니다. 악 앞치마와 가볍게 침과 변수에 관련된 도구를 사용하여이 같은 겉보기에 관련이없는 또는 위험한 기능 모호하지 printfeval
kvantour을

2

와우, 대부분의 문법은 끔찍하다! 배열을 간접적으로 참조 해야하는 경우 더 간단한 구문을 사용하는 솔루션이 하나 있습니다.

#!/bin/bash

foo_1=("fff" "ddd") ;
foo_2=("ggg" "ccc") ;

for i in 1 2 ;
do
    eval mine=( \${foo_$i[@]} ) ;
    echo ${mine[@]} ;
done ;

더 간단한 유스 케이스의 경우 Advanced Bash-Scripting Guide에 설명 된 구문을 권장합니다 .


2
ABS는 예제에서 나쁜 관행을 보여준 것으로 악명 높은 사람입니다. BashFAQ # 6을 직접 주제로 하는 bash-hackers wiki 또는 Wooledge wiki기대하십시오 .
Charles Duffy

2
이것은의 항목에만 작동 foo_1하고 foo_2공백 및 특수 기호 무료입니다. 문제가있는 항목의 예 : 'a b'안에 두 개의 항목이 생성됩니다 mine. ''안에 항목을 만들지 않습니다 mine. '*'작업 디렉토리의 내용으로 확장됩니다. 인용하면 이러한 문제를 예방할 수 있습니다.eval 'mine=( "${foo_'"$i"'[@]}" )'
Socowi

@Socowi BASH의 모든 배열을 반복하는 데 일반적인 문제입니다. 이 문제는 IFS를 일시적으로 변경 한 다음 다시 변경하여 해결할 수도 있습니다. 인용문이 제대로 작동하는지 확인하는 것이 좋습니다.
ingyhere

@ingyhere 나는달라고 간청합니다. 일반적인 문제 는 아닙니다 . 표준 솔루션이 있습니다. 항상 [@]구문을 인용하십시오 . "${array[@]}"단어 분리 또는 확장과 같은 문제없이 항상 올바른 항목 목록으로 확장 *됩니다. 또한 단어 분리 문제는 IFS배열 안에 나타나지 않는 null이 아닌 문자를 아는 경우 에만 피할 수 있습니다 . 또한 *을 설정하여 문자 그대로 처리 할 수 없습니다 IFS. IFS='*'별 을 설정 및 분할하거나 설정 IFS=somethingOther*확장합니다.
Socowi

@Socowi 쉘 확장이 바람직하지 않다고 가정하고 있으며 항상 그런 것은 아닙니다. 개발자는 모든 것을 인용 한 후 쉘 표현식이 확장되지 않을 때 버그에 대해 불평합니다. 좋은 해결책은 IFS를 사용 |하거나 LFIFS로 사용하더라도 데이터를 알고 스크립트를 적절하게 빌드하는 것 입니다. 다시 말하지만, 루프 의 일반적인 문제점 은 토큰 화가 기본적으로 발생하므로 따옴표는 토큰을 포함하는 확장 문자열을 허용 하는 특별한 솔루션 입니다. (글로브 / 매개 변수 확장 또는 따옴표 붙은 확장 문자열이지만 둘다는 아닙니다.) var를 읽는 데 8 따옴표가 필요한 경우 쉘은 잘못된 언어입니다.
ingyhere

1

인덱스 배열의 경우 다음과 같이 참조 할 수 있습니다.

foo=(a b c)
bar=(d e f)

for arr_var in 'foo' 'bar'; do
    declare -a 'arr=("${'"$arr_var"'[@]}")'
    # do something with $arr
    echo "\$$arr_var contains:"
    for char in "${arr[@]}"; do
        echo "$char"
    done
done

연관 배열은 비슷하게 참조 할 수 있지만 대신 -A스위치를 declare켜야 -a합니다.


1

어떤 쉘 / bash 버전에 의존하지 않는 추가 방법은을 사용하는 것 envsubst입니다. 예를 들면 다음과 같습니다.

newvar=$(echo '$magic_variable_'"${dynamic_part}" | envsubst)

0

명령의 첫 번째 인수를 포함하는 변수 이름을 만들 수 있기를 원합니다

script.sh 파일:

#!/usr/bin/env bash
function grep_search() {
  eval $1=$(ls | tail -1)
}

테스트:

$ source script.sh
$ grep_search open_box
$ echo $open_box
script.sh

에 따라 help eval:

쉘 명령으로 인수를 실행하십시오.


${!var}이미 언급했듯이 Bash 간접 확장을 사용할 수도 있지만 배열 인덱스 검색을 지원하지 않습니다.


추가 읽기 또는 예제는 BashFAQ / 006에서 Indirection을 확인하십시오 .

POSIX 또는 Bourne 셸에서 해당 기능을 복제 할 수없는 트릭을 알지 못 eval하므로 안전하게 수행하기가 어려울 수 있습니다. 따라서이 위험을 감수하는 데 사용하십시오 .

그러나 다음 참고 사항에 따라 간접적 인 사용을 다시 고려해야합니다.

일반적으로 bash 스크립팅에서는 간접 참조가 전혀 필요하지 않습니다. 일반적으로 사람들은 Bash Array를 이해하거나 알지 못하거나 함수와 같은 다른 Bash 기능을 완전히 고려하지 않은 경우 솔루션을 찾습니다.

변수 이름이나 다른 bash 구문을 매개 변수 안에 넣는 것은 더 나은 해결책을 가진 문제를 해결하기 위해 종종 부정확하고 부적절한 상황에서 수행됩니다. 코드와 데이터의 분리를 위반하므로 버그 및 보안 문제로 미끄러 워집니다. 간접적 인 코드를 사용하면 코드의 투명성이 떨어지고 따르기가 더 어려워 질 수 있습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.