Bash数组,元素中有空格


150

我正在尝试从相机的文件名的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)

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

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

当我尝试访问数组元素时,它将继续将空间视为elementdelimiter。

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


您是否尝试过以老式方式添加文件?喜欢FILES[0] = ...吗?(编辑:我只是做过;不起作用。有趣的是)。
丹·费戈


使用Cygwin在这里为我分解了所有答案。如果文件名,句点中有空格,这确实很奇怪。我通过在文本文件中创建要使用的所有元素的列表中创建“数组”,并遍历文件中的行来解决此问题:格式化正在使用括号括住命令中的预期反引号:IFS =“”; array =(find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); 用于$ {array [@]}中的元素;回显$ element; 完成
Alex Hall

Answers:


121

我认为问题可能与您访问元素的方式有关。如果我做简单的话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"

6
请注意,在使用数组元素(例如echo "${FILES[$i]}")时,应使用双引号。无关紧要echo,但对于将其用作文件名的任何内容都将适用。
戈登·戴维森

26
当您可以使用遍历元素时,不必遍历索引for f in "${FILES[@]}"
马克·埃德加

10
@MarkEdgar 在数组成员中有空格时,$ {FILES [@]}中的f出现问题。似乎整个数组再次被重新解释,空格将您现有的成员分割成两个或多个元素。似乎“”非常重要
Michael Shaw

1
语句中的(#)符号有什么作用for ((i = 0; i < ${#FILES[@]}; i++))
米歇尔·维契安18/12/11

4
我在六年前回答了这个问题,但我相信这是要获得数组FILES中元素数量的计数
丹·费戈

91

访问数组项的方式一定存在问题。这是完成的过程:

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

bash手册页

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

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

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

3
尽管应该重申该数组中最干净,最优雅的解决方案,但应引用数组中定义的每个元素。
特立独行

尽管Dan Fego的答案很有效,但这是处理元素中空间的一种惯用方式。
张丹

3
来自其他编程语言,摘录中的术语确实很难理解。再加上语法令人困惑。如果您能再讲一点,我将不胜感激?特别是expands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22

1
是的,同意双引号正在解决它,这比其他解决方案要好。进一步解释-大多数其他人都缺少双引号。您得到了正确的:for elem in "${files[@]}",尽管它们具有for elem in ${files[@]}-以便使空格混淆扩展,并尝试在单个单词上运行。
arntg

在我使用“ GNU bash,版本3.2.57(1)-发行版(x86_64-apple-darwin18)”的macOS 10.14.4中,这对我不起作用。也许是旧版bash中的错误?
Mark Ribau

43

您需要使用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 =“。” 希望对您有帮助:)


3
我必须将IFS =“”移到数组分配之前,但这是正确的答案。

我正在使用几个数组来解析信息,而IFS =“”的效果仅在其中一个中起作用。一旦我使用IFS =“”,所有其他数组将相应地停止解析。有什么提示吗?
Paulo Pedroso,2015年

Paulo,请在此处查看另一个可能更适合您的答案:stackoverflow.com/a/9089186/1041319。尚未尝试过IFS =“”,并且看起来确实可以很好地解决它-但是您的示例说明了为什么在某些情况下可能会遇到问题。可以在一行上设置IFS =“”,但它可能比其他解决方案更令人困惑。
arntg

在bash上它也对我有用。谢谢@ Khushneet,我正在搜索一个半小时……
csonuryilmaz 17-10-21

太好了,只有这个页面上有效的答案。但是我还必须IFS="" 在阵列构造之前移动。
pkamb

13

我同意其他人的看法,这很可能是您访问问题所在的方式。引用数组分配中的文件名是正确的:

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[@]}"会将数组分成每个数组元素一个单词。除此之外,它不会进行任何拆分。

using "${FILES[*]}"也有特殊的含义,但是它将数组元素与$ IFS的第一个字符连接在一起,产生一个单词,这可能不是您想要的。

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

使用C样式的for循环也很好,如果您不清楚,可以避免担心单词拆分:

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

3

逃逸工程。

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

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


3

如果您有这样的数组:#!/ 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

2

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

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

当然,必须在特定要求的地方采用该表达方式(例如,*.jpg用于全部或2001-09-11*.jpg仅用于特定日期的图片)。


0

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

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

0

如果您不习惯使用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

0

我曾经在完成后重置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

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.