我将此字符串存储在变量中:
IN="bla@some.com;john@home.com"
现在我想用;
分隔符拆分字符串,以便我有:
ADDR1="bla@some.com"
ADDR2="john@home.com"
我不一定需要ADDR1
和ADDR2
变量。如果它们是一个更好的数组元素。
根据以下答案的建议,我最终得到了以下内容:
#!/usr/bin/env bash
IN="bla@some.com;john@home.com"
mails=$(echo $IN | tr ";" "\n")
for addr in $mails
do
echo "> [$addr]"
done
输出:
> [bla@some.com]
> [john@home.com]
有一个解决方案涉及将Internal_field_separator(IFS)设置为;
。我不确定该答案发生了什么,你如何将IFS
重置为默认值?
RE:IFS
解决方案,我试过这个并且它有效,我保留旧版IFS
然后恢复它:
IN="bla@some.com;john@home.com"
OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
echo "> [$x]"
done
IFS=$OIFS
当我尝试时,BTW
mails2=($IN)
我在循环中打印时只获得了第一个字符串,$IN
周围没有括号。
答案 0 :(得分:1075)
您可以设置internal field separator(IFS)变量,然后将其解析为数组。当在命令中发生这种情况时,IFS
的赋值仅发生在该单个命令的环境中(read
)。然后它根据IFS
变量值将输入解析为一个数组,然后我们可以迭代它。
IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
# process "$i"
done
它将解析由;
分隔的一行项目,将其推入数组。用于处理整个$IN
的内容,每次输入一行由;
分隔:
while IFS=';' read -ra ADDR; do
for i in "${ADDR[@]}"; do
# process "$i"
done
done <<< "$IN"
答案 1 :(得分:850)
取自 Bash shell script split array :
IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
说明:
此构造用';'
(单个空格)替换字符串//
中所有出现的IN
(初始' '
表示全局替换),然后解释空格 - 作为数组的定界字符串(这是周围的括号所做的)。
花括号内部使用';'
字符替换每个' '
字符的语法称为Parameter Expansion。
有一些常见问题:
答案 2 :(得分:221)
如果您不介意立即处理它们,我喜欢这样做:
for i in $(echo $IN | tr ";" "\n")
do
# process
done
您可以使用这种循环来初始化数组,但可能有一种更简单的方法。但希望这会有所帮助。
答案 3 :(得分:160)
对于这个问题,在bash中已经有很多不同的方法可以做到这一点。 但是bash有很多特殊的功能,所谓的 bashism 效果很好,但是在任何其他shell中都无效。
特别是,数组,关联数组和模式替换是纯 bashisms ,可能无法正常工作其他 shell 。
在我的 Debian GNU / Linux 上,有一个名为dash的标准 shell,但我知道有很多人喜欢使用ksh
最后,在非常小的情况下,有一个名为busybox的特殊工具,带有自己的shell解释器(ash)。
SO问题中的字符串示例是:
IN="bla@some.com;john@home.com"
由于这对于空格非常有用,并且空格可以修改例程的结果,我更喜欢使用此示例字符串:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
在 pure bash下,我们可以使用数组和 IFS :
var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS
IFS=\; read -a fields <<<"$IN"
在最近的bash下使用此语法不会更改当前会话的$IFS
,而只会更改当前命令:
set | grep ^IFS=
IFS=$' \t\n'
现在,字符串var
被拆分并存储到一个数组(名为fields
)中:
set | grep ^fields=\\\|^var=
fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
var='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
我们可以使用declare -p
declare -p IN fields
declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
read
是执行拆分的最快方式,因为没有 forks 并且没有调用外部资源。
从那里,您可以使用已知的语法处理每个字段:
for x in "${fields[@]}";do
echo "> [$x]"
done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]
或在处理后删除每个字段(我喜欢这个转移方法):
while [ "$fields" ] ;do
echo "> [$fields]"
fields=("${fields[@]:1}")
done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]
或甚至是简单的打印输出(更短的语法):
printf "> [%s]\n" "${fields[@]}"
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]
您可以使用mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
此语法保留特殊字符,换行符和空字段!
如果你不关心空字段,你可以:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
但你可以通过函数使用字段:
myPubliMail() {
printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
# mail -s "This is not a spam..." "$2" </path/to/body
printf "\e[3D, done.\n"
}
mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail
(格式字符串末尾的Nota:\0
没用,而你不关心字符串末尾的空字段)
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
将呈现如下内容:
Seq: 0: Sending mail to 'bla@some.com', done.
Seq: 1: Sending mail to 'john@home.com', done.
Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
或在功能中删除<<<
bash语法添加的换行符:
myPubliMail() {
local seq=$1 dest="${2%$'\n'}"
printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
# mail -s "This is not a spam..." "$dest" </path/to/body
printf "\e[3D, done.\n"
}
mapfile <<<"$IN" -td \; -c 1 -C myPubliMail
将呈现相同的输出:
Seq: 0: Sending mail to 'bla@some.com', done.
Seq: 1: Sending mail to 'john@home.com', done.
Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
但是如果你想在许多shell下编写可用的东西,你必须不使用 bashisms 。
在许多shell中使用了一种语法,用于在子串的第一个或 last 出现时拆分字符串:
${var#*SubStr} # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*} # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end
(缺少这是我的答案发布的主要原因;)
正如Score_Under所指出的那样:
#
和%
删除最短的匹配字符串,
##
和%%
删除最长的。其中
#
和##
表示左起(开头)字符串,
%
和%%
meand 来自右侧(结束)字符串。
这个小样本脚本在bash,dash,ksh,busybox下运行良好,并且在Mac-OS的bash下进行了测试:
var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$var" ] ;do
iter=${var%%;*}
echo "> [$iter]"
[ "$var" = "$iter" ] && \
var='' || \
var="${var#*;}"
done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]
玩得开心!
答案 4 :(得分:126)
我看到了几个引用cut
命令的答案,但它们都被删除了。没有人详细说明这一点有点奇怪,因为我认为这是执行此类事情的更有用的命令之一,尤其是在解析分隔的日志文件时。
在将此特定示例拆分为bash脚本数组的情况下,tr
可能更有效,但可以使用cut
,如果要从中提取特定字段,则更有效中间。
示例:强>
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com
显然,您可以将其放入循环中,并迭代-f参数以独立地拉出每个字段。
当您使用包含以下行的分隔日志文件时,这会变得更有用:
2015-04-27|12345|some action|an attribute|meta data
cut
能够cat
此文件并选择特定字段进行进一步处理非常方便。
答案 5 :(得分:93)
这对我有用:
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
答案 6 :(得分:83)
这种方法怎么样:
IN="bla@some.com;john@home.com"
set -- "$IN"
IFS=";"; declare -a Array=($*)
echo "${Array[@]}"
echo "${Array[0]}"
echo "${Array[1]}"
答案 7 :(得分:62)
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
答案 8 :(得分:62)
这也有效:
IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`
小心,这个解决方案并不总是正确的。如果您只传递“bla@some.com”,它会将其分配给ADD1和ADD2。
答案 9 :(得分:38)
我认为AWK是解决问题的最佳和最有效的命令。几乎在每个Linux发行版中,AWK都默认包含在Bash中。
echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'
将给出
bla@some.com john@home.com
当然,您可以通过重新定义awk打印字段来存储每个电子邮件地址。
答案 10 :(得分:30)
对Darron's answer的不同看法,我就是这样做的:
IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
答案 11 :(得分:26)
在Bash中,一种防弹方式,即使你的变量包含换行符也能正常工作:
IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
查找
$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'
这项工作的诀窍是使用-d
(分隔符)的read
选项和空分隔符,以便read
被强制读取它所提供的所有内容。我们正确地向read
提供变量in
的内容,而感谢printf
没有尾随换行符。请注意,我们还将分隔符放在printf
中,以确保传递给read
的字符串具有尾随分隔符。没有它,read
会修剪潜在的尾随空字段:
$ in='one;two;three;' # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'
保留尾随的空字段。
自Bash 4.4起,内置mapfile
(又名readarray
)支持-d
选项来指定分隔符。因此另一种规范方式是:
mapfile -d ';' -t array < <(printf '%s;' "$in")
答案 12 :(得分:21)
如果你没有使用数组,这个衬垫怎么样:
IFS=';' read ADDR1 ADDR2 <<<$IN
答案 13 :(得分:19)
这是一个干净的3线:
in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done
其中IFS
根据分隔符分隔单词,()
用于创建array。然后[@]
用于将每个项目作为单独的单词返回。
如果您之后有任何代码,则还需要恢复$IFS
,例如unset IFS
。
答案 14 :(得分:16)
不设置IFS
如果你只有一个冒号,你可以这样做:
a="foo:bar"
b=${a%:*}
c=${a##*:}
你会得到:
b = foo
c = bar
答案 15 :(得分:8)
以下Bash / zsh函数将第一个参数拆分为第二个参数给出的分隔符:
split() {
local string="$1"
local delimiter="$2"
if [ -n "$string" ]; then
local part
while read -d "$delimiter" part; do
echo $part
done <<< "$string"
echo $part
fi
}
例如,命令
$ split 'a;b;c' ';'
产量
a
b
c
例如,此输出可以通过管道传输到其他命令。例如:
$ split 'a;b;c' ';' | cat -n
1 a
2 b
3 c
与其他解决方案相比,这个解决方案具有以下优势:
IFS
未被覆盖:由于偶数局部变量的动态范围,在循环上覆盖IFS
会导致新值泄漏到从循环内执行的函数调用。
不使用数组:使用read
将字符串读入数组需要Bash中的标记-a
和zsh中的-A
。
如果需要,可以将该函数放入脚本中,如下所示:
#!/usr/bin/env bash
split() {
# ...
}
split "$@"
答案 16 :(得分:7)
有一种简单而聪明的方式:
app = Flask(__name__)
app.config.from_envvar('FLASKAPP_SETTINGS', silent=True)
db = SQLAlchemy(app)
但你必须使用gnu xargs,BSD xargs cant支持-d delim。如果你像我一样使用苹果mac。你可以安装gnu xargs:
echo "add:sfff" | xargs -d: -i echo {}
然后
brew install findutils
答案 17 :(得分:6)
你可以将awk应用于很多情况
echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'
你也可以使用这个
echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"
答案 18 :(得分:5)
这是最简单的方法。
spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
答案 19 :(得分:4)
这里有一些很酷的答案(尤其是errator。),但是对于类似于其他语言中的分裂的东西 - 这就是我对原始问题的意思 - 我已经解决了这个问题:
IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";
现在${a[0]}
,${a[1]}
等正如您所料。使用${#a[*]}
表示条款数量。或者迭代,当然:
for i in ${a[*]}; do echo $i; done
重要提示:
这适用于无法担心的空间,这解决了我的问题,但可能无法解决您的问题。在这种情况下,请使用$IFS
解决方案。
答案 20 :(得分:3)
IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
echo $entry
done
输出
bla@some.com
john@home.com
系统:Ubuntu 12.04.1
答案 21 :(得分:2)
除了已经提供的精彩答案之外,如果只是打印出数据,您可以考虑使用awk
:
awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
这会将字段分隔符设置为;
,以便它可以使用for
循环遍历字段并相应地进行打印。
$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]
使用另一个输入:
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c d;e_;f"
> [a]
> [b]
> [c d]
> [e_]
> [f]
答案 22 :(得分:2)
两个不需要bash数组的bourne-ish替代方案:
案例1 :保持简单明了:使用NewLine作为记录分隔符......例如。
IN="bla@some.com
john@home.com"
while read i; do
# process "$i" ... eg.
echo "[email:$i]"
done <<< "$IN"
注意:在第一种情况下,没有子进程分叉以协助列表操作。
想法:也许值得广泛使用NL 内部,并且只能在生成最终结果外部时转换为不同的RS。
案例2 :使用“;”作为记录分隔符......例如。
NL="
" IRS=";" ORS=";"
conv_IRS() {
exec tr "$1" "$NL"
}
conv_ORS() {
exec tr "$NL" "$1"
}
IN="bla@some.com;john@home.com"
IN="$(conv_IRS ";" <<< "$IN")"
while read i; do
# process "$i" ... eg.
echo -n "[email:$i]$ORS"
done <<< "$IN"
在这两种情况下,循环完成后,循环内的子列表可以是持久的。这在操作内存中的列表时非常有用,而是将列表存储在文件中。 {附:保持冷静并继续B-)}
答案 23 :(得分:2)
如果没有空间,为什么不呢?
IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}
echo ${arr[1]}
答案 24 :(得分:1)
在Android shell中,大多数提议的方法都不起作用:
$ IFS=':' read -ra ADDR <<<"$PATH"
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory
工作是什么:
$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin
其中//
表示全局替换。
答案 25 :(得分:1)
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f
输出:
bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)
说明:使用括号()进行简单赋值会将分号分隔列表转换为数组,前提是您在执行此操作时具有正确的IFS。标准FOR循环像往常一样处理该数组中的各个项目。 请注意,为IN变量提供的列表必须是&#34; hard&#34;引用,即单蜱。
必须保存和恢复IFS,因为Bash不会像命令一样处理赋值。另一种解决方法是将赋值包装在函数内,并使用修改后的IFS调用该函数。在这种情况下,不需要单独保存/恢复IFS。感谢&#34; Bize&#34;指出那个。
答案 26 :(得分:1)
好的伙计们!
这是我的答案!
DELIMITER_VAL='='
read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF
SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
echo "$i"
done
为什么这种方法对我来说是“最好的”?
由于两个原因:
[]的
答案 27 :(得分:1)
使用内置的set
加载$@
数组:
IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'
然后,让派对开始:
echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2
答案 28 :(得分:0)
这甚至可以处理空格:
struct MySum : Sum<std::vector<int>>
{
MySum(int t0, int t1, int t2, int t3) : Sum<std::vector<int>>{{t0, t1, t2, t3}} { }
};
struct MySum4 : Sum<std::array<int, 4>>
{
MySum4(int t0, int t1, int t2, int t3) : Sum<std::array<int, 4>>{{t0, t1, t2, t3}} { }
};
struct MySumSet : Sum<std::set<int>>
{
MySumSet(int t0, int t1, int t2, int t3) : Sum<std::set<int>>{{t0, t1, t2, t3}} { }
};
struct MySumList : Sum<std::list<int>>
{
MySumList(int t0, int t1, int t2, int t3) : Sum<std::list<int>>{{t0, t1, t2, t3}} { }
};
int main()
{
MySum s(1, 2, 3, 4);
s.print();
MySum4 s4(1, 2, 3, 4);
s4.print();
static_assert(s4.m_terms.size() == 4);
MySumSet ss(1, 2, 3, 4);
ss.print();
MySumList sl(1, 2, 3, 4);
sl.print();
}
答案 29 :(得分:0)
也许不是最优雅的解决方案,但适用于*
和空格:
IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
echo "> [`echo $IN | cut -d';' -f$i`]"
done
输出
> [bla@so me.com]
> [*]
> [john@home.com]
其他示例(开头和结尾的分隔符):
IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []
基本上它删除;
以外的所有字符delims
。 ;;;
。然后它for
从1
循环到number-of-delimiters
,由${#delims}
计算。最后一步是使用$i
安全地获取cut
部分。
答案 30 :(得分:0)
用于分隔由';'分隔的字符串的单行进入一个数组是:
IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}
这只在子shell中设置IFS,因此您不必担心保存和恢复其值。
答案 31 :(得分:-2)
对我有用:
echo $ PATH | ruby -ne'放入$ _。split(“:”)'
答案 32 :(得分:-5)
又一个迟到的答案......如果你是一个有头脑的人,这里是 bashj (https://sourceforge.net/projects/bashj/)解决方案:
#!/usr/bin/bashj
#!java
private static String[] cuts;
private static int cnt=0;
public static void split(String words,String regexp) {cuts=words.split(regexp);}
public static String next() {return(cnt<cuts.length ? cuts[cnt++] : "null");}
#!bash
IN="bla@some.com;john@home.com"
: j.split($IN,";") # java method call
while true
do
NAME=j.next() # java method call
if [ $NAME != null ] ; then echo $NAME ; else exit ; fi
done
答案 33 :(得分:-6)
有两种简单的方法:
cat "text1;text2;text3" | tr " " "\n"
和
cat "text1;text2;text3" | sed -e 's/ /\n/g'