Мне интересно, как объявить 2D-массив в bash, а затем инициализировать до 0.
В C это выглядит так:
int a[4][5] = {0};
И как назначить значение элементу? Как и в C:
a[2][3] = 3;
Мне интересно, как объявить 2D-массив в bash, а затем инициализировать до 0.
В C это выглядит так:
int a[4][5] = {0};
И как назначить значение элементу? Как и в C:
a[2][3] = 3;
Вы можете имитировать их, например, с помощью хэшей, но нужно заботиться о ведущих нулях и многом другом. Следующая демонстрация работает, но это далеко не оптимальное решение.
#!/bin/bash
declare -A matrix
num_rows=4
num_columns=5
for ((i=1;i<=num_rows;i++)) do
for ((j=1;j<=num_columns;j++)) do
matrix[$i,$j]=$RANDOM
done
done
f1="%$((${#num_rows}+1))s"
f2=" %9s"
printf "$f1" ''
for ((i=1;i<=num_rows;i++)) do
printf "$f2" $i
done
echo
for ((j=1;j<=num_columns;j++)) do
printf "$f1" $j
for ((i=1;i<=num_rows;i++)) do
printf "$f2" ${matrix[$i,$j]}
done
echo
done
приведенный выше пример создает матрицу 4x5 со случайными числами и печатает ее транспонированную, с примером результата
1 2 3 4
1 18006 31193 16110 23297
2 26229 19869 1140 19837
3 8192 2181 25512 2318
4 3269 25516 18701 7977
5 31775 17358 4468 30345
Принцип: Создание одного ассоциативного массива, где индекс представляет собой строку типа 3,4
. Преимущества:
30,40,2
для трехмерных.${matrix[2,3]}
Bash не поддерживает многомерные массивы.
Вы можете имитировать его, используя косвенное расширение:
#!/bin/bash
declare -a a0=(1 2 3 4)
declare -a a1=(5 6 7 8)
var="a1[1]"
echo ${!var} # outputs 6
С этим методом также возможны назначения:
let $var=55
echo ${a1[1]} # outputs 55
Изменить 1. Чтобы прочитать такой массив из файла, с каждой строкой в строке и значениями, разделенными пробелом, используйте это:
idx=0
while read -a a$idx; do
let idx++;
done </tmp/some_file
Изменить 2. Чтобы объявить и инициализировать a0..a3[0..4]
до 0
, вы можете запустить:
for i in {0..3}; do
eval "declare -a a$i=( $(for j in {0..4}; do echo 0; done) )"
done
Bash не имеет многомерного массива. Но вы можете имитировать несколько схожий эффект с ассоциативными массивами. Ниже приведен пример ассоциативного массива, претендующего на использование в качестве многомерного массива:
declare -A arr
arr[0,0]=0
arr[0,1]=1
arr[1,0]=2
arr[1,1]=3
echo "${arr[0,0]} ${arr[0,1]}" # will print 0 1
Если вы не объявите массив как ассоциативный (с -A
), вышеуказанное не будет работать. Например, если вы опустите строку declare -A arr
, echo
будет печатать 2 3
вместо 0 1
, потому что 0,0
, 1,0
и такие будут приняты как арифметическое выражение и оценены как 0
( значение справа от оператора запятой).
Вы также можете приблизиться к этому гораздо менее умным способом.
q=()
q+=( 1-2 )
q+=( a-b )
for set in ${q[@]};
do
echo ${set%%-*}
echo ${set##*-}
done
конечно, 22-строчное решение или косвенность, вероятно, лучший способ пойти, и почему бы не посыпать eval где угодно.
Способ имитации массивов в bash (его можно адаптировать для любого количества измерений массива):
#!/bin/bash
## The following functions implement vectors (arrays) operations in bash:
## Definition of a vector <v>:
## v_0 - variable that stores the number of elements of the vector
## v_1..v_n, where n=v_0 - variables that store the values of the vector elements
VectorAddElementNext () {
# Vector Add Element Next
# Adds the string contained in variable $2 in the next element position (vector length + 1) in vector $1
local elem_value
local vector_length
local elem_name
eval elem_value=\"\$$2\"
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
vector_length=$(( vector_length + 1 ))
elem_name=$1_$vector_length
eval $elem_name=\"\$elem_value\"
eval $1_0=$vector_length
}
VectorAddElementDVNext () {
# Vector Add Element Direct Value Next
# Adds the string $2 in the next element position (vector length + 1) in vector $1
local elem_value
local vector_length
local elem_name
eval elem_value="$2"
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
vector_length=$(( vector_length + 1 ))
elem_name=$1_$vector_length
eval $elem_name=\"\$elem_value\"
eval $1_0=$vector_length
}
VectorAddElement () {
# Vector Add Element
# Adds the string contained in the variable $3 in the position contained in $2 (variable or direct value) in the vector $1
local elem_value
local elem_position
local vector_length
local elem_name
eval elem_value=\"\$$3\"
elem_position=$(($2))
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
if [ $elem_position -ge $vector_length ]; then
vector_length=$elem_position
fi
elem_name=$1_$elem_position
eval $elem_name=\"\$elem_value\"
if [ ! $elem_position -eq 0 ]; then
eval $1_0=$vector_length
fi
}
VectorAddElementDV () {
# Vector Add Element
# Adds the string $3 in the position $2 (variable or direct value) in the vector $1
local elem_value
local elem_position
local vector_length
local elem_name
eval elem_value="$3"
elem_position=$(($2))
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
if [ $elem_position -ge $vector_length ]; then
vector_length=$elem_position
fi
elem_name=$1_$elem_position
eval $elem_name=\"\$elem_value\"
if [ ! $elem_position -eq 0 ]; then
eval $1_0=$vector_length
fi
}
VectorPrint () {
# Vector Print
# Prints all the elements names and values of the vector $1 on sepparate lines
local vector_length
vector_length=$(($1_0))
if [ "$vector_length" = "0" ]; then
echo "Vector \"$1\" is empty!"
else
echo "Vector \"$1\":"
for ((i=1; i<=$vector_length; i++)); do
eval echo \"[$i]: \\\"\$$1\_$i\\\"\"
###OR: eval printf \'\%s\\\n\' \"[\$i]: \\\"\$$1\_$i\\\"\"
done
fi
}
VectorDestroy () {
# Vector Destroy
# Empties all the elements values of the vector $1
local vector_length
vector_length=$(($1_0))
if [ ! "$vector_length" = "0" ]; then
for ((i=1; i<=$vector_length; i++)); do
unset $1_$i
done
unset $1_0
fi
}
##################
### MAIN START ###
##################
## Setting vector 'params' with all the parameters received by the script:
for ((i=1; i<=$#; i++)); do
eval param="\${$i}"
VectorAddElementNext params param
done
# Printing the vector 'params':
VectorPrint params
read temp
## Setting vector 'params2' with the elements of the vector 'params' in reversed order:
if [ -n "$params_0" ]; then
for ((i=1; i<=$params_0; i++)); do
count=$((params_0-i+1))
VectorAddElement params2 count params_$i
done
fi
# Printing the vector 'params2':
VectorPrint params2
read temp
## Getting the values of 'params2'`s elements and printing them:
if [ -n "$params2_0" ]; then
echo "Printing the elements of the vector 'params2':"
for ((i=1; i<=$params2_0; i++)); do
eval current_elem_value=\"\$params2\_$i\"
echo "params2_$i=\"$current_elem_value\""
done
else
echo "Vector 'params2' is empty!"
fi
read temp
## Creating a two dimensional array ('a'):
for ((i=1; i<=10; i++)); do
VectorAddElement a 0 i
for ((j=1; j<=8; j++)); do
value=$(( 8 * ( i - 1 ) + j ))
VectorAddElementDV a_$i $j $value
done
done
## Manually printing the two dimensional array ('a'):
echo "Printing the two-dimensional array 'a':"
if [ -n "$a_0" ]; then
for ((i=1; i<=$a_0; i++)); do
eval current_vector_lenght=\$a\_$i\_0
if [ -n "$current_vector_lenght" ]; then
for ((j=1; j<=$current_vector_lenght; j++)); do
eval value=\"\$a\_$i\_$j\"
printf "$value "
done
fi
printf "\n"
done
fi
################
### MAIN END ###
################
Другой подход заключается в том, что вы можете представлять каждую строку в виде строки, т.е. сопоставляя 2D-массив в 1D-массив. Затем все, что вам нужно сделать, это распаковать и переупаковать строку строк всякий раз, когда вы делаете редактирование:
# Init a 4x5 matrix
a=("0 0 0 0 0" "0 0 0 0 0" "0 0 0 0 0" "0 0 0 0 0")
function aset {
IFS=' ' read -r -a tmp <<< "${a[$1]}"
tmp[$2]=$3
a[$1]="${tmp[@]}"
}
# Set a[2][3] = 3
aset 2 3 3
# Show result
for r in "${a[@]}"; do
echo $r
done
Выходы:
0 0 0 0 0
0 0 0 0 0
0 0 0 3 0
0 0 0 0 0
Если каждая строка матрицы имеет одинаковый размер, вы можете просто использовать линейный массив и умножение.
То есть
a=()
for (( i=0; i<4; ++i )); do
for (( j=0; j<5; ++j )); do
a[i*5+j]=0
done
done
Затем ваш a[2][3] = 3
станет
a[2*5+3] = 3
Этот подход может стоить превратиться в набор функций, но поскольку вы не можете передавать массивы или возвращать массивы из функций, вам придется использовать pass-by-name, а иногда и eval
. Поэтому я склонен записывать многомерные массивы под "вещи bash просто не нужно делать".
Можно просто определить две функции для записи ($ 4 - это назначенное значение) и прочитать матрицу с произвольным именем ($ 1) и индексами ($ 2 и $3), использующими eval и косвенные ссылки.
#!/bin/bash
matrix_write () {
eval $1"_"$2"_"$3=$4
# aux=$1"_"$2"_"$3 # Alternative way
# let $aux=$4 # ---
}
matrix_read () {
aux=$1"_"$2"_"$3
echo ${!aux}
}
for ((i=1;i<10;i=i+1)); do
for ((j=1;j<10;j=j+1)); do
matrix_write a $i $j $[$i*10+$j]
done
done
for ((i=1;i<10;i=i+1)); do
for ((j=1;j<10;j=j+1)); do
echo "a_"$i"_"$j"="$(matrix_read a $i $j)
done
done
Для моделирования двумерного массива я сначала загружаю первые n-элементы (элементы первого столбца)
local pano_array=()
i=0
for line in $(grep "filename" "$file")
do
url=$(extract_url_from_xml $line)
pano_array[i]="$url"
i=$((i+1))
done
Чтобы добавить второй столбец, я определяю размер первого столбца и вычисляю значения в переменной смещения
array_len="${#pano_array[@]}"
i=0
while [[ $i -lt $array_len ]]
do
url="${pano_array[$i]}"
offset=$(($array_len+i))
found_file=$(get_file $url)
pano_array[$offset]=$found_file
i=$((i+1))
done
Марк Рид предложил очень хорошее решение для 2D-массивов (матрицы)! Они всегда могут быть преобразованы в одномерный массив (вектор). Хотя Bash не имеет встроенной поддержки 2D-массивов, создать простой ADT на основе упомянутого принципа не так уж и сложно.
Вот простой пример без проверок аргументов и т.д., Просто для ясности решения: размер массива задается как два первых элемента в экземпляре.
#!/bin/bash
matrix_init() {
# matrix_init instance x y data ...
declare -n self=$1
declare -i width=$2 height=$3
shift 3;
self=(${width} ${height} "[email protected]")
}
matrix_get() {
# matrix_get instance x y
declare -n self=$1
declare -i x=$2 y=$3
declare -i width=${self[0]} height=${self[1]}
echo "${self[2+y*width+x]}"
}
matrix_set() {
# matrix_set instance x y data
declare -n self=$1
declare -i x=$2 y=$3
declare data="$4"
declare -i width=${self[0]} height=${self[1]}
self[2+y*width+x]="${data}"
}
matrix_destroy() {
# matrix_destroy instance
declare -n self=$1
unset self
}
# my_matrix[3][2]=( (one, two, three), ("1 1" "2 2" "3 3") )
matrix_init my_matrix \
3 2 \
one two three \
"1 1" "2 2" "3 3"
# print my_matrix[2][0]
matrix_get my_matrix 2 0
# print my_matrix[1][1]
matrix_get my_matrix 1 1
# my_matrix[1][1]="4 4 4"
matrix_set my_matrix 1 1 "4 4 4"
# print my_matrix[1][1]
matrix_get my_matrix 1 1
# remove my_matrix
matrix_destroy my_matrix