在Bash中将字符串拆分为数组

时间:2012-05-14 15:15:58

标签: arrays bash split

在Bash脚本中,我想将一行分成几部分并将它们存储在一个数组中。

该行:

Paris, France, Europe

我想将它们放在这样的数组中:

array[0] = Paris
array[1] = France
array[2] = Europe

我想使用简单的代码,命令的速度无关紧要。我该怎么办?

21 个答案:

答案 0 :(得分:927)

IFS=', ' read -r -a array <<< "$string"

请注意,$IFS中的字符被单独视为分隔符,因此在这种情况下,字段可以用分隔逗号或空格而不是两个字符的序列。有趣的是,当输入中出现逗号空格时,不会创建空字段,因为空格是专门处理的。

访问单个元素:

echo "${array[0]}"

迭代元素:

for element in "${array[@]}"
do
    echo "$element"
done

获取索引和值:

for index in "${!array[@]}"
do
    echo "$index ${array[index]}"
done

最后一个示例很有用,因为Bash数组很稀疏。换句话说,您可以删除元素或添加元素,然后索引不连续。

unset "array[1]"
array[42]=Earth

获取数组中元素的数量:

echo "${#array[@]}"

如上所述,数组可以是稀疏的,因此您不应该使用长度来获取最后一个元素。以下是Bash 4.2及更高版本中的方法:

echo "${array[-1]}"

在任何版本的Bash中(从2.05b之后的某个地方):

echo "${array[@]: -1:1}"

较大的负偏移选择远离数组末尾。请注意旧表单中减号之前的空格。这是必需的。

答案 1 :(得分:207)

这是一种不设置IFS的方法:

