如何检查 sed 是不是更改了文件

Posted

技术标签:

【中文标题】如何检查 sed 是不是更改了文件【英文标题】:How to check if sed has changed a file如何检查 sed 是否更改了文件 【发布时间】:2012-08-22 01:40:14 【问题描述】:

我试图找到一种聪明的方法来确定传递给 sed 的文件是否已成功更改。

基本上,我想知道文件是否已更改,而无需查看文件修改日期。

我之所以需要这个是因为如果 sed 成功替换了一个模式,我需要做一些额外的事情。

我目前有:

    grep -q $pattern $filename
    if [ $? -eq 0 ]
    then
        sed -i s:$pattern:$new_pattern: $filename
                # DO SOME OTHER STUFF HERE
    else
        # DO SOME OTHER STUFF HERE
    fi

上面的代码有点贵,我希望能够在这里使用一些技巧。

【问题讨论】:

任何时候你试图做一些“聪明”的事情,你可能不应该这样做。 @WilliamPursell 因为世界是由愚蠢的发明创造的。 如何将更改写入新文件,然后区分原始文件和生成的文件?顺便说一句,如果 grep 之前找到了模式,sed 不应该总是替换它吗? SOME OTHER STUFF 也可以和sed 一起完成吗? sed 的退出代码不反映是否找到任何匹配项。 【参考方案1】:

聚会有点晚了,但为了其他人的利益,我发现 'w' 标志正是我想要的。

sed -i "s/$pattern/$new_pattern/w changelog.txt" "$filename"
if [ -s changelog.txt ]; then
    # CHANGES MADE, DO SOME STUFF HERE
else
    # NO CHANGES MADE, DO SOME OTHER STUFF HERE
fi

changelog.txt 将在其自己的行中包含每个更改(即更改的文本)。如果没有变化,changelog.txt 将为零字节。

http://www.grymoire.com/Unix/Sed.html 是一个非常有用的 sed 资源(我在哪里找到此信息)。

【讨论】:

这会覆盖changelog.txt 文件。知道如何附加到它吗? 不是直接的,但是一些进程外文件操作肯定是可行的。 我觉得覆盖很方便...每次sed之后我可以检查而不用记住删除文件。 Sed - An Introduction and Tutorial by Bruce Barnett 是一个宝箱,但它太长了。每次我重读它,我都会学到新东西。 如果读者对shell引用规则不熟悉,不妨指出单引号内的文本不会扩展任何shell变量;将其视为伪代码。【参考方案2】:

我相信你会发现这些 GNU sed 扩展很有用

t label

If a s/// has done a successful substitution since the last input line
was read and since the last t or T command, then branch to label; if
label is omitted, branch to end of script.

q [exit-code]

Immediately quit the sed script without processing any more input, except 
that if auto-print is not disabled the current pattern space will be printed. 
The exit code argument is a GNU extension.

看起来正是你在寻找什么。

【讨论】:

我不明白这个怎么用 我也不懂。 Hostmaster 你能解释一下我们把这个选项放在哪里吗?我在安装脚本中运行 sed,如果 sed 无法更改配置文件,我需要发出警告。 如何使用tq 选项与sed 命令在此处解释:askubuntu.com/a/1036918/250399。 TLDR:sed -i 's/orig/repl/; t; q1' file.txt 重要:看起来这仅适用于单行模式。对于 sed 整个文件,其中替换在中间某处完成,在 sed 解析第一行并且在那里找不到替换后,它将只打印第一行不变并退出。【参考方案3】:

这可能对你有用(GNU sed):

sed -i.bak '/'"$old_pattern"'/s//'"$new_pattern"'/;h;$x;/./x;q1;x' file || echo changed

解释:

/'"$old_pattern"'/s//'"$new_pattern"'/;h 如果模式空间 (PS) 包含 old pattern,则将其替换为 new pattern 并将 PS 复制到保留空间 (HS)。 $x;/./x;q1;x 在遇到最后一行时,切换到 HS 并测试它是否存在任何字符串。如果在 HS 中找到一个字符串(即发生了替换),则切换回原始 PS 并使用退出代码 1 退出,否则切换回原始 PS 并使用退出代码 0 退出(默认)。

