我需要将以下行添加到配置文件的末尾:
include "/configs/projectname.conf"
到一个名为 lighttpd.conf
的文件
我正在考虑使用 sed
来执行此操作,但我不知道如何操作。
如果该行不存在,我将如何仅插入它?
crudini
可能是一个不错的选择(但不适用于 lighthttpd)
保持简单:)
grep + echo 应该足够了:
grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
-q 安静
-x 匹配整行
-F 模式是一个纯字符串
https://linux.die.net/man/1/grep
编辑:合并@cerin 和@thijs-wouters 建议。
尝试这个:
grep -q '^option' file && sed -i 's/^option.*/option=value/' file || echo 'option=value' >> file
这将是一个干净、可读和可重用的解决方案,使用 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 解释为选项。
使用 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*=\s*).*/: 忽略行首带有 # 的注释代码。 \?表示«可选»。注释将被删除,因为它在 \(...\) 中的复制部分之外。 \s* 表示«任意数量的空格»(空格,制表符)。空格会被复制,因为它们在 \(...\) 内,因此您不会丢失格式。
/^\(option=\).*/{…}:如果匹配一行/…/,则执行下一条命令。要执行的命令不是单个命令,而是一个块 {…}。
s//…/:搜索和替换。由于搜索词为空 //,它适用于最后一个匹配项,即 /^\(option=\).*/。
s//\1value/:将最后一个匹配替换为 (...) 中的所有内容,由 \1 和 textvalue` 引用
:a;n;ba;q: 设置标签a,然后读取下一行n,然后分支b(或转到)回到标签a,这意味着:读取所有行直到文件末尾,所以在第一个匹配之后,只需获取以下所有行,无需进一步处理。然后 q 退出,因此忽略其他所有内容。
$aoption=value:在文件 $ 的末尾,附加文本 option=value
关于 sed
的更多信息和命令概述在我的博客上:
https://marc.wäckerlin.ch/computer/stream-editor-sed-overview-and-reference
!
)来转义 /
字符:sed -i -e '/^\(option=\).*/{s!!\1value!;:a;n;ba;q}' -e '$aoption=value' filename
sed 's,from,to,g'
,但要允许任意名称值对,例如来自函数调用的参数,您仍然需要对它们进行转义。
filename
或 $3
不能为空
如果写入受保护的文件,@drAlberT 和 @rubo77 的答案可能对您不起作用,因为无法 sudo >>
。那么,similarly simple solution 将使用 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"
这是一个 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
sed
多年,但主要是 s
命令。 hold
和 pattern
空间交换超出了我的范围。
如果有一天,其他人必须将此代码作为“遗留代码”处理,那么如果您编写一个不那么公开的代码,那么该人将不胜感激,例如
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"
“正确转义”意味着放置一个与您的条目匹配的正则表达式,即从您的实际条目中转义所有正则表达式控件,即在 ^$/*?+() 前面放置一个反斜杠。
这可能会在文件的最后一行失败,或者如果没有悬空的换行符,我不确定,但这可以通过一些漂亮的分支来处理......
sed -i.bak -e '$a\' -e "$NEW_IP\t\t$HOST.domain\t$HOST" -e "/.*$HOST/d" /etc/hosts
sed -i -e '/<entry>/d; $a <entry>'
d
命令将重新启动 sed 程序,从而跳过 a
追加.
a
必须在 d
elete: sed -i -e '$a<entry>' -e '/<entry>/d'
之前执行 append。这与您的原始答案几乎相同。
这是一个单行 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
最后,如果您还想保留内联注释,您可以使用 catch 组来实现。例如,如果 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 的单行:
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] 是您的输入 file
。它在 END
块的 for
循环中打开和写入。在 END
块中打开 file
以进行输出,无需使用 sponge
等实用程序或写入临时文件,然后 mv
将临时文件写入 file
。
数组 a[]
的两个赋值将所有输出行累积到 a
。如果主 awk 循环在 file
中找不到 option
,则 if(!f)a[++n]=s
会附加新的 option=value
。
为了便于阅读,我添加了一些空格(不是很多),但在整个 awk 程序中确实只需要一个空格,即 print
之后的空格。如果 file
包含 # comments
,它们将被保留。
使用 awk
awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file file
file file
,因为它对同一个文件进行了两次传递。 NR==FNR
在第一遍是真的,但不是第二遍。这是一个常见的 awk 习语。
这是一个 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
或者
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
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
这是一个 awk 单行代码:
awk -v s="option=value" '/^option/{f=1;$0=s}7;END{if(!f)print s}' file
这不会对文件进行就地更改,但是您可以:
awk '...' file > tmpfile && mv tmpfile file
使用 sed
,您可以说:
sed -e '/option=/{s/.*/option=value/;:a;n;:ba;q}' -e 'aoption=value' filename
如果参数存在,这将替换该参数,否则会将其添加到文件的底部。
如果要就地编辑文件,请使用 -i
选项:
sed -i -e '/option=/{s/.*/option=value/;:a;n;:ba;q}' -e 'aoption=value' filename
sed -i '1 h
1 !H
$ {
x
s/^option.*/option=value/g
t
s/$/\
option=value/
}' /etc/fdm_monitor.conf
加载缓冲区中的所有文件,最后,更改所有出现的文件,如果没有更改,则添加到最后
使用 grep 的答案是错误的。您需要添加一个 -x 选项来匹配整行,否则像 #text to add
这样的行在寻找精确添加 text to add
时仍然会匹配。
所以正确的解决方案是这样的:
grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
使用 sed:它将在行尾插入。当然,您也可以像往常一样传递变量。
grep -qxF "port=9033" $light.conf
if [ $? -ne 0 ]; then
sed -i "$ a port=9033" $light.conf
else
echo "port=9033 already added"
fi
使用 oneliner sed
grep -qxF "port=9033" $lightconf || sed -i "$ a port=9033" $lightconf
使用 echo 可能无法在 root 下工作,但会像这样工作。但是,如果您希望这样做,它不会让您自动化操作,因为它可能会要求输入密码。
当我尝试从根目录为特定用户进行编辑时遇到问题。之前添加 $username
对我来说是一个修复。
grep -qxF "port=9033" light.conf
if [ $? -ne 0 ]; then
sudo -u $user_name echo "port=9033" >> light.conf
else
echo "already there"
fi
为了减少重复,我通过设置变量详细阐述了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 '...'
只是具有扩大变量范围的效果,而不需要 export
。 (见Setting an environment variable before a command in bash not working for second command in a pipe)
您可以使用此功能查找和搜索配置更改:
#!/bin/bash
#Find and Replace config values
find_and_replace_config () {
file=$1
var=$2
new_value=$3
awk -v var="$var" -v new_val="$new_value" 'BEGIN{FS=OFS="="}match($1, "^\\s*" var "\\s*") {$2=" " new_val}1' "$file" > output.tmp && sudo mv output.tmp $file
}
find_and_replace_config /etc/php5/apache2/php.ini max_execution_time 60
如果您想在 Linux 终端中使用 python 脚本运行此命令...
import os,sys
LINE = 'include '+ <insert_line_STRING>
FILE = <insert_file_path_STRING>
os.system('grep -qxF $"'+LINE+'" '+FILE+' || echo $"'+LINE+'" >> '+FILE)
和双引号让我陷入困境,但这有效。感谢大家
我需要编辑具有受限写入权限的文件,因此需要 sudo
。从 ghostdog74 的回答工作并使用临时文件:
awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file > /tmp/file
sudo mv /tmp/file file
不定期副业成功案例分享
grep -vq ...
ADDTEXT='include "/configs/projectname.conf"'
然后使用$ADDTEXT
怎么样?文件名foo.bar
也是如此。