N次应用替换

时间:2018-12-17 14:17:07

标签: bash sed

我有一个像这样的字符串

data1_data2_data3_data4@data5,data6

碰巧,有时data5包含下划线,而下划线恰好是字段分隔符。丑,我知道。

我想用以下方式读取这些数据片段:

IFS="_@," read d1 d2 d3 d4 d5 d6 <<< "$input"

当data5包含下划线时,问题就出现了。要变通解决此问题。我想用逗号替换前三个下划线(还有@)。到目前为止,我发现的简单方法是使用sed:

sed 's/_/,/; s/_/,/; s/_/,/; s/@/,/' <<< "$input"

但是重复三遍相同的替换似乎效率很低。如果我需要重复5000次会怎样?

有什么办法告诉sed重复替换一定次数?

为完整起见,请输入示例:

input="data1_data2_data3_data4@d_a_t_a_5,data6"
IFS="," read d1 d2 d3 d4 d5 d6 <<< "$input"

预期输出:

d1=="data1"
d2=="data2"
d3=="data3"
d4=="data4"
d5=="d_a_t_a_5"
d6=="data6"

5 个答案:

答案 0 :(得分:1)

您可以在流程替换中使用此awk

input="data1_data2_data3_data4@d_a_t_a_5,data6"

IFS=, read d1 d2 d3 d4 d5 d6 < <(awk -F@ -v OFS=, -v n=3 '{
while (i++<n) sub(/_/, ",", $1)} 1' <<< "$input")

# check variable values
declare -p d1 d2 d3 d4 d5 d6

declare -- d1="data1"
declare -- d2="data2"
declare -- d3="data3"
declare -- d4="data4"
declare -- d5="d_a_t_a_5"
declare -- d6="data6"
  • awk命令使用@作为字段分隔符。
  • awk命令仅在第一个字段恰好 _内将,替换为n

答案 1 :(得分:1)

使用awk。

$ input="data1_data2_data3_data4@d_a_t_a_5,data6"
$ awk -v RS='[@\n]' '{ if(NR % 2){ gsub(/_/, ","); ORS = "," } else ORS = "\n" } 1' <<< "$input"
data1,data2,data3,data4,d_a_t_a_5,data6

答案 2 :(得分:0)

一种选择是使用外壳扩展手动拆分${var%%pat}去除最大后缀匹配的pat,而${var#pat}去除最短前缀匹配的pat

while IFS= read line; do
    tmpline=$line
    d1=${tmpline%%_*} tmpline=${tmpline#*_}
    d2=${tmpline%%_*} tmpline=${tmpline#*_}
    d3=${tmpline%%_*} tmpline=${tmpline#*_}
    d4=${tmpline%%@*} tmpline=${tmpline#*@}
    d5=${tmpline%%,*} tmpline=${tmpline#*,}
    d6=${tmpline}

    printf "%s\n" "d1=$d1" "d2=$d2" "d3=$d3" "d4=$d4" "d5=$d5" "d6=$d6"
done <<< "$input"

或者要解决bash读取速度慢的问题,请手动分割行

tmpinput=$input
while [[ $tmpinput ]]; do
    if [[ $tmpinput = *$'\n'* ]]; then
        tmpline=${tmpinput%%$'\n'*} tmpinput=${tmpinput#*$'\n'}
    else
        tmpline=${tmpinput} tmpinput=''
    fi

    d1=${tmpline%%_*} tmpline=${tmpline#*_}
    d2=${tmpline%%_*} tmpline=${tmpline#*_}
    d3=${tmpline%%_*} tmpline=${tmpline#*_}
    d4=${tmpline%%@*} tmpline=${tmpline#*@}
    d5=${tmpline%%,*} tmpline=${tmpline#*,}
    d6=${tmpline}

    printf "%s\n" "d1=$d1" "d2=$d2" "d3=$d3" "d4=$d4" "d5=$d5" "d6=$d6"
done 

答案 3 :(得分:0)

bash中,我将使用正则表达式。

$ cat input
one_two_three_fourpt1_fourpt2@fivept1_fivept2,six
$ regex='([^_]+)_([^_]+)_([^_]+)_(.+)@([^,]+).(.*)'
$ while IFS= read -r line; do
> [[ $line =~ $regex ]]
> done < input
$ printf '%s\n' "${BASH_REMATCH[@]}"
one_two_three_fourpt1_fourpt2@fivept1_fivept2,six
one
two
three
fourpt1_fourpt2
fivept1_fivept2
six

BASH_REMATCH的零元素包含整个匹配项;其余元素从左侧开始包含各个捕获组。

或者,您可以使用read首先在@上进行拆分,然后根据需要再次使用_,来拆分两半。

$ IFS="@" read -r first second <<< "$line"
$ IFS=_ read -r f1 f2 f3 f4 <<< "$first"
$ IFS=, read -r f5 f6 <<< "$second"

由于第二个只读调用具有4个参数,因此f4将包含第三个_之后的内容,而不会在其他_上拆分任何字段。


类似的正则表达式和两级拆分方案可用于支持对文件内容进行更高效迭代的语言,正如Nahuel Fouilleul指出的那样,bash的执行速度不是很快。 (read逐字节读取其输入,而不是一次读取整个块,以避免读取超出完全消耗一行输入所需的更多字节。)

答案 4 :(得分:0)

如果您使用@。 _。 _....,
您可以尝试这个awk:

echo "data1_data2@d_a_t_a_17,data3_data4@d_a_t_a_5,data6_data7" |
awk '
{
i = split ( $0 , a , "_" )
for ( j = 1 ; j <= i ; j++ )
  if ( a[j] !~ /@/ )
    print "d" ++k "==\"" a[j] "\""
  else
    {
      split ( a[j] , b , "@" )
      print "d" ++k "==\"" b[1] "\""
      sub ( ".*@" , "" , a[j] )
      while ( a[j] !~ "," )
        {
          c = c a[j] "_"
          j++
        }
        split ( a[j] , b , "," )
        c = c b[1]
        print "d" ++k "==\"" c "\""
        a[j] = b[2]
        j--
        c = ""
    }
}'