元素中带空格的 Bash 数组

我试着用我的相机中的文件名构造一个 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)

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

我尝试过用引号包装每个名称,并用反斜杠转义空格,但这两种方法都不管用。

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

如何正确地捕获名称中包含空格的文件名?

160866 次浏览

逃跑成功了。

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

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

我认为问题可能部分在于你如何访问元素。如果我做一个简单的 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"

访问数组项的方式肯定有问题:

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

来自 Bash 手册:

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

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

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

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

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

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

这并不完全是对原始问题的引用/转义问题的回答,但可能是一些实际上对操作更有用的东西:

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

当然,这种表达方式必须符合特定的要求(例如,*.jpg表示所有人,或者 2001-09-11*.jpg表示某一天的照片)。

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

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

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

我不知道为什么,但循环打破了空格,并把它们作为一个单独的项目,即使你用引号围绕它。

为了解决这个问题,不调用数组中的元素,而是调用 index,它接受用引号包装的完整字符串。 它必须用引号包装!

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

如果您没有坚持使用 bash,那么对文件名中的空格的不同处理是 鱼壳的优点之一。考虑一个包含两个文件的目录: “ 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

我过去常常重置 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-0421.43.02

2011-09-0510.23.14

2011-09-0912.31.16

2011-09-1108.43.12

#! /bin/bash


renditions=(
"640x360    80k     60k"
"1280x720   320k    128k"
"1280x720   320k    128k"
)


for z in "${renditions[@]}"; do
echo "$z"
    

done

输出

80k 60k

1280x720320k 128k

1280x720320k 128k

`

这个问题的答案已经是 以上,但是这个答案有点简洁,而且手册页的摘录有点神秘。我想提供一个完整的例子来演示如何在实践中工作。

如果没有引号,数组就会扩展为由空格分隔的字符串,从而

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"

对于那些喜欢设置数组在一线模式,而不是使用 For 循环

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

OLD_IFS="$IFS"
IFS=$'\n'


array=( $(ls *.jpg) )  #save the hassle to construct filename


IFS="$OLD_IFS"