元素中包含空格的 Bash 数组

发布于 2024-12-31 22:12:39 字数 296 浏览 0 评论 0原文

我正在尝试在 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)

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

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

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

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

I'm trying to construct an array in bash of the filenames from my camera:

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)

As you can see, there is a space in the middle of each filename.

I've tried wrapping each name in quotes, and escaping the space with a backslash, neither of which works.

When I try to access the array elements, it continues to treat the space as the element delimiter.

How can I properly capture the filenames with a space inside the name?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

烦人精 2025-01-07 22:12:39

我认为问题可能部分在于您访问元素的方式。如果我对 $FILES 中的 elem 执行一个简单的 for elem ,我会遇到与您相同的问题。但是,如果我通过数组的索引访问数组,就像这样,如果我以数字方式或使用转义符添加元素,它就会起作用:

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"

I think the issue might be partly with how you're accessing the elements. If I do a simple for elem in $FILES, I experience the same issue as you. However, if I access the array through its indices, like so, it works if I add the elements either numerically or with escapes:

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

Any of these declarations of $FILES should work:

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)

or

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")

or

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"
恬淡成诗 2025-01-07 22:12:39

您访问数组项的方式一定有问题。其实现方式如下:

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

来自 bash 手册页

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

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

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

There must be something wrong with the way you access the array's items. Here's how it's done:

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

From the bash manpage:

Any element of an array may be referenced using ${name[subscript]}. ... If subscript is @ or *, the word expands to all members of name. These subscripts differ only when the word appears within double quotes. If the word is double-quoted, ${name[*]} expands to a single word with the value of each array member separated by the first character of the IFS special variable, and ${name[@]} expands each element of name to a separate word.

Of course, you should also use double quotes when accessing a single member

cp "${files[0]}" /tmp
瞄了个咪的 2025-01-07 22:12:39

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

You need to use IFS to stop space as element delimiter.

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

If you want to separate on basis of . then just do IFS="."
Hope it helps you:)

从﹋此江山别 2025-01-07 22:12:39

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

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

I agree with others that it's likely how you're accessing the elements that is the problem. Quoting the file names in the array assignment is correct:

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

Using double quotes around any array of the form "${FILES[@]}" splits the array into one word per array element. It doesn't do any word-splitting beyond that.

Using "${FILES[*]}" also has a special meaning, but it joins the array elements with the first character of $IFS, resulting in one word, which is probably not what you want.

Using a bare ${array[@]} or ${array[*]} subjects the result of that expansion to further word-splitting, so you'll end up with words split on spaces (and anything else in $IFS) instead of one word per array element.

Using a C-style for loop is also fine and avoids worrying about word-splitting if you're not clear on it:

for (( i = 0; i < ${#FILES[@]}; i++ ))
do
  echo "${FILES[$i]}"
done
情未る 2025-01-07 22:12:39

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

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

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)

Then $*$@ 都扩展为

a b c

"$*" 扩展为

"a b c"

"$@" 扩展为

"a" "b" "c"

This was already answered above, but that answer was a bit terse and the man page excerpt is a bit cryptic. I wanted to provide a fully worked example to demonstrate how this works in practice.

If not quoted, an array just expands to strings separated by spaces, so that

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

expands to

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

But if you quote the expansion, bash adds double quotes around each term, so that:

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

expands to

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

The simple rule of thumb is to always use [@] instead of [*] and quote array expansions if you want spaces preserved.

To elaborate on this a little further, the man page in the other answer is explaining that if unquoted, $* an $@ behave the same way, but they are different when quoted. So, given

array=(a b c)

Then $* and $@ both expand to

a b c

and "$*" expands to

"a b c"

and "$@" expands to

"a" "b" "c"
伊面 2025-01-07 22:12:39

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

If you had your array like this:
#!/bin/bash

Unix[0]='Debian'
Unix[1]="Red Hat"
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${Unix[@]});
    do echo $i;
done

You would get:

Debian
Red
Hat
Ubuntu
Suse

I don't know why but the loop breaks down the spaces and puts them as an individual item, even you surround it with quotes.

To get around this, instead of calling the elements in the array, you call the indexes, which takes the full string thats wrapped in quotes.
It must be wrapped in quotes!

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

Then you'll get:

Debian
Red Hat
Ubuntu
Suse
请持续率性 2025-01-07 22:12:39

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

暂时将 IFS 更改为新行可以避免您转义。

OLD_IFS="$IFS"
IFS=
\n'

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

IFS="$OLD_IFS"

For those who prefer set array in oneline mode, instead of using for loop

Changing IFS temporarily to new line could save you from escaping.

OLD_IFS="$IFS"
IFS=
\n'

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

IFS="$OLD_IFS"
美煞众生 2025-01-07 22:12:39

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

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

当然,必须采用表达式来满足特定要求(例如 *.jpg 表示所有图片,或 2001-09-11*.jpg 表示仅某一天的图片)。

Not exactly an answer to the quoting/escaping problem of the original question but probably something that would actually have been more useful for the op:

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

Where of course the expression would have to be adopted to the specific requirement (e.g. *.jpg for all or 2001-09-11*.jpg for only the pictures of a certain day).

暮色兮凉城 2025-01-07 22:12:39
#! /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

`

#! /bin/bash

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

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

OUTPUT

640x360 80k 60k

1280x720 320k 128k

1280x720 320k 128k

`

魄砕の薆 2025-01-07 22:12:39

逃跑有效。

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

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

Escaping works.

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

Output:

$ ./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

Quoting the strings also produces the same output.

北方的巷 2025-01-07 22:12:39

如果 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

然后尝试此操作,以便文件名中的空格不被视为分隔符:

while read -r line; do
    FILES+=("$line")
done < ./files.txt

如果它们来自另一个命令,您需要像这样重写最后一行:

while read -r line; do
    FILES+=("$line")
done < <(./output-files.sh)

If the elements of FILES come from another file whose file names are line-separated like this:

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

then try this so that the whitespaces in the file names aren't regarded as delimiters:

while read -r line; do
    FILES+=("$line")
done < ./files.txt

If they come from another command, you need to rewrite the last line like this:

while read -r line; do
    FILES+=("$line")
done < <(./output-files.sh)
無處可尋 2025-01-07 22:12:39

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

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

Another solution is using a "while" loop instead a "for" loop:

index=0
while [ ${index} -lt ${#Array[@]} ]
  do
     echo ${Array[${index}]}
     index=$(( $index + 1 ))
  done
李不 2025-01-07 22:12:39

如果您不坚持使用 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

If you aren't stuck on using bash, different handling of spaces in file names is one of the benefits of the fish shell. Consider a directory which contains two files: "a b.txt" and "b c.txt". Here's a reasonable guess at processing a list of files generated from another command with bash, but it fails due to spaces in file names you experienced:

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

With fish, the syntax is nearly identical, but the result is what you'd expect:

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

It works differently because fish splits the output of commands on newlines, not spaces.

If you have a case where you do want to split on spaces instead of newlines, fish has a very readable syntax for that:

for f in (ls *.txt | string split " "); echo $f; end
旧情勿念 2025-01-07 22:12:39

我曾经重置 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

I used to reset the IFS value and rollback when done.

# 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}

Possible output from the loop:

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文