我需要在配置文件的末尾添加以下一行:

include "/configs/projectname.conf"

到一个名为lighttpd.conf的文件

我正在研究使用sed来做到这一点,但我不知道如何。

我怎么能只插入它,如果行已经不存在?


当前回答

sed -i '1 h
1 !H
$ {
   x
   s/^option.*/option=value/g
   t
   s/$/\
option=value/
   }' /etc/fdm_monitor.conf

加载缓冲区中的所有文件,在结束时,更改所有发生的情况,如果没有发生变化,则添加到结束

其他回答

另一个sed解决方案是始终将它附加到最后一行,并删除一个预先存在的。

sed -e '$a\' -e '<your-entry>' -e "/<your-entry-properly-escaped>/d"

“适当转义”意味着放入一个匹配你的条目的正则表达式,即从你的实际条目中转义所有的正则表达式控件,即在^$/*?+()前面放一个反斜杠。

这可能会在文件的最后一行失败,或者如果没有悬空换行符,我不确定,但这可以通过一些漂亮的分支来处理…

使用sed,最简单的语法:

sed \
    -e '/^\(option=\).*/{s//\1value/;:a;n;ba;q}' \
    -e '$aoption=value' filename

如果参数存在,这将替换它,否则将把它添加到文件的底部。

如果您想就地编辑文件,请使用-i选项。


如果你想接受并保留空白,并且除了删除注释之外,如果行已经存在,但被注释掉了,那么写:

sed -i \
    -e '/^#\?\(\s*option\s*=\s*\).*/{s//\1value/;:a;n;ba;q}' \
    -e '$aoption=value' filename

请注意,选项和值都不能包含斜杠/,否则必须将其转义为\/。


要使用bash变量$option和$value,你可以这样写:

sed -i \
    -e '/^#\?\(\s*'${option//\//\\/}'\s*=\s*\).*/{s//\1'${value//\//\\/}'/;:a;n;ba;q}' \
    -e '$a'${option//\//\\/}'='${value//\//\\/} filename

bash表达式${option//\//\\/}引用斜杠,它将所有/替换为\/。

注意:刚刚陷入了一个问题。在bash中,你可以引用"${option//\//\\/}",但在busybox的sh中,这行不通,所以你应该避免使用引号,至少在非boure -shell中是这样。


所有这些组合在一个bash函数中:

# call option with parameters: $1=name $2=value $3=file
function option() {
    name=${1//\//\\/}
    value=${2//\//\\/}
    sed -i \
        -e '/^#\?\(\s*'"${name}"'\s*=\s*\).*/{s//\1'"${value}"'/;:a;n;ba;q}' \
        -e '$a'"${name}"'='"${value}" $3
}

解释:

/^\(option=\).*/: Match lines that start with option= and (.*) ignore everything after the =. The \(…\) encloses the part we will reuse as \1later. /^#?(\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 textvalue` :a;n;ba;q: Set label a, then read next line n, then branch b (or goto) back to label a, that means: read all lines up to the end of file, so after the first match, just fetch all following lines without further processing. Then q quit and therefore ignore everything else. $aoption=value: At the end of file $, append a the text option=value

关于sed和命令概述的更多信息请参阅我的博客:

https://marc.wackerlin.ch/computer/stream-editor-sed-overview-and-reference

为了减少重复,我通过设置变量详细阐述了kev的grep/sed解决方案。

设置第一行中的变量(提示:$_option将匹配一行中的所有内容,直到值[包括任何分隔符,如=或:])。

_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"\
        '

注意那个sh -c '…’只是有扩大变量范围的效果,而不需要导出。(参见在bash中的命令对管道中的第二个命令无效之前设置环境变量)

使用grep得到的答案是错误的。你需要添加一个-x选项来匹配整行,否则像#text to add这样的行在寻找要添加的文本时仍然会匹配。

所以正确的解决方案是这样的:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar

下面是一个awk实现

/^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"
}

使用

awk -f update.awk < /etc/fdm_monitor.conf > /etc/fdm_monitor.conf.tmp && \
   mv /etc/fdm_monitor.conf.tmp /etc/fdm_monitor.conf

or

awk -f update.awk < /etc/fdm_monitor.conf | sponge /etc/fdm_monitor.conf

编辑: 一句话:

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