简体   繁体   English

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

[英]How to check if sed has changed a file

I am trying to find a clever way to figure out if the file passed to sed has been altered successfully or not.我试图找到一种聪明的方法来确定传递给 sed 的文件是否已成功更改。

Basically, I want to know if the file has been changed or not without having to look at the file modification date.基本上,我想知道文件是否已更改,而无需查看文件修改日期。

The reason why I need this is because I need to do some extra stuff if sed has successfully replaced a pattern.我需要这个的原因是因为如果 sed 成功替换了一个模式,我需要做一些额外的事情。

I currently have:我目前有:

    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

The above code is a bit expensive and I would love to be able to use some hacks here.上面的代码有点贵,我希望能够在这里使用一些技巧。

A bit late to the party but for the benefit of others, I found the 'w' flag to be exactly what I was looking for.聚会有点晚了,但为了其他人的利益,我发现 '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 will contain each change (ie the changed text) on it's own line. changelog.txt将在其自己的行中包含每个更改(即更改的文本)。 If there were no changes, changelog.txt will be zero bytes.如果没有变化, changelog.txt将是零字节。

A really helpful sed resource (and where I found this info) is http://www.grymoire.com/Unix/Sed.html .一个非常有用的 sed 资源(以及我在何处找到了此信息)是http://www.grymoire.com/Unix/Sed.html

I believe you may find these GNU sed extensions useful我相信你会发现这些 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.

and

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.

It seems like exactly what are you looking for.这似乎正是你在寻找什么。

This might work for you (GNU sed):这可能对你有用(GNU sed):

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

Explanation:解释:

  • /'"$old_pattern"'/{s//'"$new_pattern"'/;h} if the pattern space (PS) contains the old pattern , replace it by the new pattern and copy the PS to the hold space (HS). /'"$old_pattern"'/{s//'"$new_pattern"'/;h}如果模式空间 (PS) 包含old pattern ,则将其替换为new pattern并将 PS 复制到保持空间 (HS) )。
  • ${x;/./{x;q1};x} on encountering the last line, swap to the HS and test it for the presence of any string. ${x;/./{x;q1};x}在遇到最后一行时,交换到 HS 并测试它是否存在任何字符串。 If a string is found in the HS (ie a substitution has taken place) swap back to the original PS and exit using the exit code of 1 , otherwise swap back to the original PS and exit with the exit code of 0 (the default).如果在 HS 中找到字符串(即发生了替换),则交换回原始 PS 并使用退出代码1退出,否则交换回原始 PS 并以退出代码0退出(默认) .

You could use awk instead:您可以改用awk

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

I'm ignoring the -i feature: you can use the shell do do redirections as necessary.我忽略了-i功能:您可以根据需要使用 shell 进行重定向。

Sigh.叹。 Many comments below asking for basic tutorial on the shell.下面的许多评论要求提供有关 shell 的基本教程。 You can use the above command as follows:您可以按如下方式使用上述命令:

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

It is not clear to me if "DO SOME OTHER STUFF HERE" is the same in each case.我不清楚“在这里做一些其他的事情”是否在每种情况下都相同。 Any similar code in the two blocks should be refactored accordingly.应该相应地重构两个块中的任何类似代码。

You can diff the original file with the sed output to see if it changed:您可以将原始文件与 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"

I know it is a old question and using awk instead of sed is perhaps the best idea, but if one wants to stick with sed, an idea is to use the -w flag.我知道这是一个老问题,使用 awk 而不是 sed 可能是最好的主意,但如果有人想坚持使用 sed,一个想法是使用 -w 标志。 The file argument to the w flag only contains the lines with a match. w 标志的文件参数只包含匹配的行。 So, we only need to check that it is not empty.所以,我们只需要检查它是否为空。

Don't use sed to tell if it has changed a file;不要使用sed来判断它是否更改了文件; instead, use grep to tell if it is going to change a file, then use sed to actually change the file.相反,使用grep判断它是否要更改文件,然后使用sed实际更改文件。 Notice the single line of sed usage at the very end of the Bash function below:请注意下面 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
}

Place that in your ~/.bashrc file, for instance.例如,将它放在您的 ~/.bashrc 文件中。 Close and reopen your terminal and then use it.关闭并重新打开您的终端,然后使用它。

Usage:用法:

gs_replace_str "regex_search_pattern" "replacement_string" "file_path"

Example: replace do with bo so that "doing" becomes "boing" (I know, we should be fixing spelling errors not creating them :) ):示例:用bo替换do ,使“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 

Screenshot of the output:输出截图:

在此处输入图片说明

References:参考:

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

In macos I just do it as follows:在 macos 中,我只是这样做:

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

I checked, and this is faster than md5 , cksum and sha comparisons我查了一下,这比md5cksumsha比较快

perl -sple '$replaced++ if s/$from/$to/g;
                END{if($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>

Example: The command will produce the following output, stating the number of changes made/file.示例:该命令将产生以下输出,说明/文件所做的更改数。

perl -sple '$replaced++ if s/$from/$to/g;
END{if($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) 

Note: I have removed -i from the above command , so it will not update the files for the people who are just trying out the command.注意:我已经从上面的命令中删除了-i ,因此它不会为刚刚尝试该命令的人更新文件。 If you want to enable in-place replacements in the file add -i after perl in above command.如果要在文件中启用就地替换,请在上述命令中的perl之后添加-i

check if sed has changed MANY files检查 sed 是否更改了许多文件

workaround with two stages: match + replace两个阶段的解决方法:匹配+替换

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"
$ echo hi > abc.txt
$ sed "s/hi/bye/g; t; q1;" -i abc.txt && (echo "Changed") || (echo "Failed")
Changed
$ sed "s/hi/bye/g; t; q1;" -i abc.txt && (echo "Changed") || (echo "Failed")
Failed

https://askubuntu.com/questions/1036912/how-do-i-get-the-exit-status-when-using-the-sed-command/1036918#1036918 https://askubuntu.com/questions/1036912/how-do-i-get-the-exit-status-when-using-the-sed-command/1036918#1036918

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM