如何在字符串中递增最后一个数字;bash

8
我有一个字符串,看起来像这样/foo/bar/baz59_ 5stuff.thing。我想要将最后一个数字(例如示例中的数字5)加一,但仅当它大于另一个变量时才这样做。9会增加到10。请注意,最后一个数字也可能是多位数;此外,“stuff.thing”可以是任何除数字之外的内容,因此无法硬编码。
上述示例将导致/foo/bar/baz59_ 6stuff.thing
我找到了多个问题(和答案),可以从字符串中提取最后一个数字,显然可以在比较中使用它。我遇到的问题是如何确保当我进行替换时,我只替换最后一个数字(因为显然我不能只简单地用“6”替换“5”)。有人能提出任何建议吗?
awk / sed / bash / grep都可行。

1
如果最后一个数字是9,期望的结果是什么?如果是59呢? - James Brown
1
如果最后一位数字是9,则期望的下一个数字是10。例如:59应该变成60,99应该变成100等等。 - UKMonkey
@gboffi 不是的;前一个字符是不固定的。 - UKMonkey
如果它比脚本参数大,您是指整个字符串还是数字?如果是数字,您是指增加字符串中最后一个大于参数的数字,还是增加字符串中最后一个数字(如果它大于参数)? - ysth
你的意思不太清楚,例如如果它大于脚本参数,那么它是否应该在参数为4时递增? - karakfa
显示剩余2条评论
7个回答

8

更新的答案

感谢@EdMorton指出了数字超过阈值的进一步要求。可以按如下方式完成:

perl -spe 's/(\d+)(?!.*\d+)/$1>$thresh? $1+1 : $1/e' <<<  "abc123_456.txt" -- -thresh=500

您可以在Perl正则表达式中使用/e来评估/计算替换。这里我只是将捕获的数字字符串加1,但您也可以进行更复杂的操作:
perl -pe 's/(\d+)(?!.*\d+)/$1+1/e' <<< "abc123_456.txt"
abc123_457.txt
< p > (?!.*\d+) 是(希望如此)对于任何其他数字的负面前瞻。< /p > < p > $1 代表在捕获组< code >(\ d +)< /code >中捕获的任何数字序列。< /p > < p > 请注意,这需要修改以处理小数,负数和科学表示法 - 但这是可能的。< /p >

这似乎完美地工作了,甚至可以处理包含引号的字符串。 - UKMonkey
5
或者s/.*(?<!\d)\K(\d+)/$1+1/sea可以翻译为“在不以数字结尾的任何字符前匹配任何字符,然后重复匹配一个或多个数字并将其替换为该数字加一”。 - ysth
2
@EdMorton 很敏锐!我之前没有注意到,但现在已经添加进去了。 - Mark Setchell
1
or s/(\d+)(?=\D*$)/$1+1/ea - Miller
感谢大家提供的其他建议。 - Mark Setchell

4

使用bash正则表达式匹配:

$ f="/foo/bar/baz59_ 99stuff.thing"
$ [[ $f =~ ([0-9]+)([^0-9]+)$ ]]

好的,现在我们有什么?

$ declare -p BASH_REMATCH
declare -ar BASH_REMATCH=([0]="99stuff.thing" [1]="99" [2]="stuff.thing")

