2016-11-18 129 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腳本,並且您的建議對於以慣用方式思考非常有幫助。 –

相關問題