Além das matrizes associativas, existem várias maneiras de obter variáveis dinâmicas no Bash. Observe que todas essas técnicas apresentam riscos, que são discutidos no final desta resposta.
Nos exemplos a seguir, assumirei isso i=37
e que você deseja criar um alias para a variável var_37
cujo valor inicial é lolilol
.
Método 1. Usando uma variável "ponteiro"
Você pode simplesmente armazenar o nome da variável em uma variável indireta, não muito diferente de um ponteiro C. O Bash possui uma sintaxe para ler a variável com alias: ${!name}
expande para o valor da variável cujo nome é o valor da variável name
. Você pode pensar nisso como uma expansão em dois estágios: ${!name}
expande para $var_37
, e expande para lolilol
.
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
Infelizmente, não há sintaxe de contraparte para modificar a variável com alias. Em vez disso, você pode conseguir a atribuição com um dos seguintes truques.
1a. Atribuindo comeval
eval
é mau, mas também é a maneira mais simples e portátil de alcançar nosso objetivo. Você deve escapar cuidadosamente do lado direito da tarefa, pois ela será avaliada duas vezes . Uma maneira fácil e sistemática de fazer isso é avaliar o lado direito de antemão (ou usá-lo printf %q
).
E você deve verificar manualmente se o lado esquerdo é um nome de variável válido ou um nome com índice (e se fosse evil_code #
?). Por outro lado, todos os outros métodos abaixo o aplicam automaticamente.
# 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”
Desvantagens:
- não verifica a validade do nome da variável.
eval
é mau.
eval
é mau.
eval
é mau.
1b. Atribuindo comread
O read
builtin permite atribuir valores a uma variável da qual você fornece o nome, fato que pode ser explorado em conjunto com as strings here:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
A IFS
peça e a opção -r
garantem que o valor seja atribuído como está, enquanto a opção -d ''
permite atribuir valores com várias linhas. Devido a esta última opção, o comando retorna com um código de saída diferente de zero.
Observe que, como estamos usando uma string here, um caractere de nova linha é anexado ao valor.
Desvantagens:
- um tanto obscuro;
- retorna com um código de saída diferente de zero;
- anexa uma nova linha ao valor.
1c. Atribuindo comprintf
Desde o Bash 3.1 (lançado em 2005), o printf
built-in também pode atribuir seu resultado a uma variável cujo nome é dado. Ao contrário das soluções anteriores, ele simplesmente funciona, não sendo necessário nenhum esforço extra para escapar das coisas, evitar divisões e assim por diante.
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
Desvantagens:
- Menos portátil (mas, bem).
Método 2. Usando uma variável "reference"
Desde o Bash 4.3 (lançado em 2014), o declare
built-in tem uma opção -n
para criar uma variável que é uma “referência de nome” para outra variável, assim como as referências em C ++. Assim como no método 1, a referência armazena o nome da variável com alias, mas cada vez que a referência é acessada (para leitura ou atribuição), o Bash resolve automaticamente o indireto.
Além disso, Bash tem um especial e sintaxe muito confuso para obter o valor da própria referência, juiz por si mesmo: ${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
Isso não evita as armadilhas explicadas abaixo, mas pelo menos torna a sintaxe direta.
Desvantagens:
Riscos
Todas essas técnicas de aliasing apresentam vários riscos. A primeira é a execução de código arbitrário toda vez que você resolve o indireto (para leitura ou atribuição) . De fato, em vez de um nome de variável escalar, como var_37
, você também pode usar um apelido de matriz, como arr[42]
. Mas o Bash avalia o conteúdo dos colchetes toda vez que necessário, para que o alias arr[$(do_evil)]
tenha efeitos inesperados ... Como conseqüência, use essas técnicas apenas quando você controlar a proveniência do alias .
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)
O segundo risco é criar um alias cíclico. Como as variáveis Bash são identificadas pelo nome e não pelo escopo, você pode inadvertidamente criar um alias para si mesmo (enquanto pensa que aliasaria uma variável de um escopo em anexo). Isso pode acontecer em particular ao usar nomes de variáveis comuns (como var
). Como conseqüência, use essas técnicas apenas quando você controlar o nome da variável com alias .
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!
Fonte: