使用正则表达式替换或追加文件中的行 | Linux Shell脚本

4

我正在尝试查找适当的命令来执行类似以下文件的操作:(即.cshrc文件)

setenv BLAH foo

我需要一个命令来替换检测到字符串BLAH的行,并将整行替换为以下内容:
setenv BLAH newfoo 

如果文件中不存在BLAH,则将其追加到文件中。
我尝试使用sed进行操作,但这并没有达到我的目的。
sed 's/^.*?BLAH.*/setenv BLAH newfoo/g' text.txt > text.tmp && mv text.tmp text.txt

我也尝试过使用 awk,但好像无法完全按照我的要求运行该命令。

awk -v s="setenv BLAH newfoo" '/^BLAH/{f=1;$0=s}7;END{if(!f)print s}' text.txt > text.tmp && mv text.tmp text.txt

如果您有任何关于如何实现这一点的想法,那就太棒了。

更新

我正在尝试使此脚本按预期工作。

脚本

ARG1="$1"
sed -i "/BLAH/s/^.*\$/setenv ${ENV_KEY} ${ENV_VAL}/g" file.txt
# If the BLAH keyword isnt there, append the file then.
grep -v -q "BLAH" file.txt && echo "setenv BLAH ${ARG1}" >> file.txt

file.txt (默认值)

setenv BLAH randomstring

脚本用法

script.sh newvalue

file.txt (结果)

setenv BLAH newvalue

目前该脚本似乎每次都在文件末尾添加内容。我也尝试了一些其他的建议,但无法将 arg1 值传递给 sed 字符串。

4个回答

6

介绍

这可以通过 脚本来完成:

sed  '/BLAH/{s/ \w+$/ newfoo/;h};${x;/BLAH/ba;x;s/$/\nsetenv BLAH newfoo/;x;:a;x}'

解释

sed -e '
    /^\(\|.*\W\)BLAH\(\W.*\|\)$/{  # lines matching word BLAH
      s/ \w\+$/ newfoo/; # replace last word by "newfoo"
      h;         # Store for not adding them at end of file 
    };
    ${           # On last line...
      x;         # Swap stored line with current line
      /BLAH/ba;  # if match, branch to label a:
      x;         # Swap back
      s/$/\nsetenv BLAH newfoo/; # Replace end of line with newline...
      x;         # Swap again
    :a;          # Label a:
      x          # Swap back
}'

您可以使用 即时编辑 开关:

sed -e'/BLAH/{h};${x;/BLAH/ba;x;s/$/\nsetenv BLAH newfoo/;x;:a;x}' -i text.txt

要更准确地搜索,需要查找分隔的单词BLAH
sed -e '
    /^\(.*\W\|\)BLAH\(\W.*\|\)$/{h}; # Store lines matching word BLAH
    ${           # On last line...
      x;         # Swap stored line with current line
      /./ba;  # if match, branch to label a:
      x;         # Swap back
      s/$/\nsetenv BLAH newfoo/; # Replace end of line with newline...
      x;         # Swap again
    :a;          # Label a:
      x          # Swap back
    }'  <(echo BLAH=foo;seq 1 4)
BLAH=foo
1
2
3
4

sed -e '
    /^\(.*\W\|\)BLAH\(\W.*\|\)$/{h}; # Store lines matching word BLAH
    ${           # On last line...
      x;         # Swap stored line with current line
      /./ba;  # if match, branch to label a:
      x;         # Swap back
      s/$/\nsetenv BLAH newfoo/; # Replace end of line with newline...
      x;         # Swap again
    :a;          # Label a:
      x          # Swap back
    }'  <(echo BLAHBLAH=foo;seq 1 4)
BLAHBLAH=foo
1
2
3
4
setenv BLAH newfoo

你可能会看到第二个BLAH被替换成了.。这是因为如果第一个没有被找到,交换空间就是空的。所以如果至少有一个字符,这意味着第一个确实出现了。

清洁和脚本化

有一种方法可以使此过程更加易于脚本化:

sedcmd='/^\(.*\W\|\)%s\(\W.*\|\)$/{s/ \w\+$/ %s/;h};'
sedcmd+='${x;/./ba;x;s/$/\\nsetenv %s %s/;x;:a;x}'
varnam=BLAH
varcnt=foo
filnam=/tmp/file.txt
printf -v sedcmd "$sedcmd" ${varnam} ${varcnt} ${varnam} ${varcnt}
sed -e "$sedcmd" -i "$filnam"

您可以在最后一行删除-i开关...

