如何在bash中声明2D数组


75

我想知道如何在bash中声明2D数组,然后初始化为0。

在C中看起来像这样:

int a[4][5] = {0};

以及如何为元素分配值?如在C中:

a[2][3] = 3;

3
相关内容:BASH中的多维数组
Izzy

4
多维数组实际上是(向下)一维数组,其处理方式略有不同,尤其是在访问其元素时。例如3x4矩阵有12个单元。您以3步的外循环遍历“行”,以1步的内循环遍历“行”
rbaleksandar

Answers:


74

您可以使用例如哈希来模拟它们,但需要注意前导零和许多其他事情。下一个演示有效,但远非最佳解决方案。

#!/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,23维。
  • 语法类似于数组的“ C” ${matrix[2,3]}

2
此方法的明显缺点是无法知道尺寸的长度。尽管如此,它在大多数其他情况下仍然有效!谢谢!!
Bhavin Doshi 2013年

请你能解释一下什么f1f2做什么?
2016年

1
@Jodesf1f2包含format用于printf对齐的漂亮的。例如,它可以是硬编码的,printf "%2s"但使用变量则更为灵活-如上述f1。在width所述的行数被计算为的长度$num_rows可变-例如,如果行数$num_rows是9,它的长度是1所述格式将是1+1如此%2s。对于$num_rows2500,它的长度是4这样,格式将是%5s-依此类推...
jm666

24

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

您能否演示如何从文件表中填充上述“ 2d数组模拟”?例如,具有一个随机行数的文件,并且每行中包含5个空格分隔的数字。
kobame

@kobame:我编辑了答案,为您的要求提供了解决方案。它将读取一个二维数组,该数组具有可变的行数和可变的列数,分别为a0,a1等。
阿索斯爵士

您将如何使用其他定界符,例如逗号或制表符?
MountainX'7

23

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,01,0并且这样将作为算术表达式和评价0(数值以逗号运算符的右侧)。


5

您也可以以一种不太聪明的方式来解决这个问题

q=()
q+=( 1-2 )
q+=( a-b )

for set in ${q[@]};
do
echo ${set%%-*}
echo ${set##*-}
done

当然,22行的解决方案或间接方案可能是更好的解决办法,为什么不在每个地方都撒些东西。


22线解决方案在哪里使用间接?对于您的解决方案,编写需要i / o且用户希望将a输入-到“数组”中的脚本时要做什么。同样,如果您想模拟一个数组,可能比echo ${set//-/ }两个数组更有意义。
恢复莫妮卡,请2014年

那是我错过的一个错误。我认为$ {set //-//}可能是更好的选择(尽管我相信您不知道%%和##的可移植性问题)。如果这是一个非常危险的问题,怎么办,如果您多次询问,就会发现您需要AI来进行选项解析器:{p
James

1
我不知道这有什么意义或无法回答这个问题。$ {set //-//}将消除“-”,将值合并在一起。即,回声产生“ ab”,而原始代码根据您想要的“-”的哪一侧返回“ a”或“ b”。
MrPotatoHead

5

另一种方法是您可以将每一行表示为字符串,即将2D数组映射到1D数组。然后,您需要做的就是在进行编辑时解包并重新打包行的字符串:

# Init a 4x5 matrix
a=("00 01 02 03 04" "10 11 12 13 14" "20 21 22 23 24" "30 31 32 33 34")

aset() {
  row=$1
  col=$2
  value=$3
  IFS=' ' read -r -a tmp <<< "${a[$row]}"
  tmp[$col]=$value
  a[$row]="${tmp[@]}"
}

# Set a[2][3] = 9999
aset 2 3 9999

# Show result
for r in "${a[@]}"; do
  echo $r
done

输出:

00 01 02 03 04
10 11 12 13 14
20 21 22 9999 24
30 31 32 33 34

4

一种在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 ###
################

2

如果矩阵的每一行都具有相同的大小,则可以简单地使用线性数组和乘法。

那是,

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根本不意味着要做”。


1

一个人可以简单地定义两个函数来编写($ 4是赋值)并利用eval和间接引用读取具有任意名称($ 1)和索引($ 2和$ 3)的矩阵。

#!/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

2
嗨,请在代码中添加一些说明,因为它有助于理解您的代码。只回答代码的人不满意。
巴尔加夫(Bhargav Rao)

1

可以通过声明1D数组在bash中实现2D数组,然后可以使用来访问元素(r * col_size) + c)。在逻辑delcares 1D数组(str_2d_arr)下方,并打印为2D数组。

col_size=3
str_2d_arr=()
str_2d_arr+=('abc' '200' 'xyz')
str_2d_arr+=('def' '300' 'ccc')
str_2d_arr+=('aaa' '400' 'ddd')

echo "Print 2D array"
col_count=0
for elem in ${str_2d_arr[@]}; do
    if [ ${col_count} -eq ${col_size} ]; then
        echo ""
        col_count=0
    fi
    echo -e "$elem \c"
    ((col_count++))
done
echo ""

输出为

Print 2D array
abc 200 xyz 
def 300 ccc
aaa 400 ddd

下面的逻辑对于从上面声明的1D数组中获取每一行非常有用str_2d_arr

# Get nth row and update to 2nd arg
get_row_n()
{
    row=$1
    local -n a=$2
    start_idx=$((row * col_size))
    for ((i = 0; i < ${col_size}; i++)); do
        idx=$((start_idx + i))
        a+=(${str_2d_arr[${idx}]})
    done
}

arr=()
get_row_n 0 arr
echo "Row 0"
for e in ${arr[@]}; do
    echo -e "$e \c"
done
echo ""

输出为

Row 0
abc 200 xyz

0

为了模拟二维数组,我首先加载前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

0

如果您在bash版本4的Mac上运行,以下代码肯定可以正常工作:不仅可以声明0,而且它更是一种通用的动态接受值的方法。

2D阵列

declare -A arr
echo "Enter the row"
read r
echo "Enter the column"
read c
i=0
j=0
echo "Enter the elements"
while [ $i -lt $r ]
do
  j=0
  while [ $j -lt $c ]
  do
    echo $i $j
    read m
    arr[${i},${j}]=$m
    j=`expr $j + 1`
  done
  i=`expr $i + 1`
done

i=0
j=0
while [ $i -lt $r ]
do
  j=0
  while [ $j -lt $c ]
  do
    echo -n ${arr[${i},${j}]} " "
    j=`expr $j + 1`
  done
  echo ""
  i=`expr $i + 1`
done

0

马克·里德(Mark Reed)提出了一个很好的2D阵列(矩阵)解决方案!它们始终可以转换为一维数组(向量)。尽管Bash没有对2D数组的本机支持,但围绕所提到的原理创建简单的ADT并不难。

这是一个没有参数检查等的准系统示例,只是为了使解决方案保持清晰:数组的大小设置为实例中的两个第一元素(实现矩阵ADT的Bash模块的文档, https://github.com /vorakl/bash-libs/blob/master/src.docs/content/pages/matrix.rst

#!/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} "$@")                                               
}                                                                               

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.