为什么不能运行下面的bash代码?如何在bash中的多字符分隔符上分割字符串?
for i in $(echo "emmbbmmaaddsb" | split -t "mm" )
do
echo "$i"
done
预期输出:
e
bb
aaddsb
为什么不能运行下面的bash代码?如何在bash中的多字符分隔符上分割字符串?
for i in $(echo "emmbbmmaaddsb" | split -t "mm" )
do
echo "$i"
done
预期输出:
e
bb
aaddsb
既然你期待换行符,你可以简单地替换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
使用bash:
s="emmbbmmaaddsb"
for i in "${s//mm/$'\n'}"; do echo "$i"; done
输出:
e bb aaddsb
这不会分割任何东西......它只是用换行符替换“mm”。你可能只需要'echo'$ {s // m/$'\ n'}“'并完全抛弃'for'循环。 –
@gniourf_gniourf:我认为提问者想要对每一行都做些什么。 – Cyrus
但是'for'循环,你写它的方式,不会在每一行循环。它只在单个字符串'$'e \ nbb \ naaddsb'上循环一次。 –
对于一个正则表达式发生或全局s/regexp/replacement/g
,推荐的字符取代工具是sed
的命令s/regexp/replacement/
,您甚至不需要循环或变量。
管你echo
输出,并尝试mm
替代字符witht换行符\n
:
echo "emmbbmmaaddsb" | sed 's/mm/\n/g'
输出是:
e
bb
aaddsb
“推荐”?请参阅[BashFAQ#100](http://mywiki.wooledge.org/BashFAQ/100)以获取有关在bash中执行字符串操作的最佳做法指导。您会注意到参数扩展通常被认为是短输入的最佳实践方法(而“echo | sed”方法虽然简洁,但它在如何实现内部引擎方面有很大的开销 - 通常需要两个叉子,一个mkfifo,一个需要链接和加载的外部工具的'execv'等等)。 –
...例如,如果您在逐行处理输入的紧密循环(或者遍历包含数百或数千个文件名的glob结果),则调用'echo | sed'每行都会*绝对*成为反模式。 (相比之下,调用'sed' *一次*来处理整个传入流通常是合适的)。 –
更普遍的例子,而无需更换多带有单个字符分隔符的字符分隔符如下所示:
使用参数扩展:(从@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
这被破坏了(如果字符串包含glob字符或空格等,将会失败)。而且,你并没有使用现代的Bash成语,这使得代码看起来很奇怪。你只需要一个简单的循环:'str =“LearnABCtoABCSplitABCaABCString”delimiter = ABC s = $ str $ delimiter array =();而[[$ s]]; do array + =(“$ {s %%”$ delimiter“*}”); S = $ {S#* “$分界”};完成; declare -p array'。就这样。 –
谢谢@gniourf_gniourf的评论。我刚刚开始使用Bash脚本,并且您的建议对于以惯用方式思考非常有帮助。 –
...是吧?这不是'split'所做的。如在**中,完全**与其实际功能无关。 –
你是否想*知道如何在bash中的任意多字符分隔符上分割任意字符串?为什么不编辑你的问题,而是问,如果这是你真正想知道的? –
@CharlesDuffy那么分裂在你看来有什么作用? – v217