元素中带有空格的 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 < $#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 数组的主要内容,如果未能解决你的问题,请参考以下文章