【讨论】:

【参考方案4】:

您可以改用awk

awk '$0 ~ p gsub(p, r); t=1 1 END exit (!t) ' p="$pattern" r="$repl"

我忽略了-i 功能:您可以根据需要使用 shell 进行重定向。

叹息。下面的许多 cmets 要求提供有关 shell 的基本教程。你可以使用上面的命令如下:

if awk '$0 ~ p  gsub(p, r); t=1 1 END exit (!t) ' \
        p="$pattern" r="$repl" "$filename" > "$filename.new"; then
    cat "$filename.new" > "$filename"
    # DO SOME OTHER STUFF HERE
else
    # DO SOME OTHER STUFF HERE
fi

我不清楚“在此处做一些其他事情”是否在每种情况下都相同。两个块中任何相似的代码都应该相应地重构。

【讨论】:

您不能仅使用 shell 重定向进行就地修改。命令文件 > 文件不起作用(应用重定向时文件被截断,这发生在命令启动之前) awk '...' $filename > tmp.txt; mv tmp.txt $filenamesed -i 只是对您隐藏临时文件的详细信息。 @AlvaroGMJ:您不能使用 sed -i 进行就地修改,但您当然可以通过 shell 重定向来做到这一点。但你是对的,你不能用cmd file > file来做。 你能给出一个使用awk解决方案的完整例子吗?目前尚不清楚您忽略 -i 功能是什么意思,或者 sed 与此有什么关系。您的代码似乎不起作用 你能扩展这个答案吗?什么是“!t”?什么是“退出”?【参考方案5】:

您可以将原始文件与 sed 输出进行比较,以查看它是否已更改:

sed -i.bak s:$pattern:$new_pattern: "$filename"
if ! diff "$filename" "$filename.bak" &> /dev/null; then
  echo "changed"
else
  echo "not changed"
fi
rm "$filename.bak"

【讨论】:

diff 会比他试图取代的grep 便宜吗? 嘿,谢谢你的解决方案,但我认为对我要搜索的每个文件进行差异可能有点 CPU 密集。你怎么看? 我也这么觉得,这样效率不高 diff 向您展示不同之处。如果您只是想知道是否有任何区别cmp 就足够了。 [ $? -ne 0] 也是没用的。只需将命令放入if 条件:if cmp "$filename" "$filename".bak; then【参考方案6】:

在macos中我只是这样做:

changes=""
changes+=$(sed -i '' "s/$to_replace/$replacement/g w /dev/stdout" "$f")
if [ "$changes" != "" ]; then
  echo "CHANGED!"
fi

我查了一下,这比md5cksumsha 比较快

【讨论】:

限制:只能处理一个替换。所以这不起作用:sed -i 's,a,b, w /dev/stdout ; s,b,a, w /dev/stdout' input.txt。见my workaround【参考方案7】:

我知道这是一个老问题,使用 awk 代替 sed 可能是最好的主意,但如果想坚持使用 sed,一个想法是使用 -w 标志。 w 标志的文件参数仅包含匹配的行。所以,我们只需要检查它是否为空。

【讨论】:

【参考方案8】:
perl -sple '$replaced++ if s/$from/$to/g;
                ENDif($replaced != 0) print "[Info]: $replaced replacement done in $ARGV(from/to)($from/$to)"
                else print "[Warning]: 0 replacement done in $ARGV(from/to)($from/$to)"' -- -from="FROM_STRING" -to="$DESIRED_STRING" </file/name>

示例: 该命令将产生以下输出,说明所做的更改/文件的数量。

