【问题标题】:Loop through an array of strings in Bash?循环遍历 Bash 中的字符串数组?
【发布时间】:2022-01-16 20:42:53
【问题描述】:

我想编写一个循环遍历 15 个字符串(可能是数组?)的脚本,这可能吗?

类似:

for databaseName in listOfNames
then
  # Do something
end

【问题讨论】:

    标签: arrays bash shell


    【解决方案1】:

    我将这种方法用于我的 GitHub 更新,我发现它很简单。

    ## declare an array variable
    arr_variable=("kofi" "kwame" "Ama")
    
    ## now loop through the above array
    for i in "${arr_variable[@]}"
    do
       echo "$i"
    
    
    done
       
    

    您可以使用具有三个表达式(C 风格)的计数器来遍历 bash 数组值,以读取循环语法的所有值和索引:

    declare -a kofi=("kofi" "kwame" "Ama")
     
    # get the length of the array
    length=${#kofi[@]}
    
    for (( j=0; j<${length}; j++ ));
    do
      print (f "Current index %d with value %s\n" $j "${kofi[$j]}")
    done
    

    【讨论】:

    • 一个 bash 数组可以有孔;通过 index 循环遍历它的正确方法(也适用于关联数组)是:for i in "${!arr_variable[@]}"; do printf '%d: %s\n' "$i" "${arr_variable[i]}"; done
    【解决方案2】:

    声明一个类型为字符串的数组

    declare -a StringArray=("Linux Mint" "Fedora" "Red Hat Linux" "Ubuntu" "Debian" )
    

    使用for循环迭代字符串数组

    for val in ${StringArray[@]}; do
       echo $val
    done
    

    输出

    【讨论】:

      【解决方案3】:

      这些答案都不包括计数器...

      #!/bin/bash
      ## declare an array variable
      declare -a array=("one" "two" "three")
      
      # get length of an array
      arraylength=${#array[@]}
      
      # use for loop to read all values and indexes
      for (( i=0; i<${arraylength}; i++ ));
      do
        echo "index: $i, value: ${array[$i]}"
      done
      

      输出:

      index: 0, value: one
      index: 1, value: two
      index: 2, value: three
      

      【讨论】:

      • 这应该是公认的答案,是唯一在数组元素包含空格时有效的答案。
      • 这只是为了使用计数器的示例输出。改变它也很简单,并且工作方式相同。
      • 最后的回声是错误的。您不需要引用常量,您需要引用扩展,或者可以安全地引用两者,如下所示:echo "$i / ${arraylength} : ${array[$i-1]}" - 否则,如果您的 $i 包含一个 glob,它将被扩展,如果它包含一个选项卡它将被更改为空格等。
      • @bzeaman,当然——但如果你对这些事情很草率,那么它需要上下文分析(就像你刚才所做的那样)来证明某些事情是正确的,如果上下文发生变化或代码发生变化,则需要重新分析在不同的地方重用或出于任何其他原因可能出现意外的控制流。以稳健的方式编写它,无论上下文如何,它都是正确的。
      • 这不适用于稀疏数组,即如果数组缺少元素。例如,如果您有数组元素 A[1]='xx'、A[4]='yy' 和 A[9]='zz',则长度将为 3,并且循环不会处理所有元素.
      【解决方案4】:

      如何循环遍历数组,取决于是否存在换行符。用换行符分隔数组元素,数组可以称为"$array",否则应称为"${array[@]}"。下面的脚本会说清楚:

      #!/bin/bash
      
      mkdir temp
      mkdir temp/aaa
      mkdir temp/bbb
      mkdir temp/ccc
      array=$(ls temp)
      array1=(aaa bbb ccc)
      array2=$(echo -e "aaa\nbbb\nccc")
      
      echo '$array'
      echo "$array"
      echo
      for dirname in "$array"; do
          echo "$dirname"
      done
      echo
      for dirname in "${array[@]}"; do
          echo "$dirname"
      done
      echo
      echo '$array1'
      echo "$array1"
      echo
      for dirname in "$array1"; do
          echo "$dirname"
      done
      echo
      for dirname in "${array1[@]}"; do
          echo "$dirname"
      done
      echo
      echo '$array2'
      echo "$array2"
      echo
      for dirname in "$array2"; do
          echo "$dirname"
      done
      echo
      for dirname in "${array2[@]}"; do
          echo "$dirname"
      done
      rmdir temp/aaa
      rmdir temp/bbb
      rmdir temp/ccc
      rmdir temp
      

      【讨论】:

        【解决方案5】:

        是的

        for Item in Item1 Item2 Item3 Item4 ;
          do
            echo $Item
          done
        

        输出:

        Item1
        Item2
        Item3
        Item4
        

        保留空格;单引号或双引号列表条目和双引号列表扩展。

        for Item in 'Item 1' 'Item 2' 'Item 3' 'Item 4' ;
          do
            echo "$Item"
          done
        

        输出:

        Item 1
        Item 2
        Item 3
        Item 4
        

        列出多行

        for Item in Item1 \
                    Item2 \
                    Item3 \
                    Item4
          do
            echo $Item
          done
        

        输出:

        Item1
        Item2
        Item3
        Item4
        

        简单列表变量
        List=( Item1 Item2 Item3 )
        

        或

        List=(
              Item1 
              Item2 
              Item3
             )
        

        显示列表变量:

        echo ${List[*]}
        

        输出:

        Item1 Item2 Item3
        

        遍历列表:

        for Item in ${List[*]} 
          do
            echo $Item 
          done
        

        输出:

        Item1
        Item2
        Item3
        

        创建一个遍历列表的函数:

        Loop(){
          for item in ${*} ; 
            do 
              echo ${item} 
            done
        }
        Loop ${List[*]}
        

        使用declare关键字(命令)创建列表,技术上称为数组:

        declare -a List=(
                         "element 1" 
                         "element 2" 
                         "element 3"
                        )
        for entry in "${List[@]}"
           do
             echo "$entry"
           done
        

        输出:

        element 1
        element 2
        element 3
        

        创建关联数组。字典:

        declare -A continent
        
        continent[Vietnam]=Asia
        continent[France]=Europe
        continent[Argentina]=America
        
        for item in "${!continent[@]}"; 
          do
            printf "$item is in ${continent[$item]} \n"
          done
        

        输出:

         Argentina is in America
         Vietnam is in Asia
         France is in Europe
        

        CSV 变量或文件到列表中。
        将内部字段分隔符从空格更改为您想要的任何内容。
        在下面的示例中,它被更改为逗号

        List="Item 1,Item 2,Item 3"
        Backup_of_internal_field_separator=$IFS
        IFS=,
        for item in $List; 
          do
            echo $item
          done
        IFS=$Backup_of_internal_field_separator
        

        输出:

        Item 1
        Item 2
        Item 3
        

        如果需要编号:

        ` 
        

        这称为反勾号。将命令放在反引号内。

        `command` 
        

        在标准美式英语键盘上,它位于键盘上的数字 1 和或 Tab 键上方。

        List=()
        Start_count=0
        Step_count=0.1
        Stop_count=1
        for Item in `seq $Start_count $Step_count $Stop_count`
            do 
               List+=(Item_$Item)
            done
        for Item in ${List[*]}
            do 
                echo $Item
            done
        

        输出是:

        Item_0.0
        Item_0.1
        Item_0.2
        Item_0.3
        Item_0.4
        Item_0.5
        Item_0.6
        Item_0.7
        Item_0.8
        Item_0.9
        Item_1.0
        

        更加熟悉 bashes 行为:

        在文件中创建一个列表

        cat <<EOF> List_entries.txt
        Item1
        Item 2 
        'Item 3'
        "Item 4"
        Item 7 : *
        "Item 6 : * "
        "Item 6 : *"
        Item 8 : $PWD
        'Item 8 : $PWD'
        "Item 9 : $PWD"
        EOF
        

        将列表文件读入列表并显示

        List=$(cat List_entries.txt)
        echo $List
        echo '$List'
        echo "$List"
        echo ${List[*]}
        echo '${List[*]}'
        echo "${List[*]}"
        echo ${List[@]}
        echo '${List[@]}'
        echo "${List[@]}"
        

        BASH commandline reference manual: Special meaning of certain characters or words to the shell.

        【讨论】:

        • 这是错误的。需要"${List[@]}" 才能正确,带引号。 ${List[@]} 是错误的。 ${List[*]} 是错误的。试试List=( "* first item *" "* second item *" )——你会得到for item in "${List[@]}"; do echo "$item"; done的正确行为,但不是来自任何其他变体。
        • 是的,特殊字符会被解释,这可能是可取的,也可能不是。我更新了答案以包含。
        • 我仍然强烈建议展示更正确/稳健的方法first。人们通常会选择看起来可行的第一个答案。如果该答案有隐藏的警告,他们可能只会在以后暴露自己。 (不只是由于缺少引用而破坏的通配符;List=( "first item" "second item" ) 也将分解为 first、item、second、item)。
        • 您还可以考虑避免使用可能导致人们解析ls 输出的示例in contravention of best practices。
        • 你在驳斥我从未提出过的说法。我对 bash 的引用语义非常熟悉——请参阅 stackoverflow.com/tags/bash/topusers
        【解决方案6】:

        我真正需要的是这样的:

        for i in $(the_array); do something; done
        

        例如:

        for i in $(ps -aux | grep vlc  | awk '{ print $2 }'); do kill -9 $i; done
        

        (将杀死所有名称中带有 vlc 的进程)

        【讨论】:

          【解决方案7】:

          简单的方法:

          arr=("sharlock"  "bomkesh"  "feluda" )  ##declare array
          
          len=${#arr[*]}  # it returns the array length
          
          #iterate with while loop
          i=0
          while [ $i -lt $len ]
          do
              echo ${arr[$i]}
              i=$((i+1))
          done
          
          
          #iterate with for loop
          for i in $arr
          do
            echo $i
          done
          
          #iterate with splice
           echo ${arr[@]:0:3}
          

          【讨论】:

            【解决方案8】:

            很惊讶还没有人发布这个——如果你在遍历数组时需要元素的索引,你可以这样做:

            arr=(foo bar baz)
            
            for i in ${!arr[@]}
            do
                echo $i "${arr[i]}"
            done
            

            输出:

            0 foo
            1 bar
            2 baz
            

            我发现这比“传统”for 循环样式 (for (( i=0; i&lt;${#arr[@]}; i++ ))) 优雅得多。

            (${!arr[@]} 和 $i 不需要引用,因为它们只是数字;有些人会建议引用它们,但这只是个人喜好。)

            【讨论】:

            • 这确实应该是选择的答案。 1:简单易读。 2:它正确处理空白,没有 IFS 废话妨碍。 3:正确处理稀疏数组。
            【解决方案9】:

            脚本或函数的隐式数组:

            除了anubhava的正确答案:如果for循环的基本语法是:

            for var in "${arr[@]}" ;do ...$var... ;done
            

            有一个特殊案例:

            运行脚本或函数时,在命令行传递的参数会被赋值给$@数组变量,你可以通过$1、$2、$3和以此类推。

            这可以由

            填充(用于测试)
            set -- arg1 arg2 arg3 ...
            

            这个数组上的循环可以简单地写成:

            for item ;do
                echo "This is item: $item."
              done
            

            注意保留的工作in不存在,也没有数组名称!

            示例:

            set -- arg1 arg2 arg3 ...
            for item ;do
                echo "This is item: $item."
              done
            This is item: arg1.
            This is item: arg2.
            This is item: arg3.
            This is item: ....
            

            请注意,这与

            相同
            for item in "$@";do
                echo "This is item: $item."
              done
            

            然后变成一个脚本:

            #!/bin/bash
            
            for item ;do
                printf "Doing something with '%s'.\n" "$item"
              done
            

            将此保存在脚本中myscript.sh、chmod +x myscript.sh,然后

            ./myscript.sh arg1 arg2 arg3 ...
            Doing something with 'arg1'.
            Doing something with 'arg2'.
            Doing something with 'arg3'.
            Doing something with '...'.
            

            函数中的相同:

            myfunc() { for item;do cat <<<"Working about '$item'."; done ; }
            

            然后

            myfunc item1 tiem2 time3
            Working about 'item1'.
            Working about 'tiem2'.
            Working about 'time3'.
            

            【讨论】:

              【解决方案10】:

              你可以使用${arrayName[@]}的语法

              #!/bin/bash
              # declare an array called files, that contains 3 values
              files=( "/etc/passwd" "/etc/group" "/etc/hosts" )
              for i in "${files[@]}"
              do
                  echo "$i"
              done
              

              【讨论】:

                【解决方案11】:

                单行循环,

                 declare -a listOfNames=('db_a' 'db_b' 'db_c')
                 for databaseName in ${listOfNames[@]}; do echo $databaseName; done;
                

                你会得到这样的输出,

                db_a
                db_b
                db_c
                

                【讨论】:

                  【解决方案12】:

                  我循环遍历我的一系列项目以获取git pull 更新:

                  #!/bin/sh
                  projects="
                  web
                  ios
                  android
                  "
                  for project in $projects do
                      cd  $HOME/develop/$project && git pull
                  end
                  

                  【讨论】:

                  • 虽然这段代码 sn-p 可以解决问题,including an explanation 确实有助于提高您的帖子质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!
                  • 你应该在你的脚本中解释IFS变量的行为
                  【解决方案13】:

                  每个 Bash 脚本/会话的可能第一行:

                  say() { for line in "${@}" ; do printf "%s\n" "${line}" ; done ; }
                  

                  使用例如:

                  $ aa=( 7 -4 -e ) ; say "${aa[@]}"
                  7
                  -4
                  -e
                  

                  可以考虑:echo 将-e 解释为此处的选项

                  【讨论】:

                    【解决方案14】:

                    试试这个。它正在运行并经过测试。

                    for k in "${array[@]}"
                    do
                        echo $k
                    done
                    
                    # For accessing with the echo command: echo ${array[0]}, ${array[1]}
                    

                    【讨论】:

                    • 这实际上不能正常工作。试试array=( "hello world" ),或arrray=( "*" );在第一种情况下,它将分别打印hello 和world,在第二种情况下,它将打印文件列表而不是*
                    • ...在 shell 中调用“tested”之前,请务必检查极端情况,其中空格和 glob 都在其中。
                    【解决方案15】:

                    声明数组不适用于 Korn shell。将以下示例用于 Korn shell:

                    promote_sla_chk_lst="cdi xlob"
                    
                    set -A promote_arry $promote_sla_chk_lst
                    
                    for i in ${promote_arry[*]};
                        do
                                echo $i
                        done
                    

                    【讨论】:

                    • 试试编辑器中的代码高亮,让你的代码看起来不错。
                    • 很高兴知道,但这个问题是关于 bash 的。
                    • 这里有很多错误。不能有带有空格的列表条目,不能有带有全局字符的列表条目。 for i in ${foo[*]} 基本上总是错误的——for i in "${foo[@]}" 是保留原始列表边界并防止全局扩展的形式。并且回声需要是echo "$i"
                    【解决方案16】:

                    本着与 4ndrew 的回答相同的精神:

                    listOfNames="RA
                    RB
                    R C
                    RD"
                    
                    # To allow for other whitespace in the string:
                    # 1. add double quotes around the list variable, or
                    # 2. see the IFS note (under 'Side Notes')
                    
                    for databaseName in "$listOfNames"   #  <-- Note: Added "" quotes.
                    do
                      echo "$databaseName"  # (i.e. do action / processing of $databaseName here...)
                    done
                    
                    # Outputs
                    # RA
                    # RB
                    # R C
                    # RD
                    

                    B.名称中没有空格:

                    listOfNames="RA
                    RB
                    R C
                    RD"
                    
                    for databaseName in $listOfNames  # Note: No quotes
                    do
                      echo "$databaseName"  # (i.e. do action / processing of $databaseName here...)
                    done
                    
                    # Outputs
                    # RA
                    # RB
                    # R
                    # C
                    # RD
                    

                    备注

                    1. 在第二个示例中,使用listOfNames="RA RB R C RD" 具有相同的输出。

                    其他引入数据的方式包括:

                    从标准输入读取

                    # line delimited (each databaseName is stored on a line)
                    while read databaseName
                    do
                      echo "$databaseName"  # i.e. do action / processing of $databaseName here...
                    done # <<< or_another_input_method_here
                    
                    1. 可以在脚本中指定 bash IFS "field separator to line" [1] 分隔符以允许其他空格(即IFS='\n',或者对于MacOS IFS='\r')
                    2. 我也喜欢接受的答案 :) -- 我已将这些 sn-ps 作为其他有助于回答问题的方法。
                    3. 脚本文件顶部包含#!/bin/bash表示执行环境。
                    4. 我花了几个月的时间才弄清楚如何简单地编写代码 :)

                    其他来源 (while read loop)

                    【讨论】:

                    • 这给人的印象是 eol 被用作字符串分隔符,因此字符串中允许使用空格。但是,带有空格的字符串被进一步分成子字符串,这是非常非常糟糕的。我认为stackoverflow.com/a/23561892/1083704这个答案更好。
                    • @Val,我添加了引用IFS 的代码注释。 (对于每个人来说,IFS 允许指定一个特定的分隔符,它允许其他空格包含在字符串中而不被分成子字符串)。
                    • 这对我不起作用。 $databaseName 只包含整个列表,因此只进行一次迭代。
                    • @AlikElzin-kilaka 我下面的回答解决了这个问题,以便为字符串的每一行运行循环。
                    【解决方案17】:

                    当然可以。

                    for databaseName in a b c d e f; do
                      # do something like: echo $databaseName
                    done 
                    

                    详情请见Bash Loops for, while and until。

                    【讨论】:

                    • 这种方法有什么问题?在简单的情况下,它似乎工作,然后,比@anubhava 的答案更直观。
                    • 这特别适用于命令替换,例如for year in $(seq 2000 2013)。
                    • 问题是他问的是遍历一个数组。
                    • 如果您必须在多个位置迭代同一个数组,“declare”方法效果最好。这种方法更简洁,但灵活性较差。
                    • 为什么这不是第一名?它更干净,您只需设置一个字符串即可轻松重用数组,即DATABASES="a b c d e f"。
                    【解决方案18】:

                    你可以这样使用它:

                    ## declare an array variable
                    declare -a arr=("element1" "element2" "element3")
                    
                    ## now loop through the above array
                    for i in "${arr[@]}"
                    do
                       echo "$i"
                       # or do whatever with individual element of the array
                    done
                    
                    # You can access them using echo "${arr[0]}", "${arr[1]}" also
                    

                    也适用于多行数组声明

                    declare -a arr=("element1" 
                                    "element2" "element3"
                                    "element4"
                                    )
                    

                    【讨论】:

                    • 请注意,"${arr[@]}" 周围的双引号非常重要。 如果没有它们,for 循环将通过字符串中的任何空格分隔的子字符串来分解数组,而不是通过数组中的整个字符串元素。即:如果您有declare -a arr=("element 1" "element 2" "element 3"),那么for i in ${arr[@]} 将错误地迭代6 次,因为每个字符串变成由字符串中的空格分隔的2 个子字符串,而for i in "${arr[@]}" 将根据需要正确地迭代3 次,将每个字符串保持为一个单元,尽管里面有空间。
                    • 数组是否也适用于旧 bash 版本?
                    • 是的,它也适用于较旧的 bash 版本。
                    • 我认为你不需要在数组变量的名称前面写“declare -a”。它对我有用。
                    • 感谢您的评论。我花了 2 个小时思考为什么它在命令行中工作,但当我为同一组命令运行脚本时却没有。
                    【解决方案19】:

                    这类似于 user2533809 的回答,但每个文件将作为单独的命令执行。

                    #!/bin/bash
                    names="RA
                    RB
                    R C
                    RD"
                    
                    while read -r line; do
                        echo line: "$line"
                    done <<< "$names"
                    

                    【讨论】:

                      【解决方案20】:
                      listOfNames="db_one db_two db_three"
                      for databaseName in $listOfNames
                      do
                        echo $databaseName
                      done
                      

                      或者只是

                      for databaseName in db_one db_two db_three
                      do
                        echo $databaseName
                      done
                      

                      【讨论】:

                        【解决方案21】:

                        这也很容易阅读:

                        FilePath=(
                            "/tmp/path1/"    #FilePath[0]
                            "/tmp/path2/"    #FilePath[1]
                        )
                        
                        #Loop
                        for Path in "${FilePath[@]}"
                        do
                            echo "$Path"
                        done
                        

                        【讨论】:

                        • 只有当我正确设置 IFS 变量 before FilePath 数组定义时,这个才对我很清楚并且对我有用(包括 FilePath 数组元素中的空格和变量替换): IFS=$'\n' 在这种情况下,这也可能适用于其他解决方案。
                        【解决方案22】:

                        如果您使用的是 Korn shell,则有“set -A databaseName”,否则有“declare -a databaseName”

                        要编写适用于所有 shell 的脚本,

                         set -A databaseName=("db1" "db2" ....) ||
                                declare -a databaseName=("db1" "db2" ....)
                        # now loop 
                        for dbname in "${arr[@]}"
                        do
                           echo "$dbname"  # or whatever
                        
                        done
                        

                        它应该适用于所有外壳。

                        【讨论】:

                        • 不,它没有:$ bash --version GNU bash,版本 4.3.33(0)-release (amd64-portbld-freebsd10.0) $ set -A databaseName=("db1" "db2" ....) || declare -a databaseName=("db1" "db2" ....) bash: 意外标记附近的语法错误` ('
                        猜你喜欢
                        • 2023-03-23
                        • 2017-10-06
                        • 1970-01-01
                        • 2019-09-07
                        • 2015-06-18
                        相关资源
                        最近更新 更多