使用Sed替换包含字符串的整行

我有一个文本文件,其中有一行

sometext sometext sometext TEXT_TO_BE_REPLACED sometext sometext sometext

我需要将上面的整行替换为

This line is removed by the admin.

搜索关键字是TEXT_TO_BE_REPLACED

我需要为此写一个shell脚本。如何使用sed实现这一点?

677439 次浏览

你需要在前后使用通配符(.*)来替换整行:

sed 's/.*TEXT_TO_BE_REPLACED.*/This line is removed by the admin./'

你可以使用改变命令替换整行代码,并使用-i标志进行相应的修改。例如,使用GNU sed:

sed -i '/TEXT_TO_BE_REPLACED/c\This line is removed by the admin.' /tmp/foo

这和上面那个很相似。

sed 's/[A-Za-z0-9]*TEXT_TO_BE_REPLACED.[A-Za-z0-9]*/This line is removed by the admin./'

这个公认的答案并不适合我,原因如下:

  • 我的sed版本不喜欢扩展长度为零的-i
  • c\命令的语法很奇怪,我无法让它工作
  • 我没有意识到我的一些问题来自于未转义的斜杠

以下是我提出的解决方案,我认为它适用于大多数情况:

function escape_slashes {
sed 's/\//\\\//g'
}


function change_line {
local OLD_LINE_PATTERN=$1; shift
local NEW_LINE=$1; shift
local FILE=$1


local NEW=$(echo "${NEW_LINE}" | escape_slashes)
# FIX: No space after the option i.
sed -i.bak '/'"${OLD_LINE_PATTERN}"'/s/.*/'"${NEW}"'/' "${FILE}"
mv "${FILE}.bak" /tmp/
}

因此本例使用提出了修正问题的方法:

change_line "TEXT_TO_BE_REPLACED" "This line is removed by the admin." yourFile

我经常使用regex从文件中提取数据,我只是用它来替换文字引用\"//什么都没有:-)

cat file.csv | egrep '^\"([0-9]{1,3}\.[0-9]{1,3}\.)' | sed  s/\"//g  | cut -d, -f1 > list.txt

在我的makefile中,我使用这个:

@sed -i '/.*Revision:.*/c\'"`svn info -R main.cpp | awk '/^Rev/'`"'' README.md

忘记-i实际上改变了文件中的文本…所以如果你定义为“修正”的模式;会变,你也会换花样。

示例输出:

abc项目由John Doe撰写

修改:1190

所以如果你设置模式"修订:1190"这显然和你定义的不一样。只有……

以上答案:

sed -i '/TEXT_TO_BE_REPLACED/c\This line is removed by the admin.' /tmp/foo

如果替换字符串/行不是变量,工作正常。

问题是在Redhat 5上,c之后的\转义了$。双\\也不能工作(至少在Redhat 5上)。

通过hit和trial,我发现在c之后的\是多余的,如果你的替换字符串/行只有一行。所以我没有在c之后使用\,而是使用一个变量作为单个替换行,它是joy。

代码看起来像这样:

sed -i "/TEXT_TO_BE_REPLACED/c $REPLACEMENT_TEXT_STRING" /tmp/foo

注意使用双引号而不是单引号。

bash-4.1$ new_db_host="DB_HOSTNAME=good replaced with 122.334.567.90"
bash-4.1$
bash-4.1$ sed -i "/DB_HOST/c $new_db_host" test4sed
vim test4sed
'
'
'
DB_HOSTNAME=good replaced with 122.334.567.90
'

它运行正常

到目前为止提供的所有答案都假设您了解要替换的文本,这是有意义的,因为这是OP所要求的。我提供的答案假设您对要替换的文本一无所知,并且文件中可能有与您不希望被替换的内容相同或类似的单独行。此外,我假设您知道要替换的行号。

下面的例子演示了如何通过特定的行号删除或更改文本:

# replace line 17 with some replacement text and make changes in file (-i switch)
# the "-i" switch indicates that we want to change the file. Leave it out if you'd
#   just like to see the potential changes output to the terminal window.
# "17s" indicates that we're searching line 17
# ".*" indicates that we want to change the text of the entire line
# "REPLACEMENT-TEXT" is the new text to put on that line
# "PATH-TO-FILE" tells us what file to operate on
sed -i '17s/.*/REPLACEMENT-TEXT/' PATH-TO-FILE


# replace specific text on line 3
sed -i '3s/TEXT-TO-REPLACE/REPLACEMENT-TEXT/'
cat find_replace | while read pattern replacement ; do
sed -i "/${pattern}/c ${replacement}" file
done

Find_replace文件包含2列,c1为匹配模式,c2为替换,sed循环替换包含变量1的模式之一的每一行

用于配置文件的操作

我受到skensell回答的启发想出了这个解决方案

configLine [searchPattern] [replaceLine] [filePath]

它将:

  • 如果不存在,则创建该文件
  • 替换searchPattern匹配的整行(所有行)
  • 如果没有找到pattern,则在文件末尾添加replaceLine

功能:

