【问题标题】:Bash array with spaces in elements元素中带有空格的 Bash 数组
【发布时间】:2012-02-23 10:48:39
【问题描述】:

我正在尝试在我的相机文件名的 bash 中构造一个数组:

FILES=(2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg)

如您所见,每个文件名中间都有一个空格。

我尝试将每个名称用引号括起来,并用反斜杠转义空格,但都不起作用。

当我尝试访问数组元素时,它继续将空格视为元素分隔符。

如何正确捕获名称中带有空格的文件名?

【问题讨论】:

  • 您是否尝试过以老式方式添加文件?喜欢FILES[0] = ...? (编辑:我刚刚做了;不起作用。有趣)。
  • 这里的所有答案都为我使用 Cygwin 分解。如果文件名中有空格,句号,它会做一些奇怪的事情。我通过在我想要使用的所有元素的文本文件列表中创建一个“数组”来解决它,并遍历文件中的行:格式化与括号中的命令周围的预期反引号混淆: IFS="";数组=(find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); ${array[@]} 中的元素;做回声$元素;完成

标签: arrays bash unix scripting


【解决方案1】:

我认为问题可能部分与您访问元素的方式有关。如果我做一个简单的for elem in $FILES,我会遇到和你一样的问题。但是,如果我通过它的索引访问数组,就像这样,如果我以数字方式或使用转义添加元素,它就可以工作:

