用顺序索引替换字符串


10

有人可以建议一种优雅的方式来实现这一目标吗?

输入:

test  instant  ()

test  instant  ()

...
test  instant  ()    //total 1000 lines

输出应为:

test      instant1  ()

test      instant2  ()

test      instant1000()

空行在我的输入文件中,并且同一目录下有很多文件需要立即处理。

我试图用它替换同一个目录中的许多文件,但没有用。

for file in ./*; do perl -i -000pe 's/instance$& . ++$n/ge' "$file"; done

错误:

Substitution replacement not terminated at -e line 1.
Substitution replacement not terminated at -e line 1.

我也尝试过这个:

perl -i -pe 's/instant/$& . ++$n/ge' *.vs

它可以工作,但是索引只是不断地从一个文件增加到另一个文件。我想在更改为新文件时将其重置为1。有什么好的建议吗?

find . -type f -exec perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

可以,但是它替换了所有其他文件,不应替换。我更喜欢只替换文件*.txt


它们是否全部由空白行组成test instant ()
terdon

我将双倍行距放回去,它们通常是新用户不知道如何使用本网站标记的标志,这就是为什么terdon在适当缩进文件内容块以使其显示为文件内容的同时将其删除的原因。希望现在可以。
2014年

Answers:


14
perl -pe 's/instant/$& . ++$n/ge'

或使用GNU awk

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

要就地编辑文件,请将-i选项添加到perl

perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' ./*.vs

或递归地:

find . -name '*.vs' -type f -exec perl -pi -e '
  s/instant/$& . ++$n{$ARGV}/ge' {} +

说明

perl -pe 's/instant/$& . ++$n/ge'

-p是逐行处理输入,评估传递给-e每行的表达式并打印出来。对于每一行,我们(使用s/re/repl/flags运算符)instant代替本身($&)和变量的递增值++$n。的g标志为使全局(不只是一次)的取代,并e使得更换被解释为Perl代码È计价(不是一个固定的字符串)。

对于其中一个perl调用处理多个文件的就地编辑,我们希望$n在每个文件处重置。相反,我们使用$n{$ARGV}$ARGV当前正在处理的文件在哪里)。

awk一个值得一些解释。

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

我们正在使用GNU的功能awk来分离任意字符串(甚至是正则表达式)上的记录。使用-vRS=instant,我们将r̲ecord分隔符设置为instantRT是保存与匹配的变量RS,因此通常instant除外,最后一条记录将是空字符串。在上面的输入中,记录($0)和记录终止符(RT)是([$0|RT]):

[test  |instant][  ()
test  |instant][  ()
...
test  |instant][  ()    //total 1000 lines|]

因此,我们要做的就是在除第一个记录之外的每个记录的开头插入一个递增数字。

上面是我们要做的。对于第一条记录,n将为空。我们将ORS(输出信号分离器)设置为RT,以便awk 打印n $0 RT。它在第二个表达式(++n)上执行此操作,该条件始终求值为true(非零数字),因此将对$0 ORS每条记录执行默认操作(打印)。



5

sed确实不是最好的工具,您需要具有更好脚本功能的工具。这里有一些选择:

  • 佩尔

    perl -00pe 's/instant/$& . $./e' file 

    -p指的是应用给出的任何脚本后“打印每一行” 的意思-e。在-00为“段落模式”这样的记录(行)转由连续的换行定义的(\n)人物,这让它对付双行距正确。$&是最后匹配的模式,$.是输入文件的当前行号。使用ein s///e可以评估替换运算符中的表达式。

  • awk(这假定您的数据与显示的完全一样,带有三个空格分隔的字段)

    awk '{if(/./) print $1,$2 ++k,$3; else print}' file 

    在这里,仅当当前行不为空时,我们才递增k变量,在这种情况下,我们还将打印必要的信息。空行按原样打印。k/./

  • 各种贝壳

     n=0; while read -r a b c; do 
       if [ "$a" ] ; then 
          (( n++ ))
          printf "%s %s%s %s\n" "$a" "$b" "$n" "$c"
       else
          printf "%s %s %s\n" "$a" "$b" "$c"
       fi
     done < file 

    在这里,每个输入行被自动分割上的空格和字段被保存为$a$b$c。然后,在循环中,$c对于$a不为空的每一行,将其增加一个,并且其当前值将显示在第二个字段的旁边$b

注意:所有上述解决方案都假定文件中的所有行都是相同的格式。如果没有,@ Stephane的答案就是解决方法。


为了处理许多文件,并假设您要对当前目录中的所有文件执行此操作,可以使用以下命令:

for file in ./*; do perl -i -00pe 's/instant/$& . $./e' "$file"; done

小心:它假定不带空格的简单文件名,如果需要处理更复杂的东西,去(假设ksh93zshbash):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -00pe 's/instant/$& . $./e' "$file"
done

perl脚本有效。但是,如果行是双倍行距,则存在一个小问题。
user3342338

@ user3342338是的,因为我使用的是当前行号,所以该计数器将递增。正如我所说的,Stephane的方法更强大,这是一种非常幼稚的方法。如果您有空行或任何行与显示的内容不同,则这些都不起作用。
terdon

@ user3342338查看更新的答案。它们现在都应该适用于双倍间距文件。
terdon

很好的答案和替代方法的选择!谢谢
Madivad

0

如果您要解决此问题,sed可以使用类似(在中bash)的方法:

i=0
while read -r line; do
  sed "s/\(instant\)/\1${i}/" <<< "${line}"
  [[ ${line} =~ instant ]] && i=$(( i + 1 ))
done < file

或更便携的解决方案是:

i=0
while read -r line; do
  echo "${line}" | sed "s/\(instant\)/\1${i}/"
  if echo "${line}" | grep -q inst; then
    i=$(( i + 1 ))
  fi
done < file
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.