仅当文件尚不存在时才将行附加到文件中
Posted
技术标签:
【中文标题】仅当文件尚不存在时才将行附加到文件中【英文标题】:Appending a line to a file only if it does not already exist 【发布时间】:2011-04-03 04:34:57 【问题描述】:我需要将以下行添加到配置文件的末尾:
include "/configs/projectname.conf"
到一个名为lighttpd.conf
的文件
我正在考虑使用 sed
来执行此操作,但我不知道如何操作。
如果该行不存在,我将如何只插入它?
【问题讨论】:
如果您正在尝试编辑 ini 文件,工具crudini
可能是一个不错的选择(但不适用于 lighthttpd)
或许也可以看看sed,awk.grep - add a line to the end of a configuration section if the line doesn't already exist
【参考方案1】:
保持简单:)
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 的建议。
【讨论】:
我会将 -q 开关添加到 grep 以抑制输出:grep -vq ...
仅供参考,使用 -v 和 && 看起来并不奏效,而 ||没有 -v 的作品。
这仅适用于非常简单的行。否则,grep 会将行中的大多数非字母字符解释为模式,导致它无法检测文件中的行。
您需要添加 -x 选项以确保仅匹配整行,而不是仅匹配行的一部分。
要附加的文本在表达式中出现两次。在命令前面加上ADDTEXT='include "/configs/projectname.conf"'
然后使用$ADDTEXT
怎么样?文件名foo.bar
也是如此。【参考方案2】:
使用 awk
awk 'FNR==NR && /configs.*projectname\.conf/f=1;nextf==0;END if(!f) print "your line" ' file file
【讨论】:
是“文件文件”还是“文件”? 它是file file
,因为它对同一个文件进行了两次传递。 NR==FNR
在第一遍是真的,但不是第二遍。这是一个常见的 Awk 习惯用法。【参考方案3】:
这是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
很好,它对我帮助很大 +1。你能解释一下你的 sed 表达吗?
我很想看到这个解决方案的注释解释。我已经使用sed
多年,但主要是s
命令。 hold
和 pattern
空间交换超出了我的范围。【参考方案4】:
这是一个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;nextprintENDif(d!=1)print "option=value"' /etc/fdm_monitor.conf | sponge /etc/fdm_monitor.conf
【讨论】:
我需要一个单行,因为我将它作为来自 c 程序的系统调用发送。【参考方案5】:这是一个 awk 单行代码:
awk -v s="option=value" '/^option/f=1;$0=s7;ENDif(!f)print s' file
这不会对文件进行就地更改,但是您可以:
awk '...' file > tmpfile && mv tmpfile file
【讨论】:
【参考方案6】:试试这个:
grep -q '^option' file && sed -i 's/^option.*/option=value/' file || echo 'option=value' >> file
【讨论】:
解释见:explainshell Marc Wäckerlin 的回答更全面,可以在一个 sed 中完成所有要求。此外,使用 && 和 ||模棱两可(|| 可以应用于 grep 和 sed,如果 sed 返回 false 会怎样?【参考方案7】:使用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
【讨论】:
这似乎根本不起作用。它在每一行之后添加“option=value”。不知道它是如何得到两个赞成票的。你可以做 '$aoption=value' 但我似乎无法以上一场比赛为条件,所以无论如何它都会附加到末尾。【参考方案8】:sed -i '1 h
1 !H
$
x
s/^option.*/option=value/g
t
s/$/\
option=value/
' /etc/fdm_monitor.conf
加载缓冲区中的所有文件,最后,改变所有出现的地方,如果没有改变,则添加到最后
【讨论】:
【参考方案9】: 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
【讨论】:
【参考方案10】:我需要编辑具有受限写入权限的文件,因此需要sudo
。从 ghostdog74 的回答工作并使用临时文件:
awk 'FNR==NR && /configs.*projectname\.conf/f=1;nextf==0;END if(!f) print "your line" ' file > /tmp/file
sudo mv /tmp/file file
【讨论】:
这看起来不正确,当缺少配置行时,它将丢失文件中的其他行。【参考方案11】:另一种 sed 解决方案是始终将其附加到最后一行并删除预先存在的一个。
sed -e '$a\' -e '<your-entry>' -e "/<your-entry-properly-escaped>/d"
“正确转义”意味着放置一个与您的条目匹配的正则表达式,即从您的实际条目中转义所有正则表达式控件,即在 ^$/*?+() 前面放置一个反斜杠。
这可能会在文件的最后一行失败,或者如果没有悬空的换行符,我不确定,但这可以通过一些漂亮的分支来处理...
【讨论】:
漂亮的单行字,简短易读。非常适合更新 etc/hosts: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>'
@Jezz 我认为这将失败,如果条目已经在文件末尾,因为d
命令将重新启动 sed 程序,从而跳过 a
ppend,如果删除恰好在最后一行。
@Robin479 该死的,a
ppend 必须在d
elete 之前执行:sed -i -e '$a<entry>' -e '/<entry>/d'
。这与您的原始答案几乎相同。【参考方案12】:
这将是一个干净、可读和可重用的解决方案,使用 grep
和 echo
仅在文件不存在时向文件添加一行:
LINE='include "/configs/projectname.conf"'
FILE='lighttpd.conf'
grep -qF -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE"
如果你需要匹配整行使用grep -xqF
添加-s
以在文件不存在时忽略错误,只使用该行创建一个新文件。
【讨论】:
如果是需要root权限的文件:sudo grep -qF "$LINE" "$FILE" || echo "$LINE" | sudo tee -a "$FILE"
当行以-
开头时,这实际上不起作用。
@zsero:好点!我在 grep 命令中添加了--
,因此它不会再将以破折号开头的 $LINE 解释为选项。【参考方案13】:
作为一个 awk-only 单行:
awk -v s=option=value '/^option=/$0=s;f=1 a[++n]=$0 ENDif(!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
,它们将被保留。
【讨论】:
【参考方案14】:为了减少重复,我通过设置变量详细阐述了kev的grep/sed解决方案。
在第一行设置变量(提示:$_option
应匹配行中的所有内容,直到值 [包括任何分隔符,如 = 或 :])。
请注意,sh -c '...'
只是具有扩大变量范围的效果,而不需要 export
。 (见Setting an environment variable before a command in bash not working for second command in a pipe)
【讨论】:
【参考方案15】:如果写入受保护的文件,@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"
【讨论】:
你也可以使用 sed,这样你就可以在你想要的地方插入这一行。grep -qF "$LINE" "$FILE" || sudo sed -i "/line_where_you_want_to_insert_before/i $LINE" "$FILE"
【参考方案16】:
这是一个单行 sed,它内联完成工作。请注意,当变量存在时,它会在文件中保留变量的位置及其缩进。这对于上下文通常很重要,例如当周围有 cmets 或变量位于缩进块中时。任何基于“delete-then-append”范式的解决方案在这方面都失败了。
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
最后,如果您还想保留内联 cmets,您可以使用 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
【讨论】:
不错的解决方案,但对代码的解释将不胜感激【参考方案17】:使用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
later 重用的部分。
/^#?(\s*'"$option/////"'\s*=\s*).*/:忽略行首带有#
的注释代码. \?
表示«可选»。注释将被删除,因为它在 \(
...\)
中的复制部分之外。 \s*
表示«任意数量的空格»(空格,制表符)。空格会被复制,因为它们在 \(
...\)
内,所以不会丢失格式。
/^\(option=\).*/…
:如果匹配一行/…/
,则执行下一条命令。要执行的命令不是单个命令,而是一个块…
。
s//…/
:搜索替换。由于搜索词为空 //
,因此它适用于最后一个匹配项,即 /^\(option=\).*/
。
s//\1value/: Replace the last match with everything in
(…), referenced by
\1and the text
value`
:a;n;ba;q
:设置标签a
,然后读取下一行n
,然后分支b
(或转到)回到标签a
,这意味着:读取所有行直到文件末尾,所以在第一场比赛,只需获取以下所有行,无需进一步处理。然后q
退出,因此忽略其他所有内容。
$aoption=value
:在文件$
的末尾,追加a
文本option=value
有关sed
的更多信息和命令概述在我的博客上:
【讨论】:
对于像“选项值”这样的文件会怎样?当我将 = 替换为空格时,该函数修复了该选项,但文件的其余部分丢失了:( @jlanza,非常抱歉,我的代码中有错字。命令是«ba»,而不是«:ba»。我编辑了文本并修复了它。请重试。 您可以通过在替换命令中使用另一个字符来转义/
字符(如!
):sed -i -e '/^\(option=\).*/s!!\1value!;:a;n;ba;q' -e '$aoption=value' filename
是的,您可以为 sed 命令使用任何字符。事实上,我经常使用逗号:sed 's,from,to,g'
,但允许任意名称值对,例如来自函数调用的参数,您仍然需要转义它们。
值得一提的是文件filename
或$3
不能为空【参考方案18】:
如果有一天,其他人有将此代码作为“遗留代码”处理,那么如果您编写一个不那么公开的代码,那么该人将不胜感激,例如
grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi
【讨论】:
感谢您的评论格雷厄姆!是的,这是正常的 shell 语法。是的,任何称职的管理员都应该理解它。但是,它的工作原理是基于 shell 中表达式评估算法的副作用。所以你可以随心所欲地称呼它,除了直截了当——这是我最初的观点。 另见Why is testing "$?" to see if a command succeeded or not, an anti-pattern?【参考方案19】:您可以使用此功能查找和搜索配置更改:
#!/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" 'BEGINFS=OFS="="match($1, "^\\s*" var "\\s*") $2=" " new_val1' "$file" > output.tmp && sudo mv output.tmp $file
find_and_replace_config /etc/php5/apache2/php.ini max_execution_time 60
【讨论】:
【参考方案20】:使用 grep 的答案是错误的。您需要添加一个 -x 选项来匹配整行,否则像 #text to add
这样的行在寻找精确添加 text to add
时仍然会匹配。
所以正确的解决方案是这样的:
grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
【讨论】:
【参考方案21】:使用 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
【讨论】:
欢迎来到***!请在发布答案之前仔细阅读问题 - OP 询问如何使用 sed 插入【参考方案22】:如果您想在 Linux 终端中使用 python 脚本 运行此命令...
import os,sys
LINE = 'include '+ <insert_line_STRING>
FILE = <insert_file_path_STRING>
os.system('grep -qxF $"'+LINE+'" '+FILE+' || echo $"'+LINE+'" >> '+FILE)
$ 和双引号让我陷入了困境,但这有效。 谢谢大家
【讨论】:
以上是关于仅当文件尚不存在时才将行附加到文件中的主要内容,如果未能解决你的问题,请参考以下文章