[英]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[@]}"
...
可以使用 ${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.