perl -sple '$replaced++ if s/$from/$to/g;
ENDif($replaced != 0) print "[Info]: $replaced replacement done in $ARGV(from/to)($from/$to)"
else print "[Warning]: 0 replacement done in $ARGV(from/to)($from/$to)"' -- -from="timeout" -to="TIMEOUT" *
[Info]: 5 replacement done in main.yml(from/to)(timeout/TIMEOUT)
[Info]: 1 replacement done in task/main.yml(from/to)(timeout/TIMEOUT)
[Info]: 4 replacement done in defaults/main.yml(from/to)(timeout/TIMEOUT)
[Warning]: 0 replacement done in vars/main.yml(from/to)(timeout/TIMEOUT) 

注意:我已经从上面的命令中删除了-i,所以它不会为刚刚尝试该命令的人更新文件。如果要在文件中启用就地替换,请在上述命令中的 perl 之后添加 -i

【讨论】:

【参考方案9】:

检查 sed 是否更改了许多文件

递归替换一个目录中的所有文件 生成所有修改文件的列表

解决方法有两个阶段:匹配 + 替换

g='hello.*world'
s='s/hello.*world/bye world/g;'
d='./' # directory of input files
o='modified-files.txt'

grep -r -l -Z -E "$g" "$d" | tee "$o" | xargs -0 sed -i "$s"

$o 中的文件路径以零分隔

【讨论】:

【参考方案10】:

不要使用sed 来判断它是否 更改了文件;相反,使用grep 来判断它是否 更改文件,然后使用sed 来实际更改文件。请注意下面 Bash 函数的最末端处的单行 sed 用法:

# Usage: `gs_replace_str "regex_search_pattern" "replacement_string" "file_path"`
gs_replace_str() 
    REGEX_SEARCH="$1"
    REPLACEMENT_STR="$2"
    FILENAME="$3"

    num_lines_matched=$(grep -c -E "$REGEX_SEARCH" "$FILENAME")
    # Count number of matches, NOT lines (`grep -c` counts lines), 
    # in case there are multiple matches per line; see: 
    # https://superuser.com/questions/339522/counting-total-number-of-matches-with-grep-instead-of-just-how-many-lines-match/339523#339523
    num_matches=$(grep -o -E "$REGEX_SEARCH" "$FILENAME" | wc -l)

    # If num_matches > 0
    if [ "$num_matches" -gt 0 ]; then
        echo -e "\n$num_matches matches found on $num_lines_matched lines in file"\
                "\"$FILENAME\":"
        # Now show these exact matches with their corresponding line 'n'umbers in the file
        grep -n --color=always -E "$REGEX_SEARCH" "$FILENAME"
        # Now actually DO the string replacing on the files 'i'n place using the `sed` 
        # 's'tream 'ed'itor!
        sed -i "s|$REGEX_SEARCH|$REPLACEMENT_STR|g" "$FILENAME"
    fi

例如,将其放在您的 ~/.bashrc 文件中。关闭并重新打开您的终端,然后使用它。

用法:

gs_replace_str "regex_search_pattern" "replacement_string" "file_path"

示例:将do 替换为bo,这样“doing”就变成了“boing”(我知道,我们应该修复拼写错误而不是创建它们:)):

$ gs_replace_str "do" "bo" test_folder/test2.txt 

9 matches found on 6 lines in file "test_folder/test2.txt":
1:hey how are you doing today
2:hey how are you doing today
3:hey how are you doing today
4:hey how are you doing today  hey how are you doing today  hey how are you doing today  hey how are you doing today
5:hey how are you doing today
6:hey how are you doing today?
$SHLVL:3 

输出截图:

参考资料:

    https://superuser.com/questions/339522/counting-total-number-of-matches-with-grep-instead-of-just-how-many-lines-match/339523#339523 https://unix.stackexchange.com/questions/112023/how-can-i-replace-a-string-in-a-files/580328#580328

【讨论】:

以上是关于如何检查 sed 是不是更改了文件的主要内容,如果未能解决你的问题,请参考以下文章

$ sed -i 不更改文件

对 Vim 的 sed 语法着色文件的这种更改是好的还是破坏性的?

Bash - 使用 sed 更改配置文件

在 xml 文件中使用 sed 更改值

如何使用sed命令使用正则表达式过滤文件?

更改文件名后缀(使用 sed ?)