我需要将以下行添加到配置文件的末尾:
include "/configs/projectname.conf"
将内容保存到名为lighttpd.conf
的文件中。
我正在研究如何使用sed
来做到这一点,但是我无法弄清楚如何操作。
如果该行不存在,我应该如何插入它?
保持简单 :)
grep + echo 应该足够了:
grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
-q
参数静默模式-x
参数匹配整行-F
参数使用普通字符串作为匹配模式echo
触发一个多行 heredoc 的 cat
到一个配置文件中。 - Eric L.ADDTEXT='include "/configs/projectname.conf"'
,然后使用$ADDTEXT
如何?
对于文件名foo.bar
也是一样。 - Jean Spector使用 grep
和 echo
实现干净、易读和可重用的解决方案,只有在文件中不存在该行时才添加一行:
LINE='include "/configs/projectname.conf"'
FILE='lighttpd.conf'
grep -qF -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE"
如果您需要匹配整行,请使用grep -xqF
。-s
。sudo grep -qF "$LINE" "$FILE" || echo "$LINE" | sudo tee -a "$FILE"
。该命令会在检查文件中是否存在指定行 $LINE
,如果不存在,则将该行写入文件 $FILE
中。注意,该操作需要管理员权限才能执行。 - nebffa试试这个:
grep -q '^option' file && sed -i 's/^option.*/option=value/' file || echo 'option=value' >> file
grep
命令在文件中查找以“option”开头的内容,如果查找成功,则执行sed
命令来替换以“option”开头的行为“option=value”,并将更改保存到原始文件中。如果没有查找到匹配项,echo
命令会将“option=value”追加写入文件末尾。 - Darwyn使用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中,这并不起作用,因此您应该避免引用,至少在非Bourne 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=\).*/
: 匹配以 option=
开头的行,并且 (.*
) 忽略等号后面的所有内容。\(
…\)
括起来的部分将被重复使用,并在后面用 \1
引用。#
开头的注释代码。\?
表示«可选项»。注释将被移除,因为它位于拷贝部分外的 \(
…\)
中。 \s*
表示«任意数量的空格»(空格、制表符)。空格被复制,因为它们在 \(
…\)
内,所以不会失去格式。/^\(option=\).*/{…}
: 如果匹配到一行 /…/
,则执行下一条命令。要执行的命令不是单个命令,而是一个块 {…}
。s//…/
: 查找并替换。由于搜索词是空的 //
,它应用于最后一次匹配,即 /^\(option=\).*/
。s//\1value/
: 用括号中的所有内容替换最后一次匹配,由 \1 引用,并添加文本值。
:a;n;ba;q
: 设置标签 a
,然后读取下一行 n
,然后分支 b
(或转到)返回标签 a
,也就是说:读取文件结尾之前的所有行,因此在第一次匹配后,只获取所有后续行而不进行进一步处理。然后 q
退出,从而忽略其他所有内容。
$aoption=value
: 在文件结尾 $
处附加文本 option=value
有关 sed
的更多信息和命令概述,请访问我的博客:
!
)来转义 /
字符:sed -i -e '/^\(option=\).*/{s!!\1value!;:a;n;ba;q}' -e '$aoption=value' filename
。 - jmlemetayersed 's,from,to,g'
,但为了允许任意名称值对,例如从函数调用中的参数,您仍然需要对它们进行转义。 - Marc Wäckerlin#option=value
,并且文件的最后一行是option=value
,那么我会得到两行相同信息。有什么方法可以处理这个问题吗? - Juan>>
。 因此,一个类似简单的解决方案是使用tee --append
(或在MacOS上使用tee -a
):LINE='include "/configs/projectname.conf"'
FILE=lighttpd.conf
grep -qF "$LINE" "$FILE" || echo "$LINE" | sudo tee --append "$FILE"
grep -qF "$LINE" "$FILE" || sudo sed -i "/line_where_you_want_to_insert_before/i $LINE" "$FILE"
- Juan这是一个基于sed
的版本:
sed -e '\|include "/configs/projectname.conf"|h; ${x;s/incl//;{g;t};a\' -e 'include "/configs/projectname.conf"' -e '}' file
string='include "/configs/projectname.conf"'
sed -e "\|$string|h; \${x;s|$string||;{g;t};a\\" -e "$string" -e "}" file
sed -i -e '\|session.*pam_mkhomedir.so|h; ${x;s/mkhomedir//;{g;tF};a\' -e 'session\trequired\tpam_mkhomedir.so umask=0022' -e '};:F;s/.*mkhomedir.*/session\trequired\tpam_mkhomedir.so umask=0022/g;' /etc/pam.d/common-session
- bgStack15sed
多年了,但大多数情况下只用 s
命令。hold
和 pattern
空间交换对我来说太复杂了。 - nortally如果有一天,其他人需要将这段代码视为“遗留代码”来处理,那么如果你写一个不那么深奥的代码,那个人会很感激。
grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi
另一个sed解决方案是始终将其附加在最后一行并删除预先存在的行。
sed -e '$a\' -e '<your-entry>' -e "/<your-entry-properly-escaped>/d"
"properly-escaped" 的意思是使用正则表达式匹配输入,即从实际输入中转义所有正则表达式控制字符,例如在 ^$/*?+() 前面加上反斜杠。
如果文件的最后一行或没有悬挂的换行符可能会导致此方法失败,但可以通过巧妙的分支处理来解决这个问题...
sed -i.bak -e '$a\' -e "$NEW_IP\t\t$HOST.domain\t$HOST" -e "/.*$HOST/d" /etc/hosts
гҖӮ - Noam Manossed -i -e '/<entry>/d; $a <entry>'
- Jérôme Pouillerd
命令将重新启动sed程序,从而跳过a
ppend,如果删除恰好发生在最后一行。 - Robin479a
ppend再执行d
elete:sed -i -e '$a<entry>' -e '/<entry>/d'
。这和你最初的回答几乎一致。 - Jérôme Pouiller这里有一个一行的sed命令可以在内部完成工作。请注意,它会在变量存在时保留其位置和缩进。这通常对于上下文很重要,例如当周围有注释或变量在缩进块中时。任何基于“删除-然后追加”范例的解决方案都会严重失败。
sed -i '/^[ \t]*option=/{h;s/=.*/=value/};${x;/^$/{s//option=value/;H};x}' test.conf
使用一个通用的变量/值对,您可以这样编写:
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
最后,如果您希望保留行内注释,可以使用捕获组来实现。例如,如果test.conf
包含以下内容:
a=123
# Here is "c":
c=999 # with its own comment and indent
b=234
d=567
var='c'
val='"yay"'
sed -i '/^[ \t]*'"$var"'=/{h;s/=[^#]*\(.*\)/='"$val"'\1/;s/'"$val"'#/'"$val"' #/};${x;/^$/{s//'"$var"'='"$val"'/;H};x}' test.conf
a=123
# Here is "c":
c="yay" # with its own comment and indent
b=234
d=567
awk -v s=option=value '/^option=/{$0=s;f=1} {a[++n]=$0} END{if(!f)a[++n]=s;for(i=1;i<=n;i++)print a[i]>ARGV[1]}' file
ARGV[1]是您的输入文件
。它在END
块的for
循环中被打开并写入。在END
块中为输出打开file
替代了像sponge
这样的实用工具或者先写入临时文件,然后再将临时文件mv
到file
。
数组a[]
的两个赋值将所有输出行累积到a
中。if(!f)a[++n]=s
如果主awk循环无法在file
中找到option
,则会添加新的option=value
。
我已经为可读性添加了一些空格(不多),但整个awk程序中您只需要一个空格,即在print
之后的空格。如果file
包含# comments
,它们将被保留。
crudini
可能是一个不错的选择(但还不能用于lighthttpd)。 - rubo77