【问题标题】:Sed replacing only part of a longer match with a shorter replacement:Sed 仅用较短的替换替换较长匹配的一部分:
【发布时间】:2016-09-15 14:36:06
【问题描述】:

所以我正在测量 c 程序经过的总时间。通过这样做,我一直在运行这个 shell 脚本,它使用 sed 来替换在我的 c 程序中某行中间某处定义的常量(如下:N)的值。

#define N 10 // This constant will be incremented by shell program

在你告诉我应该使用一个变量并为使用它的函数计时之前,我必须在一次运行中从外部计时整个程序的执行时间(意味着不重新分配 N)。

我一直在 shell 脚本中使用以下内容来提供帮助:

tmp=$(sed "11s/[0-9][0-9]*/$INCREMENTINGVAR/" myprogram.c); printf "%s" "$tmp" > myprogram.c

用我的 INCREMENTINGVAR(替换)替换一个 3 位数字。但是,当替换长度为 2 位时,这对我来说似乎无法正常工作。 Sed 仅替换前两个字符并保留前一次运行的前 3 位而不删除它。

TESTS=0
while [ $TESTS -lt 3 ]
do
    echo "This is test: $TESTS"
    INCREMENTINGVAR=10

    while [ "$INCREMENTINGVAR" -lt 10 ] 
    do
        tmp=$(sed "11s/[0-9][0-9]*/$INCREMENTINGVAR/" myprogram.c); printf "%s" "$tmp" > myprogram.c
        rm -f myprog.c.bak
        echo "$INCREMENTINGVAR"
        gcc myprogram.c -o myprogram.out; ./myprogram.out
        INCREMENTINGVAR=$((INCREMENTINGVAR+5))
    done
    TESTS=$((TESTS+1))
done

有什么我应该做的吗?

编辑:添加了整个 shell 脚本;更改了 sed 的模式。

【问题讨论】:

  • 您可以使用-i 选项将输出写回原始文件,因此您不需要该变量。
  • 您的代码仅在有 3 位数字时替换。您的输入文件只有 2 位数字。所以它不应该取代任何东西。我不明白你说它留下了之前的第三位数字,因为没有第三位数字。
  • 如果你有一个 4 位数字,它将替换前 3 个,但保留第 4 个。

标签: regex bash shell replace sed


【解决方案1】:

您只是想用新值替换第 11 行的任何数字字符串吗?如果是这样,你会写:

sed -e "11s/[0-9][0-9]*/$INCREMENTINGVAR/"

查找一个或多个数字的序列,并将其替换为$INCREMENTINGVAR 中的当前值。这将从 9 翻转到 10,从 99 翻转到 100,从 999 翻转到 1000,等等。事实上,如果你想做的话,没有什么可以阻止你从 1 跳到 987,654。

使用sed 的GNU 和BSD (Mac OS X) 版本,您可以自动覆盖文件。可移植的方式(意思是,sed 的 GNU 和 BSD 变体都一样)是:

sed -i.bak -e "11s/[0-9][0-9]*/$INCREMENTINGVAR/" myprog.c
rm -f myprog.c.bak

这将创建一个备份文件(并删除它)。问题是 GNU sed 只需要 -i 和 BSD sed 需要 -i '' (两个参数)在没有备份的情况下进行原位更改。您可以决定与可移植性无关。


请注意,使用行号来标识必须更改的内容很微妙;琐碎的更改(新标题,更多评论)可能会更改行号。使用上下文搜索可能会更好:

sed -i.bak -e "/^#define N [0-9]/ s/[0-9][0-9]*/$INCREMENTINGVAR/" myprog.c
rm -f myprog.c.bak

这假定defineN 和数字之间有空格。如果你可能有空格或制表符,那么你可以写:

sed -i.bak -e "/^[[:space:]]*#[[:space:]]*define[[:space:]]\{1,\}N[[:space:]]*\{1,\}[0-9]/ s/[0-9][0-9]*/$INCREMENTINGVAR/" myprog.c
rm -f myprog.c.bak

#之前寻找可选的前导空格,#define之间的可选空格,defineN之间的强制空格(至少一个,可能很多),在N 和数字的第一个数字之间再次出现强制空白。但可能您的输入并没有那么草率,并且更简单的搜索模式(如第一个选项)足以满足您的需求。您还可以编写代码将异常格式化的 #define 行标准化为规范表示 - 但同样,您很可能不需要。

如果您在同一文件中的其他地方包含类似这样的内容:

#undef N
#define N 100000

您也必须担心匹配此行的模式。但是,很少有文件这样做;这在实践中不太可能成为问题(如果是,那么代码通常可能存在比这里可以处理的问题更多的问题)。一种可能性是将范围限制在前 30 行,假设第一个 #define N 123 在该范围内,而第二个不在。

sed -i.bak -e "1,30 { /^[[:space:]]*#[[:space:]]*define[[:space:]]\{1,\}N[[:space:]]*\{1,\}[0-9]/ s/[0-9][0-9]*/$INCREMENTINGVAR/; }" myprog.c
rm -f myprog.c.bak

还有多种其他技巧可以用来限制损害,但详细程度各不相同。例如:

sed -i.bak -e "1,/^[[:space:]]*#[[:space:]]*define[[:space:]]\{1,\}N[[:space:]]*\{1,\}[0-9]\{1,\}/ \
                s/^[[:space:]]*#[[:space:]]*define[[:space:]]\{1,\}N[[:space:]]*\{1,\}[0-9]\{1,\}/#define N $INCREMENTINGVAR/; }" myprog.c
rm -f myprog.c.bak

使用正则表达式通常是在具体性和冗长性之间进行判断——您可以让事情变得非常安全但非常难以阅读,或者您可能会冒一个很小的风险,即您的可读性更高的代码会匹配意外的内容。

【讨论】:

  • 感谢您的帮助。我记得使用这个解决方案。但是,我永远无法让文件被覆盖,因此,我的 c 编译器在第一次之后没有编译任何新内容。这就是为什么我上面有冗长而低效的代码。 ¯_(ツ)_/¯
  • 有些系统的原生sed 不支持-i 的任一变体,因此“临时编辑并随后重命名”的方法是合理的。
  • 非常感谢您的帮助。我希望其他人会阅读您的答案并解决他们的类似问题。 ?
猜你喜欢
  • 2015-05-12
  • 2012-08-15
  • 1970-01-01
  • 1970-01-01
  • 2016-09-23
  • 2018-04-18
  • 1970-01-01
  • 1970-01-01
  • 2010-11-07
相关资源
最近更新 更多