本文介绍了Bash - 搜索和替换操作,报告已更改的文件和行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个输入文件test.txt",如下所示 -

hostname=abc.com hostname=xyz.comdb-host=abc.com db-host=xyz.com

在每一行中,空格前的值是旧值,需要在名为test"的文件夹中递归替换空格后的新值.我可以使用下面的 shell 脚本来做到这一点.

#!/bin/bashIFS=$'
'对于 `cat test.txt` 中的 f做OLD=$(echo $f| cut -d ' ' -f 1)echo "旧 = $OLD"NEW=$(echo $f| cut -d ' ' -f 2)echo "新 = $NEW"查找测试类型 f |xargs sed -i.bak "s/$OLD/$NEW/g"完毕

sed"在 100 个文件中动态替换字符串.

是否有技巧或替代方法可以让我获得文件更改的报告,例如文件的绝对路径&更改的确切行?

PS - 我知道 sed 或流编辑器不支持开箱即用的此功能.我不想使用版本控制,因为这对这项任务来说太过分了.

解决方案

让我们从简单的脚本重写开始,让它在处理更广泛的替换值时更加健壮一点,但也更快:

>

#!/bin/bash# 为 sed 转义正则表达式和替换字符串escapeRegex() { sed 's/[^^]/[&]/g;s/^/\^/g' <<<"$1";}escapeSubst() { sed 's/[&/]/\&/g' <<<"$1";}while read -r old new;做find test -type f -exec sed "/$(escapeRegex "$old")/$(escapeSubst "$new")/g" -i '{}' ;完成 <test.txt

因此,我们在 test.txt 的行中循环遍历以空格分隔的字段对(oldnew)并运行标准sed 就地替换用 find 找到的所有文件.

与您的脚本非常相似,但我们正确读取了测试中的行.txt(无分词、路径名/变量扩展等),我们尽可能使用 Bash 内置函数(无需调用外部工具,如 catcut, xargs);我们转义sed元字符old/new 值中正确用作 sed 的正则表达式和替换表达式.

现在让我们添加 从 sed 记录::>

#!/bin/bash# 为 sed 转义正则表达式和替换字符串escapeRegex() { sed 's/[^^]/[&]/g;s/^/\^/g' <<<"$1";}escapeSubst() { sed 's/[&/]/\&/g' <<<"$1";}while read -r old new;做find test -type f -printf '
[%p]
' -exec sed "/$(escapeRegex "$old")/{Hs//$(escapeSubst "$new")/gHXs/
/-->/带/开发/标准输出X}" -i '{}' > >(tee -a change.log) ;完成 <test.txt

上面的 sed 脚本将每个 old 更改为 new,但它也会写入 old -->新的 行到/dev/stdout(特定于Bash),我们依次将其附加到change.log 文件.-printf 操作在 find 输出一个带有文件名的标题"行,为每个处理的文件.

这样,您的更改日志"将如下所示:

[file1]主机名=abc.com -->主机名=xyz.com[文件 2][文件1]db-host=abc.com -->db-host=xyz.com[文件 2]db-host=abc.com -->db-host=xyz.com

为了完整起见,快速浏览 sed 脚本.我们只对包含 old 值的行进行操作.对于每一个这样的行,我们将它存储到保持空间 (h),将其更改为 new,将该新值附加到保持空间(与换行符,H) 现在保存 oldnew.我们用模式空间 (x) 交换保持,所以我们可以运行 s 命令将其转换为 old -->新的.在使用 w 将其写入 stdout 之后,我们将 new 从保持状态移回模式空间,因此它被写入(就地)到处理的文件.

I have a input file "test.txt" as below -

hostname=abc.com hostname=xyz.com
db-host=abc.com db-host=xyz.com

In each line, the value before space is the old value which needs to be replaced by the new value after the space recursively in a folder named "test". I am able to do this using below shell script.

#!/bin/bash

IFS=$'
' 
for f in `cat test.txt`
do
  OLD=$(echo $f| cut -d ' ' -f 1) 
  echo "Old = $OLD"
  NEW=$(echo $f| cut -d ' ' -f 2)
  echo "New = $NEW"
  find test -type f | xargs sed -i.bak "s/$OLD/$NEW/g"
done

"sed" replaces the strings on the fly in 100s of files.

Is there a trick or an alternative way by which i can get a report of the files changed like absolute path of the file & the exact lines that got changed ?

PS - I understand that sed or stream editors doesn't support this functionality out of the box. I don't want to use versioning as it will be an overkill for this task.

解决方案

Let's start with a simple rewrite of your script, to make it a little bit more robust at handling a wider range of replacement values, but also faster:

#!/bin/bash

# escape regexp and replacement strings for sed
escapeRegex() { sed 's/[^^]/[&]/g; s/^/\^/g' <<<"$1"; }
escapeSubst() { sed 's/[&/]/\&/g' <<<"$1"; }

while read -r old new; do
    find test -type f -exec sed "/$(escapeRegex "$old")/$(escapeSubst "$new")/g" -i '{}' ;
done <test.txt

So, we loop over pairs of whitespace-separated fields (old, new) in lines from test.txt and run a standard sed in-place replace on all files found with find.

Pretty similar to your script, but we properly read lines from test.txt (no word splitting, pathname/variable expansion, etc.), we use Bash builtins whenever possible (no need to call external tools like cat, cut, xargs); and we escape sed metacharacters in old/new values for proper use as sed's regexp and replacement expressions.

Now let's add logging from sed:

#!/bin/bash

# escape regexp and replacement strings for sed
escapeRegex() { sed 's/[^^]/[&]/g; s/^/\^/g' <<<"$1"; }
escapeSubst() { sed 's/[&/]/\&/g' <<<"$1"; }

while read -r old new; do
    find test -type f -printf '
[%p]
' -exec sed "/$(escapeRegex "$old")/{
        h
        s//$(escapeSubst "$new")/g
        H
        x
        s/
/ --> /
        w /dev/stdout
        x
    }" -i '{}' > >(tee -a change.log) ;
done <test.txt

The sed script above changes each old to new, but it also writes old --> new line to /dev/stdout (Bash-specific), which we in turn append to change.log file. The -printf action in find outputs a "header" line with file name, for each file processed.

With this, your "change log" will look something like:

[file1]
hostname=abc.com --> hostname=xyz.com

[file2]

[file1]
db-host=abc.com --> db-host=xyz.com

[file2]
db-host=abc.com --> db-host=xyz.com

Just for completeness, a quick walk-through the sed script. We act only on lines containing the old value. For each such line, we store it to hold space (h), change it to new, append that new value to the hold space (joined with newline, H) which now holds oldnew. We swap hold with pattern space (x), so we can run s command that converts it to old --> new. After writing that to the stdout with w, we move the new back from hold to pattern space, so it gets written (in-place) to the file processed.

这篇关于Bash - 搜索和替换操作,报告已更改的文件和行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-13 14:44