【问题标题】:sed/awk or other: one-liner to increment a number by 1 keeping spacing characterssed/awk 或其他:单行将数字增加 1,保持间距字符
【发布时间】:2010-04-21 22:28:57
【问题描述】:

编辑:我事先不知道我的数字将在哪个“列”,我想要一个单行。显然 sed 不做算术,所以也许是基于 awk 的单线解决方案?

我有一个字符串:(注意间距)

eh oh    37

我希望它变成:

eh oh    36

(所以我想保持间距)

使用 awk 我不知道怎么做,到目前为止我有:

echo "eh oh   37" | awk '$3>=0&&$3<=99 {$3--} {print}'

但这给出了:

eh oh 36

(丢失的空格字符,因为字段分隔符是'')

有没有办法问 awk 类似 “使用与输入完全相同的字段分隔符打印输出”

然后我尝试了其他方法,使用 awk 的 sub(..,..) 方法:

' sub(/[0-9][0-9]/, ...) {print}'

但还没有雪茄:我不知道如何引用正则表达式并在第二个参数中对其进行算术运算(我现在留下了“...”)。

然后我尝试使用 sed,但在此之后卡住了:

echo "eh oh   37" | sed -e 's/\([0-9][0-9]\)/.../' 

我可以使用对匹配数字的引用从 sed 进行算术运算,并且输出不修改空格字符的数量吗?

请注意,这与我关于 Emacs 的问题以及如何将其应用于某些(大)Emacs 区域(使用带有 Emacs 的 shell-command-on-region 的替换区域)的问题有关,但它不是相同的问题:这个问题专门关于如何在使用 awk/sed/etc 时“保留空格”。

【问题讨论】:

  • 我知道使用 perl 会很简单——你可以让 split 给你分隔符和字段。
  • 您可以在 awk 中通过完全忽略它的字段拆分来管理它,只在 $0 上做一些正则表达式的东西,但这有点不雅。 perl 是一种选择吗?
  • @Jefromi:当然,Perl 是一种选择……但我也对丑陋的 awk 版本感兴趣 :)

标签: unix shell sed awk


【解决方案1】:

这是 ghostdog74 的 答案的变体,它不需要将数字锚定在字符串的末尾。这是通过使用match 来完成的,而不是依赖于特定位置的数字。

这会将第一个数字替换为它的值减一:

$ echo "eh oh    37      aaa     22    bb" | awk '{n = substr($0, match($0, /[0-9]+/), RLENGTH) - 1; sub(/[0-9]+/, n); print }'
eh oh    36      aaa     22    bb

使用gsub 代替sub 会将“37”和“22”都替换为“36”。如果线路上只有一个号码,则使用哪个号码并不重要。但是,通过这种方式,它将处理带有尾随空格的数字以及可能存在的其他非数字字符(在一些空格之后)。

如果你有gawk,你可以像这样使用gensub在字符串中挑出一个任意数字(只需设置which的值):

$ echo "eh oh    37      aaa     22    bb    19" |
    awk -v which=2 'BEGIN { regex = "([0-9]+)\\>[^0-9]*";
        for (i = 1; i < which; i++) {regex = regex"([0-9]+)\\>[^0-9]*"}}
        { match($0, regex, a);
        n = a[which] - 1;                    # do the math
        print gensub(/[0-9]+/, n, which) }'
eh oh    37      aaa     21    bb    19

第二个 (which=2) 编号从 22 变为 21。并且保留了嵌入的空格。

它被分成多行以便于阅读,但它是复制/粘贴的。

【讨论】:

    【解决方案2】:
    $ echo "eh oh    37" | awk '{n=$NF+1; gsub(/[0-9]+$/,n) }1'
    eh oh    38
    

    $ echo "eh oh    37" | awk '{n=$NF+1; gsub(/..$/,n) }1'
    eh oh    38
    

    【讨论】:

      【解决方案3】:

      类似

      number=`echo "eh oh 37" | grep -o '[0-9]*'`
      sed 's/$number/`expr $number + 1`/'
      

      【讨论】:

      • @Paul Creasey:所以你不能从“内部”sed 做到这一点?你需要一个临时变量吗?
      • @WizardOfOdds:是的,sed 不做算术。
      【解决方案4】:

      怎么样:

      $ echo "eh oh   37" | awk -F'[ \t]' '{$NF = $NF - 1;} 1'
      eh oh   36
      

      【讨论】:

      • 很好:这里的想法是我们不再将空格跨度视为分隔符,而是将每个空格字符视为分隔符。请注意,这将用 OFS(空格)替换输入行中的任何选项卡。这可能是不受欢迎的。
      【解决方案5】:

      解决方案不会保留小数位数,因此如果数字为 10,则结果为 9,即使您想要 09。

      我没有写尽可能短的代码,它应该保持可读性

      这里我使用 RLENGTH 构造 printf 模式,所以它变成 %02d(2 是匹配模式的长度)

      $ echo "eh oh    10      aaa     22    bb" |
          awk '{n = substr($0, match($0, /[0-9]+/), RLENGTH)-1 ; 
                nn=sprintf("%0" RLENGTH "d", n)
                sub(/[0-9]+/, nn);
                print 
               }'
      eh oh    09      aaa     22    bb
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-19
        • 1970-01-01
        • 1970-01-01
        • 2012-12-09
        • 1970-01-01
        • 2020-10-29
        • 2012-11-14
        • 2011-09-16
        相关资源
        最近更新 更多