2016-11-18 93 views
1

为什么不能运行下面的bash代码?如何在bash中的多字符分隔符上分割字符串?

for i in $(echo "emmbbmmaaddsb" | split -t "mm" ) 
do 
    echo "$i" 
done 

预期输出:

e 
bb 
aaddsb 
+1

...是吧?这不是'split'所做的。如在**中,完全**与其实际功能无关。 –

+0

你是否想*知道如何在bash中的任意多字符分隔符上分割任意字符串?为什么不编辑你的问题,而是问,如果这是你真正想知道的? –

+0

@CharlesDuffy那么分裂在你看来有什么作用? – v217

回答

5

既然你期待换行符,你可以简单地替换mm所有实例的字符串以换行符。在纯天然的bash:

in='emmbbmmaaddsb' 
sep='mm' 
printf '%s\n' "${in//$sep/$'\n'}" 

如果你想要做一个较长的输入流这样的替换,你可能会更好使用awk,如bash的内置字符串操作不能很好地扩展到超过几千字节的内容。在​​给出的gsub_literal外壳函数(backending到awk)是适用的:

# Taken from http://mywiki.wooledge.org/BashFAQ/021 

# usage: gsub_literal STR REP 
# replaces all instances of STR with REP. reads from stdin and writes to stdout. 
gsub_literal() { 
    # STR cannot be empty 
    [[ $1 ]] || return 

    # string manip needed to escape '\'s, so awk doesn't expand '\n' and such 
    awk -v str="${1//\\/\\\\}" -v rep="${2//\\/\\\\}" ' 
    # get the length of the search string 
    BEGIN { 
     len = length(str); 
    } 

    { 
     # empty the output string 
     out = ""; 

     # continue looping while the search string is in the line 
     while (i = index($0, str)) { 
     # append everything up to the search string, and the replacement string 
     out = out substr($0, 1, i-1) rep; 

     # remove everything up to and including the first instance of the 
     # search string from the line 
     $0 = substr($0, i + len); 
     } 

     # append whatever is left 
     out = out $0; 

     print out; 
    } 
    ' 
} 

...使用,在这种情况下,如:

gsub_literal "mm" $'\n' <your-input-file.txt >your-output-file.txt 
2

使用bash:

s="emmbbmmaaddsb" 
for i in "${s//mm/$'\n'}"; do echo "$i"; done 

输出:

 
e 
bb 
aaddsb 
+0

这不会分割任何东西......它只是用换行符替换“mm”。你可能只需要'echo'$ {s // m/$'\ n'}“'并完全抛弃'for'循环。 –

+0

@gniourf_gniourf:我认为提问者想要对每一行都做些什么。 – Cyrus

+0

但是'for'循环,你写它的方式,不会在每一行循环。它只在单个字符串'$'e \ nbb \ naaddsb'上循环一次。 –

0

对于一个正则表达式发生或全局s/regexp/replacement/g,推荐的字符取代工具是sed的命令s/regexp/replacement/,您甚至不需要循环或变量。

管你echo输出,并尝试mm替代字符witht换行符\n

echo "emmbbmmaaddsb" | sed 's/mm/\n/g'

输出是:

e 
bb 
aaddsb 
+0

“推荐”?请参阅[BashFAQ#100](http://mywiki.wooledge.org/BashFAQ/100)以获取有关在bash中执行字符串操作的最佳做​​法指导。您会注意到参数扩展通常被认为是短输入的最佳实践方法(而“echo | sed”方法虽然简洁,但它在如何实现内部引擎方面有很大的开销 - 通常需要两个叉子,一个mkfifo,一个需要链接和加载的外部工具的'execv'等等)。 –

+0

...例如,如果您在逐行处理输入的紧密循环(或者遍历包含数百或数千个文件名的glob结果),则调用'echo | sed'每行都会*绝对*成为反模式。 (相比之下,调用'sed' *一次*来处理整个传入流通常是合适的)。 –

2

更普遍的例子,而无需更换多带有单个字符分隔符的字符分隔符如下所示:

使用参数扩展:(从@gniourf_gniourf的评论)

#!/bin/bash 

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

更粗的一种方式

#!/bin/bash 

# main string 
str="LearnABCtoABCSplitABCaABCString" 

# delimiter string 
delimiter="ABC" 

#length of main string 
strLen=${#str} 
#length of delimiter string 
dLen=${#delimiter} 

#iterator for length of string 
i=0 
#length tracker for ongoing substring 
wordLen=0 
#starting position for ongoing substring 
strP=0 

array=() 
while [ $i -lt $strLen ]; do 
    if [ $delimiter == ${str:$i:$dLen} ]; then 
     array+=(${str:strP:$wordLen}) 
     strP=$((i + dLen)) 
     wordLen=0 
     i=$((i + dLen)) 
    fi 
    i=$((i + 1)) 
    wordLen=$((wordLen + 1)) 
done 
array+=(${str:strP:$wordLen}) 

declare -p array 

参考 - Bash Tutorial - Bash Split String

+0

这被破坏了(如果字符串包含glob字符或空格等,将会失败)。而且,你并没有使用现代的Bash成语,这使得代码看起来很奇怪。你只需要一个简单的循环:'str =“LearnABCtoABCSplitABCaABCString”delimiter = ABC s = $ str $ delimiter array =();而[[$ s]]; do array + =(“$ {s %%”$ delimiter“*}”); S = $ {S#* “$分界”};完成; declare -p array'。就这样。 –

+0

谢谢@gniourf_gniourf的评论。我刚刚开始使用Bash脚本,并且您的建议对于以惯用方式思考非常有帮助。 –