如何在Bash中的分隔符上分割string?
我有这个string存储在一个variables:
IN="bla@some.com;john@home.com"  现在我想分割string; 定界符,使我有: 
 ADDR1="bla@some.com" ADDR2="john@home.com" 
 我不一定需要ADDR1和ADDR2variables。 如果它们是一个更好的数组的元素。 
经过下面答案的build议后,我得到了以下的结果:
 #!/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)的解决scheme;  。 我不确定这个答案是怎么回事,你如何将IFS重置为默认? 
  RE: IFS解决scheme,我试过这个,它工作,我保留旧的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) 
 在循环打印时,只有第一个string,没有$IN左右括号。 
 您可以设置内部字段分隔符 (IFS)variables,然后让它parsing成一个数组。 当这种情况发生在一个命令中,那么对IFS的赋值只发生在单个命令的环境( read )上。 然后它根据IFSvariables值将inputparsing成一个数组,然后我们可以迭代。 
 IFS=';' read -ra ADDR <<< "$IN" for i in "${ADDR[@]}"; do # process "$i" done 
 它将parsing由一行隔开的项目;  ,把它推到一个数组中。 用于处理整个$IN ,每次用一行input隔开;  : 
  while IFS=';' read -ra ADDR; do for i in "${ADDR[@]}"; do # process "$i" done done <<< "$IN" 
