元素中带有空格的 Bash 数组

Posted

技术标签:

【中文标题】元素中带有空格的 Bash 数组【英文标题】:Bash array with spaces in elements 【发布时间】: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] = ...? (编辑:我刚刚做了;不起作用。有趣)。 POSIX:***.com/questions/2936922/… 这里的所有答案都为我使用 Cygwin 分解。如果文件名中有空格,句号,它会做一些奇怪的事情。我通过在我想要使用的所有元素的文本文件列表中创建一个“数组”来解决它,并遍历文件中的行:格式化与括号中的命令周围的预期反引号混淆: IFS="";数组=(find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); $array[@] 中的元素;做回声$元素;完成 【参考方案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,请在此处查看另一个可能更适合您的情况的答案:***.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

【讨论】:

以上是关于元素中带有空格的 Bash 数组的主要内容,如果未能解决你的问题,请参考以下文章

bash:以空格作为参数传递路径?

如果 v-for 中带空格的数组的对象名

将空格分隔的字符串读入 Bash 中的数组

反应,渲染名称中带有空格的图像的问题

网址中带有空格的 mod_rewrite

Git添加一个名称中带有空格的文件夹