for ((i = 0; i < ${#FILES[@]}; i++))
do
    echo "${FILES[$i]}"
done

$FILES 的任何这些声明都应该有效:

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

FILES=("2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg")

FILES[0]="2011-09-04 21.43.02.jpg"
FILES[1]="2011-09-05 10.23.14.jpg"
FILES[2]="2011-09-09 12.31.16.jpg"
FILES[3]="2011-09-11 08.43.12.jpg"

【讨论】:

  • 请注意,在使用数组元素时应使用双引号(例如echo "${FILES[$i]}")。 echo 无关紧要,但任何使用它作为文件名的东西都可以。
  • 当您可以使用for f in "${FILES[@]}" 遍历元素时,不需要遍历索引。
  • @MarkEdgar 当数组成员有空格时,我遇到了 for f in ${FILES[@]} 的问题。似乎整个数组再次被重新解释,空格将您现有的成员分成两个或多个元素。看来“”很重要
  • for ((i = 0; i &lt; ${#FILES[@]}; i++)) 语句中的尖锐 (#) 符号有什么作用?
  • 我在六年前回答过这个问题,但我相信这是为了获取数组 FILES 中元素数量的 count
【解决方案2】:

您访问数组项的方式一定有问题。以下是它的完成方式:

for elem in "${files[@]}"
...

来自bash manpage

可以使用 ${name[subscript]} 引用数组的任何元素。 ... 如果下标是 @ 或 *,则单词将扩展到 name 的所有成员。只有当单词出现在双引号中时,这些下标才会有所不同。 如果单词是双引号, ${name[*]} 将扩展为单个单词,其中每个数组成员的值由 IFS 特殊变量的第一个字符分隔,并且 $ {name[@]} 将 name 的每个元素扩展为一个单独的单词

当然,访问单个成员时也应该使用双引号

cp "${files[0]}" /tmp

【讨论】:

  • 这一系列中最干净、最优雅的解决方案,但应该重申应该引用数组中定义的每个元素。
  • 虽然 Dan Fego 的回答很有效,但这是处理元素中空格的更惯用的方式。
  • 来自其他编程语言,摘录中的术语真的很难理解。加上语法令人费解。如果您能再深入一点,我将不胜感激?特别是expands to a single word with the value of each array member separated by the first character of the IFS special variable
  • 是的,同意双引号正在解决它,这比其他解决方案更好。进一步解释 - 大多数其他人只是缺少双引号。你得到了正确的:for elem in "${files[@]}",而他们有 for elem in ${files[@]} - 所以空格混淆了扩展和尝试在单个单词上运行。
  • 这在 macOS 10.14.4 中对我不起作用,它使用“GNU bash,版本 3.2.57(1)-release (x86_64-apple-darwin18)”。可能是旧版本 bash 中的错误?
【解决方案3】:

您需要使用 IFS 来停止空格作为元素分隔符。

FILES=("2011-09-04 21.43.02.jpg"
       "2011-09-05 10.23.14.jpg"
       "2011-09-09 12.31.16.jpg"
       "2011-09-11 08.43.12.jpg")
IFS=""
for jpg in ${FILES[*]}
do
    echo "${jpg}"
done

如果你想根据 .然后只做 IFS="." 希望对你有帮助:)

【讨论】:

  • 我不得不将 IFS="" 移到数组分配之前,但这是正确的答案。
  • 我正在使用几个数组来解析信息,我将在其中一个中获得 IFS="" 的效果。一旦我使用 IFS="" 所有其他数组都会相应地停止解析。对此有何提示?
  • Paulo,请在此处查看另一个可能更适合您的情况的答案:stackoverflow.com/a/9089186/1041319。没有尝试过 IFS="",似乎它确实可以优雅地解决它 - 但您的示例说明了为什么在某些情况下可能会遇到问题。可以在一行中设置 IFS="",但它可能仍然比其他解决方案更令人困惑。
  • 它在 bash 上也对我有用。谢谢@Khushneet 我搜索了半个小时......
  • 很好,只有在这个页面上有效的答案。但我还必须在数组构造之前移动IFS=""
【解决方案4】:

我同意其他人的观点,即您访问元素的方式可能是问题所在。在数组赋值中引用文件名是正确的:

FILES=(
  "2011-09-04 21.43.02.jpg"
  "2011-09-05 10.23.14.jpg"
  "2011-09-09 12.31.16.jpg"
  "2011-09-11 08.43.12.jpg"
)

for f in "${FILES[@]}"
do
  echo "$f"
done

"${FILES[@]}" 形式的任何数组周围使用双引号将数组拆分为每个数组元素一个字。除此之外,它不会进行任何分词。

使用"${FILES[*]}"也有特殊的含义,但是它加入 $IFS的第一个字符的数组元素,产生一个字,大概不是你想要什么。

使用纯${array[@]}${array[*]} 会使扩展的结果进一步分词,因此您最终会以空格(以及$IFS 中的任何其他内容)拆分单词,而不是每个单词一个单词数组元素。

使用 C 风格的 for 循环也可以,如果您不清楚的话,可以避免担心分词:

for (( i = 0; i < ${#FILES[@]}; i++ ))
do
  echo "${FILES[$i]}"
done

【讨论】:

    【解决方案5】:

    如果你的数组是这样的: #!/bin/bash

    Unix[0]='Debian'
    Unix[1]="Red Hat"
    Unix[2]='Ubuntu'
    Unix[3]='Suse'
    
    for i in $(echo ${Unix[@]});
        do echo $i;
    done
    

    你会得到:

    Debian
    Red
    Hat
    Ubuntu
    Suse
    

    我不知道为什么,但循环分解了空格并将它们作为一个单独的项目,即使你用引号括起来。

    要解决这个问题,您可以调用索引,而不是调用数组中的元素,它会获取包含在引号中的完整字符串。 必须用引号括起来!

    #!/bin/bash
    
    Unix[0]='Debian'
    Unix[1]='Red Hat'
    Unix[2]='Ubuntu'
    Unix[3]='Suse'
    
    for i in $(echo ${!Unix[@]});
        do echo ${Unix[$i]};
    done
    

    然后你会得到:

    Debian
    Red Hat
    Ubuntu
    Suse
    

    【讨论】:

      【解决方案6】:

      不完全是原始问题的引用/转义问题的答案,但可能实际上对操作更有用:

      unset FILES
      for f in 2011-*.jpg; do FILES+=("$f"); done
      echo "${FILES[@]}"
      

      当然,必须根据特定要求采用表达式(例如,*.jpg 表示全部或2001-09-11*.jpg 仅表示某一天的图片)。

      【讨论】:

        【解决方案7】:

        转义作品。

        #!/bin/bash
        
        FILES=(2011-09-04\ 21.43.02.jpg
        2011-09-05\ 10.23.14.jpg
        2011-09-09\ 12.31.16.jpg
        2011-09-11\ 08.43.12.jpg)
        
        echo ${FILES[0]}
        echo ${FILES[1]}
        echo ${FILES[2]}
        echo ${FILES[3]}
        

        输出:

        $ ./test.sh
        2011-09-04 21.43.02.jpg
        2011-09-05 10.23.14.jpg
        2011-09-09 12.31.16.jpg
        2011-09-11 08.43.12.jpg
        

        引用字符串也会产生相同的输出。

        【讨论】:

          【解决方案8】:
          #! /bin/bash
          
          renditions=(
          "640x360    80k     60k"
          "1280x720   320k    128k"
          "1280x720   320k    128k"
          )
          
          for z in "${renditions[@]}"; do
              echo "$z"
              
          done
          

          输出

          640x360 80k 60k

          1280x720 320k 128k

          1280x720 320k 128k

          `

          【讨论】:

          • 这个答案与已经给出的答案不同/更好吗?
          • 是的,正如你所看到的输出,renditions 数组中的每个元素都是一个带空格的字符串,我们在 ${renditions[@]} 周围不带引号的情况下循环它,然后空格将被视为元素分隔符,所以在这里我用双引号将 ${renditions[@]} 括起来,这给了我上面的输出。
          【解决方案9】:

          这已经回答了above,但这个答案有点简洁,手册页摘录有点神秘。我想提供一个完整的例子来展示它在实践中是如何工作的。

          如果不加引号,则数组只会扩展为由空格分隔的字符串,因此

          for file in ${FILES[@]}; do
          

          扩展到

          for file in 2011-09-04 21.43.02.jpg 2011-09-05 10.23.14.jpg 2011-09-09 12.31.16.jpg 2011-09-11 08.43.12.jpg ; do
          

          但是如果你引用扩展,bash 会在每个术语周围添加双引号,这样:

          for file in "${FILES[@]}"; do
          

          扩展到

          for file in "2011-09-04 21.43.02.jpg" "2011-09-05 10.23.14.jpg" "2011-09-09 12.31.16.jpg" "2011-09-11 08.43.12.jpg" ; do
          

          简单的经验法则是始终使用[@] 而不是[*],如果要保留空格,请引用数组扩展。

          为了进一步详细说明,另一个答案中的手册页解释说,如果不引用,$*$@ 的行为方式相同,但引用时它们是不同的。所以,给定

          array=(a b c)
          

          然后$*$@ 都展开为

          a b c
          

          "$*" 扩展为

          "a b c"
          

          "$@" 扩展为

          "a" "b" "c"
          

          【讨论】:

            【解决方案10】:

            对于那些喜欢在单行模式下设置数组,而不是使用 for 循环的人

            暂时将IFS 更改为新行可以避免您逃跑。

            OLD_IFS="$IFS"
            IFS=$'\n'
            
            array=( $(ls *.jpg) )  #save the hassle to construct filename
            
            IFS="$OLD_IFS"
            

            【讨论】:

              【解决方案11】:

              另一种解决方案是使用“while”循环而不是“for”循环:

              index=0
              while [ ${index} -lt ${#Array[@]} ]
                do
                   echo ${Array[${index}]}
                   index=$(( $index + 1 ))
                done
              

              【讨论】:

                【解决方案12】:

                如果您不坚持使用bash,则对文件名中的空格进行不同处理是fish shell 的好处之一。考虑一个包含两个文件的目录:“a b.txt”和“b c.txt”。以下是使用 bash 处理从另一个命令生成的文件列表的合理猜测,但由于您遇到的文件名中的空格而失败:

                # bash
                $ for f in $(ls *.txt); { echo $f; }
                a
                b.txt
                b
                c.txt
                
                

                使用fish,语法几乎相同,但结果符合您的预期:

                # fish
                for f in (ls *.txt); echo $f; end
                a b.txt
                b c.txt
                

                它的工作方式不同,因为 fish 在换行符上分割命令的输出,而不是空格。

                如果您确实想在空格而不是换行符上分割,fish 有一个非常易读的语法:

                for f in (ls *.txt | string split " "); echo $f; end
                

                【讨论】:

                  【解决方案13】:

                  我曾经重置IFS 值并在完成后回滚。

                  # backup IFS value
                  O_IFS=$IFS
                  
                  # reset IFS value
                  IFS=""
                  
                  FILES=(
                  "2011-09-04 21.43.02.jpg"
                  "2011-09-05 10.23.14.jpg"
                  "2011-09-09 12.31.16.jpg"
                  "2011-09-11 08.43.12.jpg"
                  )
                  
                  for file in ${FILES[@]}; do
                      echo ${file}
                  done
                  
                  # rollback IFS value
                  IFS=${O_IFS}
                  

                  循环的可能输出:

                  2011-09-04 21.43.02.jpg

                  2011-09-05 10.23.14.jpg

                  2011-09-09 12.31.16.jpg

                  2011-09-11 08.43.12.jpg

                  【讨论】:

                    猜你喜欢
                    • 2021-01-24
                    • 2013-07-18
                    • 2020-05-16
                    • 2015-02-19
                    • 2013-08-06
                    • 1970-01-01
                    • 2017-06-13
                    • 2016-02-28
                    • 1970-01-01
                    相关资源
                    最近更新 更多