數組的索引範圍不允許您在 bash 中迭代新行
我正在研究一個簡單的 bash 腳本,該腳本修復了一個重複的命名問題。
該腳本本身會抓取在 gameslist.xml 文件中多次提及的任何名稱,然後將這些名稱儲存在一個數組中以供以後使用。
我最終在索引中循環這個數組,如下所示:
pi@retropie:~ $ for game in ${game_array[@]:0:10} ; do echo $game; done
它將第一個元素拉到第 10 個元素(即
${game_array[9]}
),但是輸出連接到一行:pi@retropie:~ $ for game in ${game_array[@]:0:10} ; do echo $game; done R.B.I. Baseball '94 World Series Baseball '95 Mega Games 1 Bill Walsh College Football T2: The Arcade Game Sonic & Knuckles + Sonic the Hedgehog Sega Top Five Pyramid Magic Tecmo Super Baseball Super Chinese Tycoon
但是,如果我遍歷整個數組,它會按預期在新行上輸出:
pi@retropie:~ $ for game in ${game_array[@]}; do echo $game; done | head -10 R.B.I. Baseball '94 World Series Baseball '95 Mega Games 1 Bill Walsh College Football T2: The Arcade Game Sonic & Knuckles + Sonic the Hedgehog Sega Top Five Pyramid Magic Tecmo Super Baseball Super Chinese Tycoon
欄位分隔符已設置為新行,
IFS='$\n'
這就是第二個有效的原因,但我一生都無法弄清楚為什麼它不適用於第一個?這是上下文的完整測試腳本:
#!/bin/bash user_input=$1 while [ -z "$user_input" ]; do echo "please enter the name of the system you want to fix the game list for" echo "(as it is labelled in /home/pi/RetroPie/roms)" read -r user_input done ls "/home/pi/RetroPie/roms/$user_input" >/dev/null 2>&1 if [ "$?" -ne 0 ]; then echo "this doesn't appear to be a system installed here. exiting." exit 1 fi games_to_fix() { IFS=$'\n' console=$1 filepath="/opt/retropie/configs/all/emulationstation/gamelists/$console/gamelist.xml" game_array=($(fgrep "<name>" "$filepath" | sort | uniq -c | sort -rn | awk '$1 > 1 {print $0}'| cut -d ">" -f 2 | cut -d "<" -f 1)) number_to_fix=($(fgrep "<name>" "$filepath" | sort | uniq -c | sort -rn | awk '$1 > 1 {print $1}')) } get_new_name() { mYpath=$1 new_name=$(echo $mYpath | cut -d ">" -f 2 | cut -d "<" -f 1 | sed -e 's/\.\///g' | sed -e 's/\.7z//g') } games_to_fix $user_input IFS=$'\n' index=0 for i in ${number_to_fix[@]}; do loop=1 for game in ${game_array[@]:$index:$i}; do # for ind in $(eval echo {1..$i}); do line_number=$(fgrep -n "<name>$game</name>" $filepath | awk '{print $1}' | cut -d : -f 1 | sed -e "${loop}q;d") path_line_number=$(expr $line_number - 1 ) path=$(sed "${path_line_number}q;d" $filepath | cut -d : -f 2) get_new_name "$path" sed -i "${line_number}s/$game/$new_name/g" $filepath ((loop++)) done index=$(expr index + $i); done
簡而言之:除非您明確需要欄位/單詞拆分,否則您應該在這樣的數組擴展周圍使用引號。
"$@"
將每個位置參數擴展為一個單獨的單詞,"${a[@]}"
. 通過擴展,這應該以相同的方式為"${a[@]:0:2}"
.也就是說,在 Bash 中似乎仍然不一致,並且您使用的內容應該適用於您的情況(因為值中沒有 glob 字元,並且通過
IFS
正確設置來處理欄位拆分)。採取完整的陣列工作:
$ IFS=$'\n' $ a=("foo bar" "asdf ghjk") $ printf "%s\n" ${a[@]} foo bar asdf ghjk
切片不適用於數組,但適用於
$@
:$ printf "%s\n" ${a[@]:0:2} foo bar asdf ghjk $ set -- "aa bb" "cc dd" $ printf "%s\n" ${@:1:2} aa bb cc dd
它在 ksh 和 zsh 中確實有效,這強調了 Bash 在這裡不一致(zsh 當然有它自己的等效語法):
$ ifs=$'\n' ksh -c 'IFS="$ifs"; a=("foo bar" "asdf ghjk"); printf "%s\n" ${a[@]:0:2}' foo bar asdf ghjk $ ifs=$'\n' zsh -yc 'IFS="$ifs"; a=("foo bar" "asdf ghjk"); printf "%s\n" ${a[@]:0:2}' foo bar asdf ghjk
引用的版本也可以在 Bash 中使用,並且當您只需要原樣的值時會更好,因為您不需要依賴
IFS
.IFS
即使數組元素有空格,預設值在這裡也可以正常工作:$ unset IFS # uses default of $' \t\n' $ printf "%s\n" "${a[@]:0:2}" foo bar asdf ghjk
看起來好像未加引號
${a[@]:0:2}
的元素用空格連接起來,有點像在 Bash 中不會發生分詞的上下文中發生的事情(例如str=${a[@]}
)。IFS
然後它像往常一樣嘗試用 拆分結果。例如在這裡,它在第二個數組元素中間的換行符處拆分:$ IFS=$'\n' $ a=("foo bar" $'new\nline' "asdf ghjk"); $ printf ":%s\n" ${a[@]:0:3} :foo bar new :line asdf ghjk
如上所述,在大多數情況下,您確實應該在數組擴展周圍使用引號,但仍然會假設
${a[@]:n:m}
會導致多個單詞,就像這樣${a[@]}
做一樣。這裡的行為似乎存在於 Bash
4.4.12(1)-release
和5.0.0(1)-alpha
. 我發布了一個關於它的錯誤報告。