ChatGPT解决这个技术问题 Extra ChatGPT

仅当文件尚不存在时才将行附加到文件中

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

include "/configs/projectname.conf"

到一个名为 lighttpd.conf 的文件

我正在考虑使用 sed 来执行此操作,但我不知道如何操作。

如果该行不存在,我将如何仅插入它?

如果您尝试编辑 ini 文件,工具 crudini 可能是一个不错的选择(但不适用于 lighthttpd)

a
anatoly techtonik

保持简单:)

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 也是如此。
k
kev

尝试这个:

grep -q '^option' file && sed -i 's/^option.*/option=value/' file || echo 'option=value' >> file

有关说明,请参阅:explainshell
Marc Wäckerlin 的回答更全面,可以在一个 sed 中完成所有要求。此外,使用 && 和 ||是模棱两可的(|| 可以应用于 grep 和 sed,如果 sed 返回 false 会发生什么?
r
rubo77

这将是一个干净、可读和可重用的解决方案,使用 grepecho 仅在文件不存在时才向文件添加一行:

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 解释为选项。
A
Alexis Wilke

使用 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


对于像“选项值”这样的文件,这将如何?当我将 = 替换为空格时,该函数修复了该选项,但文件的其余部分丢失了:(
@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 不能为空
a
adlaika

如果写入受保护的文件,@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"
D
Dennis Williamson

这是一个 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 命令。 holdpattern 空间交换超出了我的范围。
I
Igor Milla

如果有一天,其他人必须将此代码作为“遗留代码”处理,那么如果您编写一个不那么公开的代码,那么该人将不胜感激,例如

grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
  echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi

感谢您的评论格雷厄姆!是的,这是正常的 shell 语法。是的,任何称职的管理员都应该理解它。但是,它的工作原理是基于 shell 中表达式评估算法的副作用。所以你可以随心所欲地称呼它,除了直截了当——这是我最初的观点。
J
Jean Spector

另一种 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 追加.
@Robin479 该死,a必须在 delete: sed -i -e '$a<entry>' -e '/<entry>/d' 之前执行 append。这与您的原始答案几乎相同。
M
MoonCactus

这是一个单行 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

很好的解决方案,但对代码的解释将不胜感激
s
stepse

作为仅 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,它们将被保留。


g
ghostdog74

使用 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 习语。
r
rzymek

这是一个 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

我需要一个衬里,因为我将它作为来自 ac programm 的系统调用发送。
L
Lesmana
 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

K
Kent

这是一个 awk 单行代码:

 awk -v s="option=value" '/^option/{f=1;$0=s}7;END{if(!f)print s}' file

这不会对文件进行就地更改,但是您可以:

awk '...' file > tmpfile && mv tmpfile file

d
devnull

使用 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' 但我似乎不能以上一场比赛为条件,所以无论如何它只是附加到最后。
N
NeronLeVelu
sed -i '1 h
1 !H
$ {
   x
   s/^option.*/option=value/g
   t
   s/$/\
option=value/
   }' /etc/fdm_monitor.conf

加载缓冲区中的所有文件,最后,更改所有出现的文件,如果没有更改,则添加到最后


T
Thijs Wouters

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

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

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

R
Rakib Fiha

使用 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 插入
C
Community

为了减少重复,我通过设置变量详细阐述了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


G
Guray Celik

您可以使用此功能查找和搜索配置更改:

#!/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

M
M1NDF1V3D

如果您想在 Linux 终端中使用 python 脚本运行此命令...

import os,sys
LINE = 'include '+ <insert_line_STRING>
FILE = <insert_file_path_STRING>                                
os.system('grep -qxF $"'+LINE+'" '+FILE+' || echo $"'+LINE+'" >> '+FILE)

和双引号让我陷入困境,但这有效。感谢大家


w
webdevguy

我需要编辑具有受限写入权限的文件,因此需要 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

这看起来不正确,当缺少配置行时,它将丢失文件中的其他行。