采取从Bash shell脚本拆分数组 :
 IN="bla@some.com;john@home.com" arrIN=(${IN//;/ }) 
说明:
 这种构造取代了所有的';'  (最初的//意思是全局replace),然后将空格分隔的string解释为一个数组(这是括号内的括号)。 
 在花括号里面使用的语法来replace每个';' 带有' '字符的字符称为参数扩展 。 
有一些常见的问题:
-  如果原始string有空格,则需要使用IFS :
-  IFS=':'; arrIN=($IN); unset IFS;
 
-  
-  如果原始string有空格,分隔符是新行,则可以使用以下命令设置IFS :
-  IFS=$'\n'; arrIN=($IN); unset IFS;
 
-  
如果你不介意处理,我喜欢这样做:
 for i in $(echo $IN | tr ";" "\n") do # process done 
你可以使用这种循环来初始化一个数组,但可能有一个更简单的方法来做到这一点。 希望这有助于,虽然。
兼容的答案
对于这个问题,在bash中已经有很多不同的方法来做到这一点。 但bash有许多特殊的function,所谓的bashism运行良好,但是在其他shell中不起作用。 特别是, 数组 , 关联数组和模式replace都是纯粹的双方,并且可能无法在其他shell下工作。
在我的Debian GNU / Linux上 ,有一个叫做dash的标准 shell,但是我知道很多喜欢使用ksh的人 。
最后,在非常小的情况下,有一个叫做busybox的特殊工具,带有自己的shell解释器( ash )。
请求的string
SO问题中的string示例是:
 IN="bla@some.com;john@home.com" 
由于这可能对空格有用,并且由于空格可以修改例程的结果,所以我更喜欢使用这个示例string:
  IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>" 
根据bash中的分隔符分割string(version> = 4.2)
在纯粹的 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 <<<"$var" 
 在最近的bash下使用这个语法不会改变当前会话的$IFS ,但只能用于当前的命令: 
 set | grep ^IFS= IFS=$' \t\n' 
 现在stringvar被分割并存储到一个数组(名为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>' 
这是做这件事最快捷的方法,因为没有叉子 ,也没有外部的资源。
从那里,你可以使用你已经知道的语法来处理每个字段;
 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>] 
根据shell中的分隔符分割string
但是,如果你要在许多shell下写一些可用的东西,你不得不使用bashisms 。
在许多shell中有一个语法用于分割stringaccros 首次或最后一次发生的子string:
 ${var#*SubStr} # will drop begin of string upto first occur of `SubStr` ${var##*SubStr} # will drop begin of string upto 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 
(这是我的答案出版物的主要原因;)
这个小示例脚本在bash , dash , ksh , busybox下工作得很好,并在Mac-OS的bash下也进行了testing:
 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>] 
玩的开心!
这种方法如何:
 IN="bla@some.com;john@home.com" set -- "$IN" IFS=";"; declare -a Array=($*) echo "${Array[@]}" echo "${Array[0]}" echo "${Array[1]}" 
资源
这也适用:
 IN="bla@some.com;john@home.com" echo ADD1=`echo $IN | cut -d \; -f 1` echo ADD2=`echo $IN | cut -d \; -f 2` 
小心,这个解决scheme并不总是正确的。 如果您仅通过“bla@some.com”,则将其分配给ADD1和ADD2。
 echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g' bla@some.com john@home.com 
 我已经看到了几个参考cut命令的答案,但是他们都被删除了。 有一点奇怪,没有人详细说明这一点,因为我认为这是做这种事情的更有用的命令之一,特别是parsing分隔的日志文件。 
 在将这个特定的例子分割成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这个文件,并select一个特定的领域进一步处理。 
这对我工作:
 string="1;2" echo $string | cut -d';' -f1 # output is 1 echo $string | cut -d';' -f2 # output is 2 
我认为AWK是解决您的问题的最好,最有效率的命令。 在几乎所有Linux发行版中,AWK都默认包含在Bash中。
 echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}' 
会给
 bla@some.com john@home.com 
当然,您可以通过重新定义awk打印字段来存储每个电子邮件地址。
对Darron的回答有一个不同的看法,这就是我的做法:
 IN="bla@some.com;john@home.com" read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN) 
在Bash中,一个防弹的方法,即使你的variables包含换行符也可以工作:
 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被强制读取所有提供的内容。 而且我们提供的read完全是variables的内容,没有拖尾换行感谢printf 。 请注意,我们也将分隔符放在printf以确保传递给read的string具有尾随分隔符。 没有它, 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以来,内buildmapfile (又名readarray )支持-d选项来指定分隔符。 因此,另一个规范的方法是: 
 mapfile -d ';' -t array < <(printf '%s;' "$in") 
这里是一个干净的3class轮:
 in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof" IFS=';' list=($in) for item in "${list[@]}"; do echo $item; done 
  IFS根据分隔符分隔单词, ()用于创build一个数组 。 然后[@]用来作为单独的单词返回每个项目。 
 如果您之后有任何代码,您还需要恢复$IFS ,例如未unset IFS 。 
如果你不使用数组,那这个class轮呢?
 IFS=';' read ADDR1 ADDR2 <<<$IN 
没有设置IFS
如果你只有一个冒号,你可以这样做:
 a="foo:bar" b=${a%:*} c=${a##*:} 
你会得到:
 b = foo c = bar 
有这样一个简单而巧妙的方法:
 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 {} 
这是最简单的方法。
 spo='one;two;three' OIFS=$IFS IFS=';' spo_array=($spo) IFS=$OIFS echo ${spo_array[*]} 
下面的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 
与其他解决scheme相比,这个解决scheme具有以下优点:
- 
IFS未被覆盖:由于即使是局部variables的dynamic作用域,覆盖循环中的IFS也会导致新值泄漏到循环中执行的函数调用中。
- 
不使用数组:使用 read将string读入数组需要在Bash中使用-a标志,在zsh使用-A。
如果需要的话,可以按如下方式将函数放入脚本中:
 #!/usr/bin/env bash split() { # ... } split "$@" 
如果没有空间,为什么不呢?
 IN="bla@some.com;john@home.com" arr=(`echo $IN | tr ';' ' '`) echo ${arr[0]} echo ${arr[1]} 
 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
这里有一些很酷的答案(尤其是errator),但是对于类似于其他语言的分裂的东西 – 这就是我原来的问题的意思 – 我在这个问题上解决了:
 IN="bla@some.com;john@home.com" declare -aa="(${IN/;/ })"; 
 现在${a[0]} , ${a[1]}等等,就像你期望的那样。 使用${#a[*]}作为条款数量。 或者当然重复: 
 for i in ${a[*]}; do echo $i; done 
重要的提示:
 这在没有空间担心的情况下工作,这解决了我的问题,但可能无法解决你的问题。 在这种情况下,使用$IFS解决scheme。 
 使用内置的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 
两个都不需要bash数组的bourne-ish选项:
案例1 :保持简洁:使用NewLine作为logging分隔符。
 IN="bla@some.com john@home.com" while read i; do # process "$i" ... eg. echo "[email:$i]" done <<< "$IN" 
注意:在第一种情况下,没有任何subprocess分叉来协助列表操作。
想法:也许值得在内部广泛使用NL,并且在外部产生最终结果时仅转换为不同的RS。
案例2 :使用“;” 作为logging分隔符…例如。
 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循环遍历字段并for相应的打印。 
testing
 $ 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] 
另外input:
 $ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;cd;e_;f" > [a] > [b] > [cd] > [e_] > [f] 
在Android shell中,大部分build议的方法都不起作用:
 $ 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 
  //表示全局replace。 
单行划分由';'分隔的string 进入一个数组是:
 IN="bla@some.com;john@home.com" ADDRS=( $(IFS=";" echo "$IN") ) echo ${ADDRS[0]} echo ${ADDRS[1]} 
这只能在一个子shell中设置IFS,所以你不必担心保存和恢复它的价值。
 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循环像往常一样处理该数组中的单个项目。 注意给INvariables的列表必须是“硬”的,也就是说,只有一个刻度。
IFS必须保存和恢复,因为Bash不会像命令一样处理赋值。 另一种解决方法是将分配包装在一个函数中,并用一个修改后的IFS调用该函数。 在这种情况下,不需要单独保存/恢复IFS。 感谢“Bize”的指出。
 也许不是最优雅的解决scheme,但与*和空格: 
 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] 
Other example (delimiters at beginning and end):
 IN=";bla@so me.com;*;john@home.com;" > [] > [bla@so me.com] > [*] > [john@home.com] > [] 
 Basically it removes every character other than ; making delims eg. ;;;  。 Then it does for loop from 1 to number-of-delimiters as counted by ${#delims} . The final step is to safely get the $i th part using cut . 
Okay guys!
Here's my answer!
 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 
Why this approach is "the best" for me?
Because of two reasons:
- You do not need to escape the delimiter;
- You will not have problem with blank spaces . The value will be properly separated in the array!
[]'s
Python version:
 python -c 'from __future__ import print_function ; f = open("your-file"); [print(a) for a in f.read().split()]'