如何检查 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 无法更改配置文件,我需要发出警告。 如何使用t
和q
选项与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 $filename
。 sed -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
我查了一下,这比md5
、cksum
和sha
比较快
【讨论】:
限制:只能处理一个替换。所以这不起作用: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 是不是更改了文件的主要内容,如果未能解决你的问题,请参考以下文章