bash 如果找到不匹配,如何在 sed 中添加一行
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20267910/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to add a line in sed if not match is found
提问by Evilmachine
I am using the following sed
command to replace some parameters in a config file:
我正在使用以下sed
命令替换配置文件中的一些参数:
sed -i 's/^option.*/option=value/g' /etc/fdm_monitor.conf
Now I have one problem. If the line does not exist, I want to add it to the bottom of the file.
现在我有一个问题。如果该行不存在,我想将其添加到文件底部。
I am calling this with a popen
out of a C program. I tried using awk
.
我用popen
一个 C 程序来调用它。我尝试使用awk
.
回答by kev
Try this:
尝试这个:
grep -q '^option' file && sed -i 's/^option.*/option=value/' file || echo 'option=value' >> file
回答by Marc W?ckerlin
Using sed, the simplest syntax:
使用 sed,最简单的语法:
sed \
-e '/^\(option=\).*/{s//value/;:a;n;ba;q}' \
-e '$aoption=value' filename
This would replace the parameter if it exists, else would add it to the bottom of the file.
如果参数存在,这将替换参数,否则会将其添加到文件的底部。
Use the -i
option if you want to edit the file in-place.
-i
如果要就地编辑文件,请使用该选项。
If you want to accept and keep white spaces, and in addition to remove the comment, if the line already exists, but is commented out, write:
如果要接受并保留空格,并且除了删除注释之外,如果该行已经存在,但被注释掉,请编写:
sed -i \
-e '/^#\?\(\s*option\s*=\s*\).*/{s//value/;:a;n;ba;q}' \
-e '$aoption=value' filename
Please note that neither option nor value must contain a slash /
, or you will have to escape it to \/
.
请注意,option 和 value 都不能包含斜杠/
,否则您必须将其转义为\/
。
To use bash-variables $option
and $value
, you could write:
要使用 bash 变量$option
and $value
,你可以这样写:
sed -i \
-e '/^#\?\(\s*'${option//\//\/}'\s*=\s*\).*/{s//'${value//\//\/}'/;:a;n;ba;q}' \
-e '$a'${option//\//\/}'='${value//\//\/} filename
The bash expression ${option//\//\\/}
quotes slashes, it replaces all /
with \/
.
bash 表达式${option//\//\\/}
引用斜杠,它/
用\/
.
Note:i Just trapped into a problem. In bash you may quote "${option//\//\\/}"
, but in the sh of busybox, this does not work, so you should avoid the quotes, at least in non-bourne-shells.
注意:我刚刚陷入了一个问题。在 bash 中,您可以引用"${option//\//\\/}"
,但在 busybox 的 sh 中,这不起作用,因此您应该避免使用引号,至少在非 bourne-shell 中。
All combined in a bash function:
所有这些都组合在一个 bash 函数中:
# call option with parameters: =name =value =file
function option() {
name=${1//\//\/}
value=${2//\//\/}
sed -i \
-e '/^#\?\(\s*'"${name}"'\s*=\s*\).*/{s//'"${value}"'/;:a;n;ba;q}' \
-e '$a'"${name}"'='"${value}"
}
Explanation:
解释:
/^\(option=\).*/
: Match lines that start withoption=
and (.*
) ignore everything after the=
. The\(
…\)
encloses the part we will reuse as\1
later.- /^#\?(\s*'"${option//////}"'\s*=\s*).*/: Ignore commented out code with
#
at the begin of line.\?
means ?optional?. The comment will be removed, because it is outside of the copied part in\(
…\)
.\s*
means ?any number of white spaces? (space, tabulator). White spaces are copied, since they are within\(
…\)
, so you do not lose formatting. /^\(option=\).*/{…}
: If matches a line/…/
, then execute the next command. Command to execute is not a single command, but a block{…}
.s//…/
: Search and replace. Since the search term is empty//
, it applies to the last match, which was/^\(option=\).*/
.s//\1value/: Replace the last match with everything in
(…), referenced by
\1and the text
value`:a;n;ba;q
: Set labela
, then read next linen
, then branchb
(or goto) back to labela
, that means: read all lines up to the end of file, so after the first match, just fetch all following lines without further processing. Thenq
quit and therefore ignore everything else.$aoption=value
: At the end of file$
, appenda
the textoption=value
/^\(option=\).*/
: 匹配以option=
and (.*
)开头的行忽略=
. 该\(
...\)
围住我们将重用的部分\1
之后。- /^#\?(\s*'"${option//////}"'\s*=\s*).*/: 忽略在行首注释掉的代码
#
。\?
意思是?可选?。注释将被删除,因为它位于\(
... 中复制的部分之外\)
。\s*
意味着?任意数量的空格?(空格,制表符)。空格被复制,因为它们在\(
...内\)
,所以你不会丢失格式。 /^\(option=\).*/{…}
: 如果匹配一行/…/
,则执行下一条命令。要执行的命令不是单个命令,而是一个块{…}
。s//…/
: 搜索和替换。由于搜索词为空//
,因此它适用于最后一个匹配项,即/^\(option=\).*/
。s//\1value/: Replace the last match with everything in
(…), referenced by
\1 值and the text
`:a;n;ba;q
:设置 labela
,然后读取下一行n
,然后分支b
(或转到)回到 labela
,这意味着:读取文件末尾的所有行,因此在第一次匹配后,只需获取所有后续行而无需进一步处理。然后q
退出,因此忽略其他一切。$aoption=value
: 在文件末尾$
,附加a
文本option=value
More information on sed
and a command overview is on my blog:
有关sed
命令概述的更多信息,请参见我的博客:
回答by stepse
As an awk-only one-liner:
作为仅 awk 的单行:
awk -v s=option=value '/^option=/{ sed -i '/^[ \t]*option=/{h;s/=.*/=value/};${x;/^$/{s//option=value/;H};x}' test.conf
=s;f=1} {a[++n]= var=c
val='12 34' # it handles spaces nicely btw
sed -i '/^[ \t]*'"$var"'=/{h;s/=.*/='"$val"'/};${x;/^$/{s//c='"$val"'/;H};x}' test.conf
} END{if(!f)a[++n]=s;for(i=1;i<=n;i++)print a[i]>ARGV[1]}' file
ARGV[1] is your input file
. It is opened and written to in the for
loop of theEND
block. Opening file
for output in the END
block replaces the need for utilities like sponge
or writing to a temporary file and then mv
ing the temporary file to file
.
ARGV[1] 是您的输入file
。它for
在END
块的循环中打开并写入。file
在END
块中打开输出取代了对诸如sponge
或写入临时文件之类的实用程序的需求,然后mv
将临时文件file
.
The two assignments to array a[]
accumulate all output lines into a
. if(!f)a[++n]=s
appends the new option=value
if the main awk loop couldn't find option
in file
.
对 array 的两个赋值将a[]
所有输出行累加到a
. if(!f)a[++n]=s
追加新option=value
,如果主要的awk循环找不到option
在file
。
I have added some spaces (not many) for readability, but you really need just one space in the whole awk program, the space after print
.
If file
includes # comments
they will be preserved.
为了便于阅读,我添加了一些空格(不是很多),但是在整个 awk 程序中您确实只需要一个空格,即print
. 如果file
包含,# comments
它们将被保留。
回答by MoonCactus
Here is a one-liner sed which does the job inline. Note that it preserves the location of the variable and its indentationin the file when it exists. This is often important for the context, like when there are comments around or when the variable is in an indented block. Any solution based on "delete-then-append" paradigm fails badly at this.
这是一个单行 sed,它执行内联工作。请注意,当变量存在时,它会保留变量的位置及其在文件中的缩进。这对于上下文通常很重要,例如当周围有注释或变量位于缩进块中时。任何基于“删除然后附加”范式的解决方案都在这方面失败了。
a=123
# Here is "c":
c=999 # with its own comment and indent
b=234
d=567
With a generic pair of variable/value you can write it this way:
使用一对通用变量/值,您可以这样编写:
var='c'
val='"yay"'
sed -i '/^[ \t]*'"$var"'=/{h;s/=[^#]*\(.*\)/='"$val"'/;s/'"$val"'#/'"$val"' #/};${x;/^$/{s//'"$var"'='"$val"'/;H};x}' test.conf
Finally, if you want also to keep inline comments, you can do it with a catch group. E.g. if test.conf
contains the following:
最后,如果您还想保留内联注释,可以使用 catch 组来实现。例如,如果test.conf
包含以下内容:
a=123
# Here is "c":
c="yay" # with its own comment and indent
b=234
d=567
Then running this
然后运行这个
sed -i 's/^option.*/option=value/g' /etc/fdm_monitor.conf
grep -q "option=value" /etc/fdm_monitor.conf || echo "option=value" >> /etc/fdm_monitor.conf
Produces that:
产生:
/^option *=/ {
print "option=value"; # print this instead of the original line
done=1; # set a flag, that the line was found
next # all done for this line
}
{print} # all other lines -> print them
END { # end of file
if(done != 1) # haven't found /option=/ -> add it at the end of output
print "option=value"
}
回答by Hyman
awk -f update.awk < /etc/fdm_monitor.conf > /etc/fdm_monitor.conf.tmp && \
mv /etc/fdm_monitor.conf.tmp /etc/fdm_monitor.conf
回答by rzymek
Here's an awk
implementation
这是一个awk
实现
awk -f update.awk < /etc/fdm_monitor.conf | sponge /etc/fdm_monitor.conf
Run it using
运行它使用
awk '/^option *=/ {print "option=value";d=1;next}{print}END{if(d!=1)print "option=value"}' /etc/fdm_monitor.conf | sponge /etc/fdm_monitor.conf
or
或者
awk -v s="option=value" '/^option/{f=1;awk '...' file > tmpfile && mv tmpfile file
=s}7;END{if(!f)print s}' file
EDIT: As a one-liner:
编辑:作为单行:
sed -e '/option=/{s/.*/option=value/;:a;n;:ba;q}' -e 'aoption=value' filename
回答by Kent
here is an awk one-liner:
这是一个 awk 单行:
sed -i -e '/option=/{s/.*/option=value/;:a;n;:ba;q}' -e 'aoption=value' filename
this doesn't do in-place change on the file, you can however :
这不会对文件进行就地更改,但是您可以:
sed -i '1 h
1 !H
$ {
x
s/^option.*/option=value/g
t
s/$/\
option=value/
}' /etc/fdm_monitor.conf
回答by devnull
Using sed
, you could say:
使用sed
,你可以说:
_file="/etc/ssmtp/ssmtp.conf" _option="mailhub=" _value="my.domain.tld" \ sh -c '\ grep -q "^$_option" "$_file" \ && sed -i "s/^$_option.*/$_option$_value/" "$_file" \ || echo "$_option$_value" >> "$_file"\ '
This would replace the parameter if it exists, else would add it to the bottom of the file.
如果参数存在,这将替换参数,否则会将其添加到文件的底部。
Use the -i
option if you want to edit the file in-place:
-i
如果要就地编辑文件,请使用该选项:
回答by NeronLeVelu
Load all the file in buffer, at the end, change all occurence and if no change occur, add to the end
加载缓冲区中的所有文件,最后,更改所有出现,如果没有发生更改,则添加到末尾
回答by Jonas Eberle
I elaborated on kev's grep/sed solution by setting variables in order to reduce duplication.
我通过设置变量以减少重复详细说明了 kev 的 grep/sed 解决方案。
Set the variables in the first line (hint: $_option
shall match everything on the line up until the value [including any seperator like = or :]).
在第一行设置变量(提示:$_option
应匹配行上的所有内容,直到值 [包括任何分隔符,如 = 或 :])。
Mind that the sh -c '...'
just has the effect of widening the scope of the variables without the need for an export
. (See Setting an environment variable before a command in bash not working for second command in a pipe)
请注意,sh -c '...'
只是具有扩大变量范围的效果,而无需export
. (请参阅在 bash 中的命令之前设置环境变量不适用于管道中的第二个命令)