string="1:2:3:4:5"
set -f                      # avoid globbing (expansion of *).
array=(${string//:/ })
for i in "${!array[@]}"
do
    echo "$i=>${array[i]}"
done

这个想法是使用字符串替换:

${string//substring/replacement}

将$ substring的所有匹配替换为空格,然后使用替换字符串初始化数组:

(element1 element2 ... elementN)

注意:此答案使用split+glob operator。因此,为了防止某些字符(例如*)的扩展,最好为此脚本暂停globbing。

答案 2 :(得分:66)

t="one,two,three"
a=($(echo "$t" | tr ',' '\n'))
echo "${a[2]}"

打印三个

答案 3 :(得分:30)

有时碰巧我接受的答案中描述的方法不起作用,特别是如果分隔符是回车符。
在那些情况下,我以这种方式解决了:

string='first line
second line
third line'

oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"

for line in "${lines[@]}"
    do
        echo "--> $line"
done

答案 4 :(得分:26)

接受的答案适用于一行中的值。
如果变量有多行:

string='first line
        second line
        third line'

我们需要一个非常不同的命令来获取所有行:

while read -r line; do lines+=("$line"); done <<<"$string"

或者更简单的bash readarray

readarray -t lines <<<"$string"

利用printf功能打印所有线条非常容易:

printf ">[%s]\n" "${lines[@]}"

>[first line]
>[        second line]
>[        third line]

答案 5 :(得分:8)

如果您使用macOS而不能使用readarray,则只需执行此操作-

    PERMNO  date        LPERMNO   LINKDT      LINKENDDT    PRC   
0   66325   2006-03-30  66325     1992-07-01  2014-04-30   10
1   66325   2006-06-30  66325     1992-07-01  2014-04-30   10
2   66325   2015-09-30  66325     2014-05-01  2019-12-31   8.5
3   66325   2015-12-30  66325     2014-05-01  2019-12-31   8.5

要遍历元素:

TrackingNo

答案 6 :(得分:5)

将字符串拆分为数组的关键是", "的多字符分隔符。使用IFS进行多字符分隔符的任何解决方案本质上都是错误的,因为IFS是这些字符的集合,而不是字符串。

如果您指定IFS=", ",则字符串将在","" "或其任意组合中断,这不是{{1}的两个字符分隔符的准确表示}。

您可以使用", "awk拆分字符串,并使用流程替换:

sed

直接在Bash中使用正则表达式更有效:

#!/bin/bash

str="Paris, France, Europe"
array=()
while read -r -d $'\0' each; do   # use a NUL terminated field separator 
    array+=("$each")
done < <(printf "%s" "$str" | awk '{ gsub(/,[ ]+|$/,"\0"); print }')
declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output

使用第二种形式,没有子shell,它本身就会更快。

由bgoldst编辑:以下是一些基准测试,将我的#!/bin/bash str="Paris, France, Europe" array=() while [[ $str =~ ([^,]+)(,[ ]+|$) ]]; do array+=("${BASH_REMATCH[1]}") # capture the field i=${#BASH_REMATCH} # length of field + delimiter str=${str:i} # advance the string by that length done # the loop deletes $str, so make a copy if needed declare -p array # declare -a array=([0]="Paris" [1]="France" [2]="Europe") output... 解决方案与dawg的正则表达式解决方案进行比较,并且我还包含了readarray解决方案(注意:我稍微修改了正则表达式解决方案,以便与我的解决方案更加和谐)(另请参阅帖子下方的评论):

read

答案 7 :(得分:3)

这类似于Jmoney38的方法,但使用sed:

string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)
echo ${array[0]}

打印1

答案 8 :(得分:3)

纯bash多字符定界符解决方案。

正如其他人在此线程中指出的那样,OP的问题给出了一个以逗号分隔的字符串要解析为数组的示例,但没有指出他/她是否只对逗号分隔符,单字符分隔符或多字符定界符。

由于Google倾向于将此答案排在搜索结果的顶部或附近,因此,我想为读者提供有关多个字符分隔符问题的有力答案,因为至少在一个答复中也提到了这一点。

如果您正在寻找解决多字符定界符问题的方法,建议您查看Mallikarjun M的帖子,尤其是gniourf_gniourf的回复 谁使用参数扩展提供了这种优雅的纯BASH解决方案:

#!/bin/bash
str="LearnABCtoABCSplitABCaABCString"
delimiter=ABC
s=$str$delimiter
array=();
while [[ $s ]]; do
    array+=( "${s%%"$delimiter"*}" );
    s=${s#*"$delimiter"};
done;
declare -p array

链接到cited comment/referenced post

链接到所引用的问题:Howto split a string on a multi-character delimiter in bash?

答案 9 :(得分:1)

试试这个

IFS=', '; array=(Paris, France, Europe)
for item in ${array[@]}; do echo $item; done

这很简单。如果需要,您还可以添加声明(并删除逗号):

IFS=' ';declare -a array=(Paris France Europe)

添加IFS以撤消上述内容,但在新的bash实例中没有它可以正常工作

答案 10 :(得分:1)

当我试图解析一个输入时,我碰到了这篇文章: word1,word2,...

以上都不对我有帮助。通过使用awk解决了它。如果可以帮助某人:

STRING="value1,value2,value3"
array=`echo $STRING | awk -F ',' '{ s = $1; for (i = 2; i <= NF; i++) s = s "\n"$i; print s; }'`
for word in ${array}
do
        echo "This is the word $word"
done

答案 11 :(得分:0)

使用此:

countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"

#${array[1]} == Paris
#${array[2]} == France
#${array[3]} == Europe

答案 12 :(得分:0)

这是我的黑客!

使用字符串按字符串拆分字符串是一件非常无聊的事情。会发生的事情是我们的方法有限,仅在少数情况下有效(由&#34;;&#34;,&#34; /&#34;,&#34;。&#34;等等)或者我们在输出中有各种副作用。

下面的方法需要进行一些操作,但我相信它可以满足我们的大部分需求!

#!/bin/bash

# --------------------------------------
# SPLIT FUNCTION
# ----------------

F_SPLIT_R=()
f_split() {
    : 'It does a "split" into a given string and returns an array.

    Args:
        TARGET_P (str): Target string to "split".
        DELIMITER_P (Optional[str]): Delimiter used to "split". If not 
    informed the split will be done by spaces.

    Returns:
        F_SPLIT_R (array): Array with the provided string separated by the 
    informed delimiter.
    '

    F_SPLIT_R=()
    TARGET_P=$1
    DELIMITER_P=$2
    if [ -z "$DELIMITER_P" ] ; then
        DELIMITER_P=" "
    fi

    REMOVE_N=1
    if [ "$DELIMITER_P" == "\n" ] ; then
        REMOVE_N=0
    fi

    # NOTE: This was the only parameter that has been a problem so far! 
    # By Questor
    # [Ref.: https://unix.stackexchange.com/a/390732/61742]
    if [ "$DELIMITER_P" == "./" ] ; then
        DELIMITER_P="[.]/"
    fi

    if [ ${REMOVE_N} -eq 1 ] ; then

        # NOTE: Due to bash limitations we have some problems getting the 
        # output of a split by awk inside an array and so we need to use 
        # "line break" (\n) to succeed. Seen this, we remove the line breaks 
        # momentarily afterwards we reintegrate them. The problem is that if 
        # there is a line break in the "string" informed, this line break will 
        # be lost, that is, it is erroneously removed in the output! 
        # By Questor
        TARGET_P=$(awk 'BEGIN {RS="dn"} {gsub("\n", "3F2C417D448C46918289218B7337FCAF"); printf $0}' <<< "${TARGET_P}")

    fi

    # NOTE: The replace of "\n" by "3F2C417D448C46918289218B7337FCAF" results 
    # in more occurrences of "3F2C417D448C46918289218B7337FCAF" than the 
    # amount of "\n" that there was originally in the string (one more 
    # occurrence at the end of the string)! We can not explain the reason for 
    # this side effect. The line below corrects this problem! By Questor
    TARGET_P=${TARGET_P%????????????????????????????????}

    SPLIT_NOW=$(awk -F"$DELIMITER_P" '{for(i=1; i<=NF; i++){printf "%s\n", $i}}' <<< "${TARGET_P}")

    while IFS= read -r LINE_NOW ; do
        if [ ${REMOVE_N} -eq 1 ] ; then

            # NOTE: We use "'" to prevent blank lines with no other characters 
            # in the sequence being erroneously removed! We do not know the 
            # reason for this side effect! By Questor
            LN_NOW_WITH_N=$(awk 'BEGIN {RS="dn"} {gsub("3F2C417D448C46918289218B7337FCAF", "\n"); printf $0}' <<< "'${LINE_NOW}'")

            # NOTE: We use the commands below to revert the intervention made 
            # immediately above! By Questor
            LN_NOW_WITH_N=${LN_NOW_WITH_N%?}
            LN_NOW_WITH_N=${LN_NOW_WITH_N#?}

            F_SPLIT_R+=("$LN_NOW_WITH_N")
        else
            F_SPLIT_R+=("$LINE_NOW")
        fi
    done <<< "$SPLIT_NOW"
}

# --------------------------------------
# HOW TO USE
# ----------------

STRING_TO_SPLIT="
 * How do I list all databases and tables using psql?

\"
sudo -u postgres /usr/pgsql-9.4/bin/psql -c \"\l\"
sudo -u postgres /usr/pgsql-9.4/bin/psql <DB_NAME> -c \"\dt\"
\"

\"
\list or \l: list all databases
\dt: list all tables in the current database
\"

[Ref.: https://dba.stackexchange.com/questions/1285/how-do-i-list-all-databases-and-tables-using-psql]


"

f_split "$STRING_TO_SPLIT" "bin/psql -c"

# --------------------------------------
# OUTPUT AND TEST
# ----------------

ARR_LENGTH=${#F_SPLIT_R[*]}
for (( i=0; i<=$(( $ARR_LENGTH -1 )); i++ )) ; do
    echo " > -----------------------------------------"
    echo "${F_SPLIT_R[$i]}"
    echo " < -----------------------------------------"
done

if [ "$STRING_TO_SPLIT" == "${F_SPLIT_R[0]}bin/psql -c${F_SPLIT_R[1]}" ] ; then
    echo " > -----------------------------------------"
    echo "The strings are the same!"
    echo " < -----------------------------------------"
fi

答案 13 :(得分:0)

另一种不修改IFS的方法:

read -r -a myarray <<< "${string//, /$IFS}"

我们可以通过", "替换所有出现的$IFS所需的分隔符"${string//, /$IFS}",而不是更改IFS以匹配我们想要的分隔符。 < / p>

对于非常大的字符串,这可能会很慢吗?

这是基于丹尼斯威廉姆森的回答。

答案 14 :(得分:0)

这在OSX上对我有效:

string="1 2 3 4 5"
declare -a array=($string)

如果您的字符串具有不同的定界符,则只需1st用空格替换它们:

string="1,2,3,4,5"
delimiter=","
declare -a array=($(echo $string | tr "$delimiter" " "))

简单:-)

答案 15 :(得分:0)

我们可以使用tr命令将字符串拆分为数组对象。它可以同时在MacOS和Linux上运行

  #!/usr/bin/env bash
  currentVersion="1.0.0.140"
  arrayData=($(echo $currentVersion | tr "." "\n"))
  len=${#arrayData[@]}
  for (( i=0; i<=$((len-1)); i++ )); do 
       echo "index $i - value ${arrayData[$i]}"
  done

另一个选择是使用IFS命令

IFS='.' read -ra arrayData <<< "$currentVersion"
#It is the same as tr
arrayData=($(echo $currentVersion | tr "." "\n"))

#Print the split string
for i in "${arrayData[@]}"
do
    echo $i
done

答案 16 :(得分:0)

对于多行元素,为什么不喜欢

$ array=($(echo -e $'a a\nb b' | tr ' ' '§')) && array=("${array[@]//§/ }") && echo "${array[@]/%/ INTERELEMENT}"

a a INTERELEMENT b b INTERELEMENT

答案 17 :(得分:0)

更新:由于eval问题,请不要这样做。

仪式略少:

IFS=', ' eval 'array=($string)'

e.g。

string="foo, bar,baz"
IFS=', ' eval 'array=($string)'
echo ${array[1]} # -> bar

答案 18 :(得分:-1)

另一种方式是:

string="Paris, France, Europe"
IFS=', ' arr=(${string})

现在您的元素存储在“arr”数组中。 迭代元素:

for i in ${arr[@]}; do echo $i; done

答案 19 :(得分:-1)

由于有很多方法可以解决此问题,因此我们先定义要在解决方案中看到的内容。

  1. Bash为此提供了内置的readarray。让我们使用它。
  2. 避免使用丑陋和不必要的技巧,例如更改IFS,循环,使用eval或添加额外的元素然后将其删除。
  3. 找到一种简单易读的方法,可以轻松地将其应用于类似问题。

readarray命令最容易与换行符用作分隔符。使用其他定界符,可能会在数组中添加额外的元素。最干净的方法是先将我们的输入调整为与readarray配合使用的形式,然后再将其传递。

此示例中的输入具有多字符定界符。如果我们应用一些常识,则最好将其理解为逗号分隔的输入,可能需要针对每个输入修剪它们。我的解决方案是用逗号将输入分成多行,修剪每个元素,然后将其全部传递给readarray

string='  Paris,France  ,   All of Europe  '
readarray -t foo < <(tr ',' '\n' <<< "$string" |sed 's/^ *//' |sed 's/ *$//')
declare -p foo

# declare -a foo='([0]="Paris" [1]="France" [2]="All of Europe")'

答案 20 :(得分:-2)

另一种方法可以是:

str="a, b, c, d"  # assuming there is a space after ',' as in Q
arr=(${str//,/})  # delete all occurrences of ','

此后'arr'是一个包含四个字符串的数组。 这不需要处理IFS或读取或任何其他特殊的东西因此更简单和直接。