如何在bash中替换字符串中的模式

时间:2014-05-22 10:16:09

标签: regex bash shell

我将以下字符串存储在shell

中的变量中
a1="aaa,bbb3,12,ccc,"
a2="aaa,2,bbb,ccc,"

我想对上面的字符串变量执行一个公共命令,以从路径中删除对象编号。对象编号是2个逗号之间的数字。

所以结果应该是

a1="aaa,bbb3,ccc,"
a2="aaa,bbb,ccc,"

我怎么能这样做?

我试过了:

echo ${a1//,[0-9],/,} ==> wrong result
echo ${a2//,[0-9],/,} ==> good result

此外,我将以下字符串存储在shell

中的变量中
a1="aaa,bbb3,#zu_45,ccc,"
a2="aaa,#mn,bbb,ccc,"
a3="aaa,bbb,ccc,#kn,"

我想对上面的字符串变量执行一个通用命令,从路径中删除以#开头的对象。位于2个逗号之间的对象。

所以结果应该是

a1="aaa,bbb3,ccc,"
a2="aaa,bbb,ccc,"
a3="aaa,bbb,ccc,"

我怎么能这样做?

5 个答案:

答案 0 :(得分:3)

您可以使用此sed:

sed -r 's/,(#.*)?[0-9]+//' file

a1="aaa,bbb3,ccc,"
a2="aaa,bbb,ccc,"
a3="aaa,bbb,ccc,"

答案 1 :(得分:2)

对于您的第一个问题,您可以使用extglob

$ a1="aaa,bbb3,12,ccc,"
$ a2="aaa,2,bbb,ccc,"
$ shopt -s extglob
$ echo "${a1//,+([0-9]),/,}"
aaa,bbb3,ccc,
$ echo "${a2//,+([0-9]),/,}"
aaa,bbb,ccc,
$

Greg's wiki和许多其他地方都有很好的记录。


对于第二个问题,您可以使用更高级的模式匹配来处理此问题,或者将逗号分隔的元素视为字段并单独处理它们。

首先,模式匹配解决方案。

$ a1="aaa,bbb3,#zu_45,ccc,"
$ a2="aaa,#mn,bbb,ccc,"
$ a3="aaa,bbb,ccc,#kn,"
$ echo "${a1//,#+([^,]),/,}"
aaa,bbb3,ccc,
$ echo "${a2//,#+([^,]),/,}"
aaa,bbb,ccc,
$ echo "${a3//,#+([^,]),/,}"
aaa,bbb,ccc,
$

(我只想指出任何流浪的读者,虽然像这样的模式可能看起来像正则表达式{/ 3}}。)

其次,处理字段等字段的解决方案当然会涉及循环。这是一个单行的例子:

$ a=(${a1//,/ }); unset newa; for i in "${a[@]}"; do if [[ ! $i =~ ^# ]]; then newa="$newa${newa:+,}$i"; fi; done; echo "$newa"
aaa,bbb3,ccc

您可以对其他变量重复此操作。

请注意,这取决于您的数据中没有任何空格,因为这是用于分隔bash数组中字段的字符。

答案 2 :(得分:1)

使用纯bash,你可以这样做:

$ re='(.*),[0-9]+,(.*)'
$ a1="aaa,bbb3,12,ccc,"
$ a2="aaa,2,bbb,ccc,"
$ [[ $a1 =~ $re ]] && echo ${BASH_REMATCH[1]},${BASH_REMATCH[2]}
aaa,bbb3,ccc,
$ [[ $a2 =~ $re ]] && echo ${BASH_REMATCH[1]},${BASH_REMATCH[2]}
aaa,bbb,ccc,

这些echo可以转换为作业,为您提供所需的内容。

您可以在使用( )之前和之后捕获零件,而不是进行搜索和替换,它们将存储在特殊数组变量$BASH_REMATCH中。

对于第二个,你可以做类似的事情:

$ re='(.*),#[^,]*,(.*)'
$ a1="aaa,bbb3,#zu_45,ccc,"
$ a2="aaa,#mn,bbb,ccc,"
$ a3="aaa,bbb,ccc,#kn,"
$ [[ $a1 =~ $re ]] && echo ${BASH_REMATCH[1]},${BASH_REMATCH[2]}
aaa,bbb3,ccc,
$ [[ $a2 =~ $re ]] && echo ${BASH_REMATCH[1]},${BASH_REMATCH[2]}
aaa,bbb,ccc,
$ [[ $a3 =~ $re ]] && echo ${BASH_REMATCH[1]},${BASH_REMATCH[2]}
aaa,bbb,ccc,

使用[^,](不是逗号)字符类可确保不使用字符串的其余部分。

更新

你可以把它变成这样的函数:

$ rm_hash () { 
>     local re='(.*),#[^,]*,(.*)'
>     local subject="$1"
>     [[ $subject =~ $re ]] && echo ${BASH_REMATCH[1]},${BASH_REMATCH[2]}
> }
$ a1="aaa,bbb3,#zu_45,ccc,"
$ rm_hash $a1
aaa,bbb3,ccc,

答案 3 :(得分:0)

仅限bash解决方案:

  

...从路径中删除对象编号...

$ a1="aaa,bbb3,12,ccc,"
$ echo ${a1//,+([0-9]),/,}
aaa,bbb3,ccc,
  

...从路径中删除以#开头的对象...

$ a1="aaa,bbb3,#zu_45,ccc,"
$ echo ${a1//,#*([^,]),/,}
aaa,bbb3,ccc,

以上两种解决方案可以组合成一个单独的操作:

$ a1="aaa,32,bbb3,#zu_45,ccc,"
$ echo ${a1//,@(#*([^,])|+([0-9])),/,}
aaa,bbb3,ccc,

所有这些解决方案都依赖于shell option extglob的设置。他们使用bash extended patterns来匹配字符串的一部分:

  • +([0-9]) - 匹配一个或多个数字;
  • #*([^,]) - 匹配以散列[#]开头的任何字符串,后跟任意数量的非逗号;
  • @(...|...) - 完全匹配给定模式的一个,这些模式由竖线符号分隔。

答案 4 :(得分:0)

不完全是单线,但是防弹方法:

IFS=, read -d '' -ra ary <<< "$variable"
unset ary[${#ary[@]}-1]

将创建一个数组ary,其中的字段是变量variable中的字段(字段以逗号分隔)。然后,如果你想删除所有数字:

for i in "${!ary[@]}"; do
    [[ ${ary[i]} = +([[:digit:]]) ]] && unset ary[$i]
done

或删除所有以哈希开头的元素:

for i in "${!ary[@]}"; do
    [[ ${ary[i]} = \#* ]] && unset ary[$i]
done

最后,把它放回你的变量中:

printf -v variable '%s,' "${ary[@]}"

执行该操作的功能:

remove_number() {
    local ary i
    IFS=, read -d '' -ra ary <<< "${!1}"
    unset ary[${#ary[@]}-1]
    for i in "${!ary[@]}"; do
        [[ ${ary[i]} = +([[:digit:]]) ]] && unset ary[$i]
    done
    printf -v "$1" '%s,' "${ary[@]}"
}

演示(假设此功能在会话中定义):

$ a1="aaa,bbb3,12,ccc,"
$ remove_number a1
$ echo "$a1"
aaa,bbb3,ccc,

如果变量中有换行符,这也有效(注意:它不会删除负数......这个数字并不是很清楚,但修改函数来处理这个问题也很简单,请参阅{下面{1}}:

is_number

哦,如果数字出现在第一个字段中(与其他答案不同),它会起作用:

$ a=$'aaa,\n\nnewline here\n,123456,-1234,abc\n,'
$ echo "$a"
aaa,

newline here
,123456,-1234,abc
,
$ remove_number a
$ echo "$a"
aaa,

newline here
,-1234,abc
,

对于“空”列表也很好:

$ a='1234,abc,'
$ b='1234,'
$ remove_number a
$ remove_number b
$ echo "a='$a'"; echo "b='$b'"
a='abc,'
b=','

更实用的方法:如果满足函数给出的条件,则创建一个删除字段的函数:

$ a=','
$ remove_number a
$ echo "$a"
,

然后是几个条件:

remove_cond() {
    # $1 is condition
    # $2 is name of variable that contains list
    local ary i
    IFS=, read -d '' -ra ary <<< "${!2}"
    unset ary[${#ary[@]}-1]
    for i in "${!ary[@]}"; do
        "$1" "${ary[i]}" && unset ary[$i]
    done
    printf -v "$2" '%s,' "${ary[@]}"
}

在当前会话中设置这些:

is_number() {
    [[ $1 = ?(-)+([[:digit:]]) ]]
}
is_bang() {
    [[ $1 = \#* ]]
}
is_bang_or_number() {
    is_number "$1" || is_bang "$1"
}