Bash

數組的索引範圍不允許您在 bash 中迭代新行

  • December 16, 2018

我正在研究一個簡單的 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[@]}做一樣。

這裡的行為似乎存在於 Bash4.4.12(1)-release5.0.0(1)-alpha. 我發布了一個關於它的錯誤報告。

引用自:https://unix.stackexchange.com/questions/459664