【问题标题】: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 的退出代码不反映是否找到任何匹配项。

标签: linux bash shell ubuntu 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;
                      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>
      

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

      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) 
      

      注意:我已经从上面的命令中删除了-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 
          

          输出截图:

          参考资料:

          1. 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

          【讨论】:

            猜你喜欢
            • 2016-08-08
            • 2022-01-24
            • 1970-01-01
            • 1970-01-01
            • 2018-09-13
            • 2017-02-02
            • 2012-04-28
            • 1970-01-01
            • 2021-06-24
            相关资源
            最近更新 更多