Agradeço sua ajuda com o seguinte problema:
Estou tentando definir um array que contém uma variável como parte do nome do array, exemplo: Arr_$COUNTER
(onde $COUNTER
é alterado com base em uma contagem de loops)
Todas as maneiras possíveis que tentei resultaram em um erro, como "substituição incorreta" ou "erro de sintaxe próximo ao token inesperado"
Aqui está todo o fluxo:
Existe um arquivo que contém várias linhas. cada linha tem 6 valores separados por espaço
10 20 30 40 50 60 100 200 300 400 500 600
O script destina-se a ler cada linha do arquivo e declará-lo como um array (com o número da linha que é a variável.
como teste, cada valor deverá ser impresso e eventualmente outra função será executada em cada valor.
#!/bin/bash COUNTER=1 LINES=`wc -l VALUES_FILE.txt | awk '{print $1}'` echo "Total number of lines "$LINES echo while [ $COUNTER -le $LINES ] do echo "Counter value is $COUNTER" field=`awk "NR == $COUNTER" VALUES_FILE.txt` echo "Field = $field" declare -a "arr$COUNTER=($field)" echo "arr$COUNTER[0] = ${arr$COUNTER[0]}" echo "arr$COUNTER[1] = ${arr$COUNTER[1]}" echo "arr$COUNTER[2] = ${arr$COUNTER[2]}" echo "arr$COUNTER[3] = ${arr$COUNTER[3]}" echo "arr$COUNTER[4] = ${arr$COUNTER[4]}" echo "arr$COUNTER[5] = ${arr$COUNTER[5]}" let COUNTER=COUNTER+1 echo done echo "The End" echo
Aqui está o resultado:
Número total de linhas 2 O valor do contador é 1 Campo = 10 20 30 40 50 60 ./sort.sh: linha 12: arr$COUNTER[0] = ${arr$COUNTER[0]}: substituição incorreta O fim
O que deve ser alterado/corrigido para que funcione corretamente?
agradecer !
Responder1
Algumas ideias:
Uma "expansão de parâmetro" de um valor variável (a parte ${...}):
echo "arr$COUNTER[0] = ${arr$COUNTER[0]}"
não funciona. Você pode contornar usando eval (mas eu não recomendo):
eval echo "arr$COUNTER[0] = \${arr$COUNTER[0]}"
Essa linha poderia ser escrita assim:
i="arr$COUNTER[0]"; echo "$i = ${!i}"
Isso é chamado de indireção (o!) no Bash.
Um problema semelhante acontece com esta linha:
declare -a "arr$COUNTER=($field)"
Que deve ser dividido em duas linhas e eval usado:
declare -a "arr$COUNTER" eval arr$COUNTER\=\( \$field \)
Novamente, não recomendo usar eval (neste caso).
Como você está lendo o arquivo inteiro na memória do shell, podemos também usar um método mais simples para colocar todas as linhas em um array:
readarray -t lines <"VALUES_FILE.txt"
Isso deve ser mais rápido do que chamar o awk para cada linha.
Um script com todos os itens acima poderia ser:
#!/bin/bash
valfile="VALUES_FILE.txt"
readarray -t lines <"$valfile" ### read all lines in.
line_count="${#lines[@]}"
echo "Total number of lines $line_count"
for ((l=0;l<$line_count;l++)); do
echo "Counter value is $l" ### In which line are we?
echo "Field = ${lines[l]}" ### kepth only to help understanding.
k="arr$l" ### Build the variable arr$COUNTER
IFS=" " read -ra $k <<<"${lines[l]}" ### Split into an array a line.
eval max=\${#$k[@]} ### How many elements arr$COUNTER has?
#echo "field $field and k=$k max=$max" ### Un-quote to "see" inside.
for ((j=0;j<$max;j++)); do ### for each element in the line.
i="$k[$j]"; echo "$i = ${!i}" ### echo it's value.
done
done
echo "The End"
echo
Porém, ainda assim, o AWK pode ser mais rápido, se pudéssemos executar o que você precisa no AWK.
Um processamento semelhante poderia ser feito no awk. Supondo que os 6 valores serão usados como um IP (4 deles) e os outros dois são um número e uma época.
Apenas um exemplo muito simples de um script AWK:
#!/bin/sh
valfile="VALUES_FILE.txt"
awk '
NF==6 { printf ( "IP: %s.%s.%s.%s\t",$1,$2,$3,$4)
printf ( "number: %s\t",$5+2)
printf ( "epoch: %s\t",$6)
printf ( "\n" )
}
' "$valfile"
Basta fazer uma nova pergunta com os detalhes.
Responder2
Você pode usar a variável indiretamente, se atribuir o nome e o índice a uma variável:
s="arr$COUNTER[0]"
echo "arr$COUNTER[0] = ${!s}"
Responder3
A maneira padrão de armazenar dados de matriz multidimensional em uma matriz de dimensão 1 é armazenar cada linha em um deslocamento na matriz.
O elemento (i,j)
estará localizado no índice i*m + j
onde i
está o índice da linha com base em zero, j
é o índice da coluna com base em zero e m
é o número de colunas.
Isso também facilita a leitura dos dados, pois podemos simplesmente pegar seu arquivo de entrada, alterar todos os espaços para novas linhas e usar readarray
.
Na linha de comando:
$ readarray -t arr < <( tr -s ' ' '\n' <data )
$ printf '%s\n' "${arr[@]}"
10
20
30
40
50
60
100
200
300
400
500
600
Podemos descobrir o número de colunas nos dados com
$ m=$( awk '{ print NF; exit }' <data )
E o número de linhas:
$ n=$( wc -l <data )
Então podemos iterar colunas e linhas em um loop duplo como de costume:
for (( i = 0; i < n; ++i )); do
for (( j = 0; j < m; ++j )); do
printf '%4d' "${arr[i*m + j]}"
done
printf '\n'
done
Para os dados fornecidos, isso geraria
10 20 30 40 50 60
100 200 300 400 500 600
Idealmente, você usaria uma linguagem, como Perl, Python ou C, que suporte matrizes multidimensionais. Isto é, se você realmente precisar armazenar todo o conjunto de dados na memória e não puder processá-los linha por linha.
Para processamento linha por linha, awk
seria um bom candidato para substituir bash
(qualquerseria uma boa candidata para substituir um shell para qualquer tipo de processamento de dados):
awk '{ for (i = 1; i <= NF; ++i) printf("%4d", $i); printf("\n") }' data
Responder4
Você pode gerar nomes usando eval
, por exemplo,
eval declare -a '"arr'$COUNTER'=($field)"'
essencialmente citando todos os metacaracteres, exceto aqueles que você deseja avaliar.
Então... se $COUNTER
for 1, seu script serviria
declare -a "arr1=($field)"
Leitura adicional: