Estou confuso sobre um script bash.
Eu tenho o seguinte código:
function grep_search() {
magic_way_to_define_magic_variable_$1=`ls | tail -1`
echo $magic_variable_$1
}
Eu quero ser capaz de criar um nome de variável contendo o primeiro argumento do comando e com o valor de, por exemplo, a última linha de ls
.
Então, para ilustrar o que eu quero:
$ ls | tail -1
stack-overflow.txt
$ grep_search() open_box
stack-overflow.txt
Então, como devo definir / declarar $magic_way_to_define_magic_variable_$1
e como devo chamá-lo dentro do script?
Eu tentei eval
, ${...}
, \$${...}
, mas eu ainda estou confuso.
Respostas:
Use uma matriz associativa, com nomes de comandos como chaves.
Se você não pode usar matrizes associativas (por exemplo, você deve suportar
bash
3), você pode usardeclare
para criar nomes de variáveis dinâmicas:e use a expansão indireta de parâmetros para acessar o valor.
Consulte BashFAQ: Indirection - Avaliando variáveis indiretas / de referência .
fonte
-a
declara uma matriz indexada, não uma matriz associativa. A menos que o argumento togrep_search
seja um número, ele será tratado como um parâmetro com um valor numérico (o padrão é 0 se o parâmetro não estiver definido).4.2.45(2)
e declaro não listá-lo como uma opçãodeclare: usage: declare [-afFirtx] [-p] [name[=value] ...]
. Parece estar funcionando corretamente no entanto.declare -h
em 4.2.45 (2) para mim mostradeclare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]
. Você pode verificar se está realmente executando o 4.xe não o 3.2.declare $varname="foo"
?${!varname}
é muito mais simples e amplamente compatívelEstive procurando uma maneira melhor de fazê-lo recentemente. A matriz associativa parecia um exagero para mim. Olha o que eu achei:
...e depois...
fonte
prefix_${middle}_postfix
(isto é, a sua formatação não não iria trabalhar para.varname=$prefix_suffix
)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ávelvar_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ávelname
. Você pode pensar nisso como uma expansão em dois estágios:${!name}
expande para$var_37
, e expande paralolilol
.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 com
eval
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á-loprintf %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.Desvantagens:
eval
é mau.eval
é mau.eval
é mau.1b. Atribuindo com
read
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: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:
1c. Atribuindo com
printf
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.Desvantagens:
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}
.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, comoarr[42]
. Mas o Bash avalia o conteúdo dos colchetes toda vez que necessário, para que o aliasarr[$(do_evil)]
tenha efeitos inesperados ... Como conseqüência, use essas técnicas apenas quando você controlar a proveniência do alias .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 .Fonte:
fonte
${!varname}
técnica requer uma var intermediária paravarname
.O exemplo abaixo retorna o valor de $ name_of_var
fonte
echo
s com uma substituição de comando (que perde aspas) é desnecessário. Além disso, a opção-n
deve ser dadaecho
. E, como sempre,eval
é inseguro. Mas tudo isso é desnecessário desde Bash tem uma sintaxe mais segura, mais claro e mais curto para este fim:${!var}
.Isso deve funcionar:
fonte
Isso vai funcionar também
No seu caso
fonte
De acordo com BashFAQ / 006 , você pode usar
read
com a sintaxe aqui para atribuir variáveis indiretas:Uso:
fonte
Usar
declare
Não há necessidade de usar prefixos como em outras respostas, nem matrizes. Use apenas
declare
, aspas duplas , e expansão de parâmetros .Costumo usar o seguinte truque para analisar listas de
one to n
argumentos que contenham argumentos formatados comokey=value otherkey=othervalue etc=etc
, como:Mas expandindo a lista argv como
Dicas extras
fonte
printf
oueval
Uau, a maior parte da sintaxe é horrível! Aqui está uma solução com alguma sintaxe mais simples, se você precisar fazer referência indireta a matrizes:
Para casos de uso mais simples, recomendo a sintaxe descrita no Advanced Bash-Scripting Guide .
fonte
foo_1
efoo_2
estiverem livres de espaço em branco e símbolos especiais. Exemplos de entradas problemáticas:'a b'
criará duas entradas dentromine
.''
não criará uma entrada dentromine
.'*'
será expandido para o conteúdo do diretório de trabalho. Você pode evitar esses problemas citando:eval 'mine=( "${foo_'"$i"'[@]}" )'
[@]
construções."${array[@]}"
sempre será expandido para a lista correta de entradas sem problemas como divisão de palavras ou expansão de*
. Além disso, o problema de divisão de palavras só pode ser contornadoIFS
se você souber algum caractere não nulo que nunca apareça dentro da matriz. Além disso, o tratamento literal de*
não pode ser alcançado pela configuraçãoIFS
. Você defineIFS='*'
e divide as estrelas ou defineIFS=somethingOther
e*
expande.|
ouLF
como IFS. Novamente, o problema geral nos loops é que a tokenização ocorre por padrão, de modo que a citação é a solução especial para permitir seqüências estendidas que contêm tokens. (É expansão globbing / parâmetro ou seqüências de caracteres estendidas entre aspas, mas não ambas.) Se forem necessárias oito aspas para ler uma var, shell é o idioma errado.Para matrizes indexadas, é possível referenciá-las da seguinte maneira:
Matrizes associativas podem ser referenciadas da mesma forma, mas precisam ser
-A
ativadas emdeclare
vez de-a
.fonte
Um método extra que não depende da versão do shell / bash que você possui está usando
envsubst
. Por exemplo:fonte
script.sh
Arquivo:Teste:
Conforme
help eval
:Você também pode usar
${!var}
a expansão indireta do Bash , como já mencionado, no entanto, ele não suporta a recuperação de índices de matriz.Para mais leituras ou exemplos, consulte BashFAQ / 006 sobre Indirection .
No entanto, você deve considerar novamente o uso de indireção, conforme as notas a seguir.
fonte
para o
varname=$prefix_suffix
formato, basta usar:fonte