您可以通过运行以下命令来尝试:

sed -e "$sedcmd" <(echo setenv BLAH oldfoo;seq 1 4) 
setenv BLAH foo
1
2
3
4

sed -e "$sedcmd" <(echo setenv BLAHBLAH oldfoo;seq 1 4) 
setenv BLAHBLAH oldfoo
1
2
3
4
setenv BLAH foo

这个完美地运行了,只需附加文件交换并且就好了。谢谢。 - Jaison Brooks
我该如何将参数包含在这里?newfoo=$1 - Jaison Brooks
只需像写代码一样,您可以在必要时使用八进制值,但不包括 $: s/$/\nBLAH=newfoo\nnewfoo=$1\ndir=\o57tmp\o57/; - F. Hauri - Give Up GitHub
我尝试了类似这样的东西,但是出现了错误。sed -e "/^\(.*\W\|\)BLAH\(\W.*\|\)$/{h};${x;/./ba;x;s/$/\nexport BLAH=FOO\nFOO=$1/;x;:a;x}" -i $FILE - Jaison Brooks
1
是的,如果您使用双引号(")而不是单引号('),则还必须转义 $ 符号和反斜杠。:sed -e "/^\\(.*\\W\\|\\)BLAH\\(\\W.*\\|\\)\$/{h};\${x;/./ba;x;s/$/\\nsetenv BLAH newfoo=\$1/;x;:a;x}" <(echo BLAH=foo;seq 1 4) - F. Hauri - Give Up GitHub
显示剩余4条评论

1
这可能适用于您(GNU sed):
sed '/blah/h;//s/foo/newfoo/;$!b;G;/blah/!s/.$/&setenv blah newfoo/;t;P;d' file

如果找到blah,则将该行存储在保留空间(HS)中,并将foo替换为newfoo。如果未到达文件末尾,则打印每一行。到达文件末尾时,将HS附加到模式空间并检查blah。如果未找到,则附加setenv blah newfoo并打印,否则打印最后一行并删除其余部分。

感谢您的回答。 - Jaison Brooks

1
这个 awk 命令应该适用于两种情况:
awk -v kw='BLAH' '$2 == kw{$3="newfoo"; seen=1} 1;
      END{if (!seen) print "setenv " kw " newfoo"}' file

您可以使用命令行选项-v kw=...传递任何其他关键字进行搜索。

END块仅在文件中未找到给定的关键字时执行。


我尝试使用这个,但它只替换了值,而没有替换整行。 - Jaison Brooks
END{if (!seen) print "setenv " kw " $HOME"}' $FILE > test.tmp && mv test.tmp $FILE``` 将上述代码翻译成中文。 - Jaison Brooks
如果你正在使用 setenv BLAH foo 命令将其修改为 setenv BLAH newfoo,那么你只是将第三个单词从 foo 改为了 newfoo - anubhava
那是真的。对此感到抱歉。不过,如果文本值为BLAH=asdfasdf并将该行替换为export BLAH=newfoo,这种方法可行吗? - Jaison Brooks
你能展示一个仅更新值的例子吗?我一直得到整行的输出。awk -v kw='BLAH' '$2 == kw{$3=newfoo; seen=1} 1; END{if (!seen) print "setenv " kw " newfoo"}' $FILE > .tmpf && mv .tmpf $FILE 输出 setenv BLAH setenv BLAH newfoo - Jaison Brooks

1

如果您在使用sed后快速通过grep查找并附加文本,可能会让sed变得更容易。

例如:

ENV_KEY=BLAH
ENV_VAL=foo
sed -i "/BLAH/s/^.*\$/setenv ${ENV_KEY} ${ENV_VAL}/g" text.tmp
! grep -q "${ENV_KEY}" text.tmp && echo "setenv ${ENV_KEY} ${ENV_VAL}" >> text.tmp

这告诉sed搜索字符串BLAH,如果找到则替换整行^.*$-i选项告诉sed直接在文件中更新。


我应该期望grep每次都会附加行吗?似乎每次它都会添加新行,即使已经存在一行。两者都基于val进行更新。我需要有一个与我实际运行sed的文件不同的临时文件吗? - Jaison Brooks
看看更新的问题。我使用了您的建议,但正在努力弄清楚如何使其按您详细说明的方式工作。 - Jaison Brooks
是的,我的错 - 我放了一个 grep -v,它不能与 -q 选项一起使用。你只需要在 shell 中否定查询,加上一个 !,我已经更新了我的答案。 - user4401178

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接