簡體   English   中英

Bash 元素中有空格的數組

[英]Bash array with spaces in elements

我正在嘗試在 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)

如您所見,每個文件名中間都有一個空格。

我試過將每個名稱用引號引起來,並用反斜杠將空格引起來 escaping,這兩種方法都不起作用。

當我嘗試訪問數組元素時,它繼續將空格視為元素定界符。

如何正確捕獲名稱中帶有空格的文件名?

我認為問題可能部分與您訪問元素的方式有關。 如果我for elem in $FILES做一個簡單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[@]} 擴展每個元素名稱要單獨一個字

當然,訪問單個成員時也應該使用雙引號

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

如果你的數組是這樣的:#!/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

不完全是原始問題的引用/轉義問題的答案,但可能實際上對操作更有用:

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

當然,表達方式必須根據特定要求采用(例如, *.jpg表示全部或2001-09-11*.jpg僅表示某一天的圖片)。

逃生工程。

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

引用字符串也會產生相同的輸出。

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

`

另一種解決方案是使用“while”循環而不是“for”循環:

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

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

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

上面已經回答了這個問題,但這個答案有點簡潔,手冊頁摘錄有點神秘。 我想提供一個完整的示例來演示這在實踐中是如何工作的。

如果沒有引用,數組只會擴展為由空格分隔的字符串,因此

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"

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM