【问题标题】:How to declare 2D array in bash如何在 bash 中声明二维数组
【发布时间】:2013-05-05 10:23:44
【问题描述】:

我想知道如何在 bash 中声明一个二维数组,然后初始化为 0。

在 C 中它看起来像这样:

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

我如何为元素赋值?和 C 一样:

a[2][3] = 3;

【问题讨论】:

  • 顺便说一句,多维数组实际上(深入地)是一个一维数组,它的处理方式略有不同,尤其是在访问其元素时。例如,一个 3x4 矩阵有 12 个单元。您使用步长为 3 的外循环遍历的“行”和使用步长为 1 的内循环遍历的“列”。

标签: linux bash shell


【解决方案1】:

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

【讨论】:

  • 您能否演示如何从文件表中填充上述“二维数组模拟”?例如有一个随机行数的文件,每行包含 5 个空格分隔的数字。
  • @kobame:我编辑了答案,为您提出的问题提供了解决方案。它将具有可变行数和可变列数的二维数组读取到 a0、a1 等。
  • 如何使用逗号或制表符等其他分隔符?
【解决方案2】:

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

#!/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 用于 3 维。
  • 语法接近“C”,如数组${matrix[2,3]}

【讨论】:

  • 这种方法的明显缺点是无法知道维度的长度。尽管如此,它在大多数其他情况下都很好用!谢谢!!
  • 请你解释一下f1f2是做什么的?
  • @Jodes 的f1f2 包含formatprintf,用于对齐打印。它可以是硬编码的,例如printf "%2s",但使用变量更灵活——如上面的f1行号width 计算为$num_rows 变量的长度 - 例如如果$num_rows 的行数为 9,则其长度为1,格式将为1+1,因此%2s。对于$num_rows 2500,其长度为4,因此格式为%5s - 依此类推...
  • 在修改数组元素时遇到了实施此方法的大问题。例如。对于matrix[1,1]=0; matrix[2,1]=0; matrix[1,1]=1;,您期望元素matrix[2,1] 保留0,但实际上它来自1。换句话说,尝试通过一个列索引更改元素,您修改了该列索引上的所有行,这令人沮丧
  • @mechanic 你忘了declare。所以这个:declare -A matrix; matrix[1,1]=0; matrix[2,1]=0; matrix[1,1]=1; printf ${matrix[2,1]} 打印 0 应该是这样。
【解决方案3】:

你也可以用一种不那么聪明的方式来解决这个问题

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

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

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

【讨论】:

  • 22行解决方案在哪里使用了间接?对于您的解决方案,在编写需要 i/o 的脚本并且用户想要将- 输入“数组”时,您将要做什么。此外,如果你想模拟一个数组,echo ${set//-/ } 可能比你的两个更有意义。
  • 这是我的错误,我错过了 or 。我认为 ${set//-/} 可能是一个更好的方法(虽然我相信你,但我不知道 %% 和 ## 的可移植性问题)。如果是一个非常危险的问题,如果你多次问它,你会发现你需要人工智能。对于您的选项解析器:{p
  • 我不明白这有什么关系或有助于回答这个问题。 ${set//-/ } 将消除“-”,将值合并在一起。即,回显结果为“ab”,而原始代码根据您想要的“-”的哪一侧返回“a”或“b”。
【解决方案4】:

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】:

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

    【讨论】:

      【解决方案6】:

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

      # 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
      

      【讨论】:

        【解决方案7】:

        人们可以简单地定义两个函数来写入($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
        

        【讨论】:

        • 您好,请在代码中添加一些解释,因为它有助于理解您的代码。仅代码的答案是不受欢迎的。
        【解决方案8】:

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

        也就是说,

        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
        

        这种方法可能值得变成一组函数,但由于您不能将数组传递给函数或从函数返回数组,因此您必须使用按名称传递,有时还必须使用eval。所以我倾向于将多维数组归档在“bash is simply Not Meant To Do”下。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

            Mark Reed 为二维数组(矩阵)提出了一个非常好的解决方案!它们总是可以转换为一维数组(向量)。尽管 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
            

            【讨论】:

              【解决方案11】:

              如果您在具有 bash 版本 4 的 Mac 上工作,那么下面的代码肯定可以工作。您不仅可以声明 0,而且这更像是一种动态接受值的通用方法。

              二维数组

              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
              

              【讨论】:

                【解决方案12】:

                在 bash 中可以通过声明一维数组来实现二维数组,然后可以使用(r * col_size) + c) 访问元素。下面的逻辑删除一维数组 (str_2d_arr) 并打印为二维数组。

                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
                

                下面的逻辑对于从上面声明的一维数组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
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-01-15
                  • 2010-12-21
                  • 2012-02-27
                  • 2012-04-01
                  • 2020-08-01
                  • 2018-06-29
                  • 1970-01-01
                  相关资源
                  最近更新 更多