function configLine {
local OLD_LINE_PATTERN=$1; shift
local NEW_LINE=$1; shift
local FILE=$1
local NEW=$(echo "${NEW_LINE}" | sed 's/\//\\\//g')
touch "${FILE}"
sed -i '/'"${OLD_LINE_PATTERN}"'/{s/.*/'"${NEW}"'/;h};${x;/./{x;q100};x}' "${FILE}"
if [[ $? -ne 100 ]] && [[ ${NEW_LINE} != '' ]]
then
echo "${NEW_LINE}" >> "${FILE}"
fi
}

疯狂的退出状态魔术来自https://stackoverflow.com/a/12145797/1262663

下面的命令对我有用。哪个是处理变量的

sed -i "/\<$E\>/c $D" "$B"

要做到这一点,不依赖任何GNUisms,如-i不带参数或c不带换行符:

sed '/TEXT_TO_BE_REPLACED/c\
This line is removed by the admin.
' infile > tmpfile && mv tmpfile infile

在这种(POSIX兼容的)命令形式中

c\
text

text可以由一行或多行组成,应该成为替换的一部分的换行符必须转义:

c\
line1\
line2
s/x/y/

其中s/x/y/是一个新的sed命令后,模式空间已替换为两行

line1
line2

将包含指定字符串的整行替换为这一行的内容

文本文件:

Row: 0 last_time_contacted=0, display_name=Mozart, _id=100, phonebook_bucket_alt=2
Row: 1 last_time_contacted=0, display_name=Bach, _id=101, phonebook_bucket_alt=2

单一的字符串:

$ sed 's/.* display_name=\([[:alpha:]]\+\).*/\1/'
output:
100
101

由空格分隔的多个字符串:

$ sed 's/.* display_name=\([[:alpha:]]\+\).* _id=\([[:digit:]]\+\).*/\1 \2/'
output:
Mozart 100
Bach 101

调整regex以满足您的需要

[:alpha]和[:digit:] 是字符类和括号表达式 < / p >

这招对我很管用:

sed -i <extension> 's/.*<Line to be replaced>.*/<New line to be added>/'

一个例子是:

sed -i .bak -e '7s/.*version.*/      version = "4.33.0"/'
  • -i:替换后备份文件的扩展名。在本例中,它是.bak
  • -e: sed脚本。在本例中,它是'7s/.*version.*/ version = "4.33.0"/'。如果你想使用sed文件,使用-f标志
  • s:要替换的文件中的行号。在本例中,它是7s,即第7行。

请注意:

如果你想用sed进行递归查找和替换,那么你可以将grep放到命令的开头:

grep -rl --exclude-dir=<directory-to-exclude> --include=\*<Files to include> "<Line to be replaced>" ./ | sed -i <extension> 's/.*<Line to be replaced>.*/<New line to be added>/'

问题要求使用sed解决方案,但如果这不是一个困难的要求,那么还有另一个可能是更明智的选择。

公认的答案是sed -i,并将其描述为就地替换文件,但-i并没有真正做到这一点,而是做了与sed pattern file > tmp; mv tmp file相当的事情,保留了所有权和模式。这在很多情况下并不理想。一般来说,我不建议以非交互式的方式运行sed -i作为自动过程的一部分——这就像设置一个引信长度未知的炸弹。它迟早会在某人身上爆炸。

要真正“就地”编辑文件;并将匹配模式的行替换为使用实际文本编辑器会更好的其他内容。这是使用标准文本编辑器ed来完成的。

printf '%s\n' '/TEXT_TO_BE_REPLACED/' d i 'This line is removed by the admin' . w q | \
ed -s /tmp/foo > /dev/null

注意,这只替换了第一个匹配行,这是问题所暗示的。这与大多数其他答案有本质区别。

除了这个缺点,使用ed比使用sed还有一些优点:

  • 您可以用一行或多行替换匹配,而不需要任何额外的工作。
  • 替换文本可以任意复杂,而不需要任何转义来保护它。
  • 最重要的是,原始文件将被打开、修改和保存。没有复制。

它是如何工作的

工作原理:

  • printf将使用它的第一个参数作为格式字符串,并使用该格式打印它的每个其他参数,这实际上意味着printf的每个参数都成为一行输出,这些输出都被发送到stdin的ed
  • 第一行是一个正则表达式模式匹配,它导致ed移动“当前行”的概念;转发到匹配的第一行(如果不匹配,则当前行被设置为文件的最后一行)。
  • 下一个是d命令,它指示ed删除整个当前行。
  • 之后是i命令,它将ed置于插入模式;
  • 在此之后,所有随后输入的行都被写入当前行(如果有任何嵌入换行符,则写入其他行)。这意味着你可以在这里展开一个包含多行的变量(例如"$foo"),它将插入所有这些行。
  • ed看到由.组成的行时,插入模式结束
  • w命令将文件的内容写入磁盘,并且
  • q命令退出。
  • ed命令被赋予-s开关,将其置于静默模式,以便在运行时不回显任何信息,
  • 要编辑的文件是作为ed的参数给出的,
  • 最后,丢弃stdout以防止打印匹配正则表达式的行。

一些类unix系统可能(不恰当地)在发布时没有安装ed,但仍然可能附带ex;如果是这样,你可以简单地用它来代替。如果有vim但没有exed,可以使用vim -e代替。如果你只有标准的vi而没有exed,向你的系统管理员投诉。