在原地编辑的Sed

8
for term in `cat stopwords`; do sed -i 's/\<$term\>//g' spam.txt ;done

给定的停用词列表每行包含一个单词,而spam.txt是一个纯文本文件,我只需要替换停用词的精确匹配。但实际效果与我的预期不同......请注意,这两个文件中都有像doesn'tcouldn't这样的单词。

4个回答

8

您确定要在for循环中运行sed吗?我建议使用sed脚本文件。

TMPFILE=mktemp
for WORD in $(cat stopwords); do echo 's/'$WORD'//g' >> $TMPFILE; done
sed -f $TMPFILE spam.txt
rm -f $TMPFILE

谢谢指出来...我以为直接进行原地替换更高端。但这样更好,更优雅... - Tathagata
2
想知道这个答案中是否有错别字,我认为应该是sed -i -f $TMPFILE spam.txt-从而保持原作者的意图进行原地替换,只是更高效地使用脚本文件。 - Peter Mularien
我们可以使用 sed 生成 $TMPFILE: sed 's|^|s/|;s|$|//g|' stopwords > $TMPFILE。然后直接将其作为脚本使用: sed 's|^|s/|;s|$|//g|' stopwords | sed -i -f - spam.txt - aragaer
@aragaer:那基本上就是我在评论中复制的我的答案 - mschilli
@sh-lecram:抱歉,我没有注意到那个答案。但这个技巧太棒了,我现在经常使用它。 - aragaer
如果你真的想使用 tmpfile,我建议使用 trap 来确保它被删除。 - mschilli

8

很好,你应该在sed命令中使用“”而不是'。使用单引号 ' 告诉Shell不要替换$term。

这样写:

for term in `cat stopwords`; do sed -i "s/\<$term\>//g" spam.txt ;done

适用于:

# stopwords
couldn't

并且:

# spam.txt
foo <couldn't> bar

我的两分钱意见


3

@kerolasa的想法很有道理。

最重要的是,你的$term没有被扩展成变量。你可以将代码改写为

for term in `cat stopwords`; do sed -i "s/\<${term}\>//g" spam.txt ;done

但这是一项非常昂贵的操作,你正在运行每个在stopwords中的单词的sed。根据@kerolasa的想法制作sed脚本更有效,但这取决于情况,如果这是一个一次性的项目,那么你的解决方案将起作用。
除了... "像doesn't, couldn't这样的单词在两个文件中", 是的,然后呢?我不确定你在说什么,你期望/想要发生什么,为什么你认为它不会发生?改变引用方式会有所帮助。
最后,请注意,如果你的停用词列表包含空格,即'spanner in the works' ;-),则此解决方案可能会出现问题。
希望这可以帮到你。

我总是对bash中的引号感到困惑...怀疑替换没有正确工作。所以指出来了...并且停用词中没有空格。 - Tathagata

2

不要像Sami Kerola建议的那样使用临时文件,你也可以将脚本通过管道传输给sed,使用第二个sed实例从stopwords中创建它:

sed 's,.*,s/\\<&\\>//g,' stopwords | sed -i -f- spam.txt

请注意,我在第一个sed实例中使用了,而不是/作为分隔符,以避免在生成的表达式中引用每个作为分隔符的/。但这只是个人口味问题,当然你也可以使用's/.*/s\/\\<&\\>\/\/g/',如果你更喜欢的话。请保留HTML标签。

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