在Bash脚本中,我想将一行分成几部分并将它们存储在一个数组中。
该行:
Paris, France, Europe
我想将它们放在这样的数组中:
array[0] = Paris
array[1] = France
array[2] = Europe
我想使用简单的代码,命令的速度无关紧要。我该怎么办?
答案 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)
由于有很多方法可以解决此问题,因此我们先定义要在解决方案中看到的内容。
readarray
。让我们使用它。IFS
,循环,使用eval
或添加额外的元素然后将其删除。 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或读取或任何其他特殊的东西因此更简单和直接。