我将此字符串存储在一个变量中:
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
顺便说一句,当我尝试
mails2=($IN)
我在循环打印时只得到了第一个字符串,$IN
周围没有括号它可以工作。
local IFS=...
+1; (b) unset IFS
为 -1,这并不完全将 IFS 重置为其默认值,尽管我相信未设置的 IFS 的行为与 IFS 的默认值 ($'\t\n') 相同,但似乎盲目假设您的代码永远不会在 IFS 设置为自定义值的情况下被调用,这是一种不好的做法; (c) 另一个想法是调用子shell:(IFS=$custom; ...)
当子shell 退出时,IFS 将返回到原来的样子。
ruby -e "puts ENV.fetch('PATH').split(':')"
。如果您想保持纯 bash 将无济于事,但使用具有内置拆分的 任何脚本语言 会更容易。
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
\n
更改为一个空格。所以最后一行是mails=($(echo $IN | tr ";" " "))
。所以现在我可以通过使用数组表示法 mails[index]
或只是在循环中迭代来检查 mails
的元素
您可以设置 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"
取自 Bash shell script split array:
IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
echo ${arrIN[1]} # Output: john@home.com
解释:
此构造将字符串 IN
中所有出现的 ';'
(初始 //
表示全局替换)替换为 ' '
(单个空格),然后将空格分隔的字符串解释为数组(这就是周围的括号做)。
在花括号内使用 ' '
字符替换每个 ';'
字符的语法称为 Parameter Expansion。
有一些常见的陷阱:
如果原始字符串有空格,则需要使用 IFS:
IFS=':'; arrIN=($IN);取消设置 IFS;
如果原始字符串有空格并且分隔符是新行,则可以使用以下方式设置 IFS:
IFS=$'\n'; arrIN=($IN);取消设置 IFS;
IN="bla@some.com;john@home.com;*;broken apart"
。简而言之:如果您的标记包含嵌入的空格和/或字符,这种方法将失效。例如 *
恰好使令牌匹配当前文件夹中的文件名。
;*;
,则 *
将扩展为当前目录中的文件名列表。 -1
如果您不介意立即处理它们,我喜欢这样做:
for i in $(echo $IN | tr ";" "\n")
do
# process
done
您可以使用这种循环来初始化数组,但可能有更简单的方法来完成它。
IN="bla@some.com;john@home.com;*;broken apart"
。简而言之:如果您的标记包含嵌入的空格和/或字符,这种方法将失效。例如 *
恰好使令牌匹配当前文件夹中的文件名。
IN=abc;def;123
。我们如何也打印索引号? echo $count $i ?
我已经看到几个引用 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
此文件并选择特定字段进行进一步处理。
cut
,它是工作的正确工具!比任何那些 shell hack 都清楚。
兼容的答案
bash 中有很多不同的方法可以做到这一点。
但是,首先要注意的是,bash
有许多 特殊 功能(所谓的 bashisms)在任何其他 shell 中都不起作用。
特别是,在本文的解决方案以及线程中的其他解决方案中使用的数组、关联数组和模式替换是 bashism,可能无法在许多人使用的其他 shell 下工作。
例如:在我的 Debian GNU/Linux 上,有一个名为 dash 的 标准 shell;我知道很多人喜欢使用另一个叫做 ksh 的 shell;还有一个名为 busybox 的特殊工具,带有他自己的 shell 解释器 (ash)。
请求的字符串
上述问题中要拆分的字符串是:
IN="bla@some.com;john@home.com"
我将使用此字符串的修改版本来确保我的解决方案对包含空格的字符串具有鲁棒性,这可能会破坏其他解决方案:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
根据 bash 中的分隔符拆分字符串(版本 >=4.2)
在 pure bash
中,我们可以创建一个 array,其中的元素由 IFS 的临时值(输入字段分隔符)。除其他外,IFS 告诉 bash
在定义数组时应将哪些字符视为元素之间的分隔符:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS
在较新版本的 bash
中,使用 IFS 定义为命令添加前缀仅更改该命令的 IFS,然后立即将其重置为以前的值。这意味着我们可以在一行中完成上述操作:
IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'
我们可以看到字符串 IN
已存储到名为 fields
的数组中,并以分号分隔:
set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='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 或外部资源。
定义数组后,您可以使用一个简单的循环来处理每个字段(或者,更确切地说,处理您现在定义的数组中的每个元素):
# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
echo "> [$x]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
或者,您可以在使用移位方法处理后从数组中删除每个字段,我喜欢这种方法:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
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>]
更新:最近的 bash >= 4.4
在较新版本的 bash
中,您还可以使用命令 mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
此语法保留特殊字符、换行符和空字段!
如果您不想包含空字段,可以执行以下操作:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
使用 mapfile
,您还可以跳过声明数组并隐式“循环”分隔元素,在每个元素上调用一个函数:
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
(注意:如果您不关心字符串末尾的空字段或它们不存在,则格式字符串末尾的 \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.
或者您可以使用 <<<
,并在函数体中包含一些处理以删除它添加的换行符:
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
# Renders the same output:
# 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中的分隔符拆分字符串
如果您不能使用 bash
,或者如果您想编写可以在许多不同的 shell 中使用的东西,您通常不能使用 bashisms -- 并且这包括我们在上述解决方案中一直使用的数组。
但是,我们不需要使用数组来循环字符串的“元素”。在许多 shell 中都有一种语法用于从模式的 first 或 last 出现中删除字符串的子字符串。请注意,*
是代表零个或多个字符的通配符:
(到目前为止发布的任何解决方案都缺乏这种方法是我写这个答案的主要原因;)
${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
正如 Score_Under 所解释的:
# 和 % 分别从字符串的开头和结尾删除尽可能短的匹配子串,## 和 %% 删除尽可能长的匹配子串。
使用上述语法,我们可以创建一种方法,通过删除分隔符之前或之后的子字符串,从字符串中提取子字符串“元素”。
下面的代码块在 bash(包括 Mac OS 的 bash
)、dash、ksh 和 busybox 的 ash 中运行良好:
(感谢 Adam Katz 的 comment,让这个循环变得简单多了!)
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" != "$iter" ] ;do
# extract the substring from start of string up to delimiter.
iter=${IN%%;*}
# delete this first "element" AND next separator, from $IN.
IN="${IN#$iter;}"
# Print (or doing anything with) the first "element".
echo "> [$iter]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
玩得开心!
#
、##
、%
和 %%
替换具有 IMO 更容易记住的解释(它们删除了多少):#
和 %
删除可能的最短匹配字符串,并且##
和 %%
删除尽可能长的时间。
IFS=\; read -a fields <<<"$var"
在换行符上失败并添加尾随换行符。另一个解决方案删除了一个尾随的空字段。
while
条件更改为 [ "$IN" != "$iter" ]
,您将不需要最后的条件,只需要它的 else 子句。整个循环可以压缩为两条内线:while [ "$IN" != "$iter" ]; do iter="${IN%%;*}" IN="${IN#*;}"; echo "> [$iter]"; done
这对我有用:
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
cut
示例。
我认为 AWK 是解决您的问题的最佳和有效的命令。几乎每个 Linux 发行版都默认包含 AWK。
echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'
会给
bla@some.com john@home.com
当然,您可以通过重新定义 awk 打印字段来存储每个电子邮件地址。
inode=
替换为 ;
,例如用 sed -i 's/inode\=/\;/g' your_file_to_process
,然后在应用 awk
时定义 -F';'
,希望对您有所帮助。
这种方法怎么样:
IN="bla@some.com;john@home.com"
set -- "$IN"
IFS=";"; declare -a Array=($*)
echo "${Array[@]}"
echo "${Array[0]}"
echo "${Array[1]}"
IFS";" && Array=($IN)
$'...'
:IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'
。然后 echo "${Array[2]}"
将打印一个带有换行符的字符串。在这种情况下,set -- "$IN"
也是必需的。是的,为防止全局扩展,解决方案应包括 set -f
。
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )
在这种情况下将生成一个包含 8 个元素的数组(每个单词空格分隔一个元素),而不是 2 个(每行一个元素半冒号分隔)
arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )
来实现这一点,并建议将 IFS 更改为 IFS=$'\n'
以供将来登陆这里并需要拆分包含空格的字符串的人使用。 (并在之后恢复它)。 :)
这也有效:
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。
对 Darron's answer 的不同看法,这就是我的做法:
IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
IFS=";"
赋值只存在于 $(...; echo $IN)
子shell中;这就是为什么一些读者(包括我)最初认为它不起作用的原因。我假设所有的 $IN 都被 ADDR1 吞噬了。但是 nickjb 是正确的;它确实有效。原因是 echo $IN
命令使用 $IFS 的当前值解析其参数,然后使用空格分隔符将它们回显到标准输出,而不管 $IFS 的设置如何。所以最终效果就好像有人调用了 read ADDR1 ADDR2 <<< "bla@some.com john@home.com"
(注意输入是空格分隔的,而不是 ;-分隔的)。
echo $IN
中使用不带引号的变量扩展来扩展通配符 *
。
如果您不使用数组,这一个衬里怎么样:
IFS=';' read ADDR1 ADDR2 <<<$IN
read -r ...
来确保输入中的两个字符“\t”最终与变量中的两个字符相同(而不是单个制表符)。
echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2"
添加到您的代码段将输出 ADDR1 bla@some.com john@home.com\nADDR2
(\n 是换行符)
bash
4.3 中修复的 IFS
和此处的字符串的错误。引用 $IN
应该可以解决它。 (理论上,$IN
在扩展后不会进行分词或通配,这意味着引号应该是不必要的。不过,即使在 4.3 中,至少还有一个错误 - 已报告并计划修复 - 所以引用仍然是个好主意。)
在 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")'
这样做的诀窍是使用带有空分隔符的 read
(分隔符)的 -d
选项,以便强制 read
读取它输入的所有内容。并且我们用变量 in
的内容提供 read
,由于 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的更新
从 Bash 4.4 开始,内置 mapfile
(又名 readarray
)支持 -d
选项来指定分隔符。因此,另一种规范的方式是:
mapfile -d ';' -t array < <(printf '%s;' "$in")
\n
、空格和 *
一起正常工作的解决方案。此外,没有循环;执行后可以在 shell 中访问数组变量(与最高投票的答案相反)。注意,in=$'...'
,它不适用于双引号。我认为,它需要更多的赞成票。
%
作为分隔符,mapfile
示例将失败。我建议printf '%s' "$in%"
。
不设置 IFS
如果你只有一个冒号,你可以这样做:
a="foo:bar"
b=${a%:*}
c=${a##*:}
你会得到:
b = foo
c = bar
这是一个干净的 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
。
$in
可以扩展通配符。
以下 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 "$@"
help read
:-d delim continue until the first character of DELIM is read, rather than newline
有一个简单而聪明的方法是这样的:
echo "add:sfff" | xargs -d: -i echo {}
但是你必须使用 gnu xargs,BSD xargs 不支持 -d delim。如果你像我一样使用苹果mac。您可以安装 gnu xargs :
brew install findutils
然后
echo "add:sfff" | gxargs -d: -i echo {}
您可以将 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"
如果没有空间,为什么不呢?
IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}
echo ${arr[1]}
这是最简单的方法。
spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
这里有一些很酷的答案(尤其是错误的),但是对于类似于在其他语言中拆分的东西——这就是我认为原始问题的意思——我决定了这一点:
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
个解决方案。
IN
包含两个以上的电子邮件地址时不起作用。请在 palindrom's answer 参考相同的想法(但已修复)
${IN//;/ }
(双斜杠)使其也适用于两个以上的值。请注意,任何通配符 (*?[
) 都会被扩展。一个尾随的空字段将被丢弃。
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
read
的特定上下文中设置,因此它可能会扰乱其余代码(如果有)。
使用 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
set -- $IN
来避免“$IN”以破折号开头的一些问题。尽管如此,不带引号的 $IN
扩展将扩展通配符 (*?[
)。
两个都不需要 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"
在这两种情况下,可以在循环内组成的子列表在循环完成后是持久的。这在操作内存中的列表时很有用,而不是将列表存储在文件中。 {ps保持冷静并继续B-)}
除了已经提供的精彩答案之外,如果只是打印出您可以考虑使用 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]
在 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
其中 //
表示全局替换。
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 变量提供的列表必须是“硬”引用的,即带有单个刻度。
IFS 必须被保存和恢复,因为 Bash 不会以与命令相同的方式处理分配。另一种解决方法是将赋值包装在一个函数中,并使用修改后的 IFS 调用该函数。在这种情况下,不需要单独保存/恢复 IFS。感谢“Bize”指出这一点。
!"#$%&/()[]{}*? are no problem
好吧...不完全是:[]*?
是全局字符。那么如何创建这个目录和文件:`mkdir '!"#$%&'; touch '!"#$%&/()[]{} 让你哈哈哈哈——没问题'并运行你的命令?简单也许是美好的,但当它破碎时,它就破碎了。
mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem'
。他们只会创建一个目录和一个文件,名字看起来很奇怪,我必须承认。然后使用您提供的确切 IN
运行您的命令:IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
。你会发现你不会得到你期望的输出。因为您正在使用一种受路径名扩展影响的方法来拆分您的字符串。
*
、?
、[...]
甚至,如果设置了 extglob
,则 !(...)
、@(...)
、?(...)
、+(...)
是 这种方法有问题!
这是我的答案!
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
为什么这种方法对我来说是“最好的”?
因为两个原因:
您不需要转义分隔符;你不会有空格的问题。该值将在数组中正确分隔。
/etc/os-release
和 /etc/lsb-release
旨在获取来源,而不是解析。所以你的方法真的是错误的。此外,您并没有完全回答关于 在分隔符上分割字符串的问题。
IFS="=" read -r
的笨拙方式
DELIMITER_VAL='='
变量,对吧?无论如何,感谢您的贡献。 😊
用于拆分由 ';' 分隔的字符串的单行进入一个数组是:
IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}
这仅将 IFS 设置在子外壳中,因此您不必担心保存和恢复其值。
0: bla@some.com;john@home.com\n 1:
(\n 是新行)
$IN
被引用,因此不受 IFS 拆分的影响。 3.进程替换被空格分割,但这可能会破坏原始数据。
也许不是最优雅的解决方案,但适用于 *
和空格:
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}
计数。最后一步是使用 cut
安全地获得第 $i
部分。
IFS
设置在与read
相同的行上,没有分号或其他分隔符,而不是在单独的命令中,将其范围限定为该命令 - 所以它总是“恢复”;您无需手动执行任何操作。$IN
。该错误已在bash
4.3 中修复。