因此,我们可以构建新的文件名。
if [[ $f =~ ([0-9]+)([^0-9]+)$ ]]; then
    prefix=${f%${BASH_REMATCH[0]}}           # remove "99stuff.thing" from $f
    number=$(( 10#${BASH_REMATCH[1]} + 1 ))  # use "10#" to force base10
    new=${prefix}${number}${BASH_REMATCH[2]}
    echo $new
fi
# => /foo/bar/baz59_ 100stuff.thing

4

使用GNU awk进行第三个参数匹配:

$ awk -v t=3 'match($0,/(.*)([0-9]+)([^0-9]*)$/,a) && a[2]>t{a[2]++; $0=a[1] a[2] a[3]} 1' file
/foo/bar/baz59_ 6stuff.thing

只需将t设置为您增加的阈值即可,例如:

$ awk -v t=7 'match($0,/(.*)([0-9]+)([^0-9]*)$/,a) && a[2]>t{a[2]++; $0=a[1] a[2] a[3]} 1' file
/foo/bar/baz59_ 5stuff.thing

1

如果它大于一个脚本参数。

如果我理解正确(我假设您通过脚本传递参数,如果它的值大于字符串的第二个字段数字,则将1增加到该第二个字段的数字),请尝试以下操作。

cat script.ksh
value=$1
echo "/foo/bar/baz59_ 5stuff.thing" | 
awk -v arg="$value" '
  match($2,/[0-9]+/){
    val=substr($2,RSTART,RLENGTH)
    val=val<arg?val+1:val
    $2=val substr($2,RSTART+RLENGTH)
}
1'

这是一个例子,当我运行 script.ksh 脚本时,它会输出以下内容。

/script.ksh 7
/foo/bar/baz59_ 6stuff.thing

@UKMonkey,您能否再检查一下这个并告诉我是否适用于您? - RavinderSingh13
@UKMonkey,很酷,很高兴它对你有帮助。我之所以特别询问是因为没有其他解决方案从脚本中获取参数并进行比较,所以我想确保我正确理解了问题,如果你通过传递参数进行了测试,那就很好 :) - RavinderSingh13
我关心的是替换背后的原则,而不是如何实现,比如如何插入字符串等等,这些都是我不想让问题受到影响的担忧。 - UKMonkey
@UKMonkey,嗯(如果我理解正确的话),如果这是完整的要求,那么现在你不需要担心了,你现在有完整的解决方案 :) - RavinderSingh13

1

一个 awk:

$ echo /foo/bar/baz59_ 99stuff.thing |
awk '
/[0-9]/ {
    rstart=1                                    # keep track of the start
    while(match(substr($0,rstart),/[0-9]+/)) {  # while numbers last
        rstart+=RSTART+RLENGTH-1                # increase rstart
        rlength=RLENGTH                         # remember length too
    }
    v=substr($0,rstart-rlength,rlength)+1                    # increase last number
    print substr($0,1,rstart-rlength-1) v substr($0,rstart)  # print in parts
    next
}1'                                             # in case there was no number
/foo/bar/baz59_ 100stuff.thing

编辑:

哎呀,我错过了参数要求(如果最后一个数字大于脚本参数,则将其增加一):

$ echo /foo/bar/baz59_ 99stuff.thing |
awk -v arg=100 '
/[0-9]/ {
    rstart=1
    while(match(substr($0,rstart),/[0-9]+/)) {
        rstart+=RSTART+RLENGTH-1
        rlength=RLENGTH
    }
    v=substr($0,rstart-rlength,rlength)
    if(0+v>arg) {                           # test if v greater that argument
        print substr($0,1,rstart-rlength-1) v+1 substr($0,rstart)
        next
    }
}1'

现在输出:

/foo/bar/baz59_ 99stuff.thing

更新了脚本参数要求。一开始忘记了。 - James Brown

1
这是一个较短的 GNU awk 方法:

cat incr.awk
{
   n = split($0, a, /[0-9]+/, b)
   for(i=1; i<n; i++)
      s = s a[i] b[i] + (b[i] < max && i == n-1 ? 1 : 0)
   print s a[i]
}

然后将其用作:
awk -v max=80 -f incr.awk <<< '/foo/bar/baz59_ 5stuff.thing'
/foo/bar/baz59_ 6stuff.thing

awk -v max=80 -f incr.awk <<< '/foo/bar/baz59_ 79stuff.thing'
/foo/bar/baz59_ 80stuff.thing


awk -v max=80 -f incr.awk <<< '/foo/bar/baz59_ 90stuff.thing'
/foo/bar/baz59_ 90stuff.thing

awk -v max=80 -f incr.awk <<< '/foo/bar/baz59_ 80stuff.thing'
/foo/bar/baz59_ 80stuff.thing

awk -v max=80 -f incr.awk <<< '/foo/bar/stuff.thing'
/foo/bar/stuff.thing

0
如果“testing”数字在“bound”变量中:
perl -pe 'BEGIN{$bound=6} s{(\d+)_(\d+)(?!.*\d+)}{ $i=$2+1;($i>$bound? $1+1:$1)."_".$i}e' <<<"/foo/bar/baz59_5stuff.thing"
/foo/bar/baz59_6stuff.thing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接