【问题标题】:Looping through files in a dir; Pulling out filenames to replace string(s) in existing files遍历目录中的文件;提取文件名以替换现有文件中的字符串
【发布时间】:2019-08-19 23:29:42
【问题描述】:

我有一个 Markdown 文件目录,我正在尝试通过以下方式完成以下任务:

  • 获取markdown文件的文件名并将其存储在变量中
  • 获取该变量并将文件中的一系列字符串替换为存储的文件名变量
  • 遍历目录中的所有文件并执行相同的操作

我很接近,但以下代码仅提取第一个降价文件的文件名并将变量应用于文件中的所有字符串。到目前为止,这是我的工作代码:

#!/bin/bash

for file in /home/user/dir/*; do

  str="somestring"
  filename=$(basename $file)
  fn="$(echo "${filename%.*}")"

  find ./ -type f -exec sed -i '' -e "s/${str}/${fn}/g" {} \;

done

假设markdown文件是这样的:

123456789.md/home/user/dir/123456789.md 以及其他几个具有其他随机数字名称的 .md 文件。

.md 文件的结构类似于:

---
layout: default
date: 2010-03-28
original: /orig/somestring.jpg
thumbnail: /thumb/somestring_thumb.jpg
permalink: /images/somestring/
---

我的目标是让脚本根据 .md 文件本身的文件名使每个文件看起来像这样:

---
layout: default
date: 2010-03-28
original: /orig/123456789.jpg
thumbnail: /thumb/123456789_thumb.jpg
permalink: /images/123456789/
---

对编辑 sed 调用的最佳方式或其他编写方式有何想法?在我的测试中,sed 偶尔会返回 sed: RE error: illegal byte sequence,但无论如何都会重命名字符串,即使它是错误的字符串。

【问题讨论】:

    标签: linux bash macos scripting


    【解决方案1】:

    考虑使用以下相当可靠的解决方案。它确保在给定的搜索字符串和/或 Markdown 文件名中可能被解释为 basic regular expression (BRE) 元字符的任何字符在 sed 替换中被视为文字。

    解决办法:

    #!/usr/bin/env bash
    
    target_dir=/path/to/dir
    search='somestring'
    
    search_escaped=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search")
    
    while read -rd ''; do
      base=$(basename -- "$REPLY")
      replace_escaped=$(sed 's/[&/\]/\\&/g' <<<"${base%.*}")
      sed -i '' -e 's/'"$search_escaped"'/'"$replace_escaped/g"'' "$REPLY"
    done < <(find $target_dir -depth 1 -type f -name '*.md' -print0)
    

    说明:

    • target_dir 变量的值应定义为您要在其中执行搜索的目录的路径名。例如您的问题中指定的 /home/user/dir

    • search 变量的值应更改为要在 Markdown (.md) 文件中搜索的字符串,并且必须用单引号 ('...') 括起来。

    • 读取的行;

      search_escaped=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search")
      

      转义 search 字符串中可能存在的潜在 BRE 元字符,并将结果分配给名为 search_escaped 的新变量。

      我们这样做是因为您定义的搜索字符串最终将用作带有 sed 的 s commandsearch 字符串,即 s/regexp/replacement/flags。本质上,您给定的search 字符串的每个字符都放置在其自己的字符集[...] 表达式中,以将其视为文字,但插入符号(^)除外,因为它们被转义为\^。详情请参阅this answer

      这意味着我们可以提供一个search 字符串,例如s$o.m *e[s\t^ring,即具有许多元字符的字符串,它们将被视为文字,并防止我们的程序出错。

    • 使用find 实用程序,我们定义以下命令来获取给定target_dir 内所有.md 文件的路径名:

      find $target_dir -depth 1 -type f -name '*.md' -print0
      
      • -depth 1 部分确保我们只能在顶层找到文件。但是,如果您想递归地下降给定的目录树,您可以将其删除 - 通过删除它,您还将在给定目录的子目录中包含许多级别的任何 .md 文件。

      • -name '*.md' 部分确保我们只包含 Markdown 文件 (.md) 并排除给定 target_dir 中可能存在的任何其他文件。

      • &lt;( ... ) 中包含的find 部分称为process substitution,前面的&lt; redirectsfindstdin 找到的路径名。

    • while 循环 reads 是 find 命令的结果,即找到的每个 .md 文件的路径名。

      while 循环的主体中,我们执行以下任务:

      • 我们从每个路径名中获取 basename(注意:$REPLY 是一个与 while 关联的内置变量 - 在这种情况下,它在循环的每一轮中都包含对路径名的引用) :

        base=$(basename -- "$REPLY")
        
      • 行如下:

        replace_escaped=$(sed 's/[&/\]/\\&/g' <<<"${base%.*}")
        

        sed 可能认为的内容转义为占位符字符,例如文件名中的\1。例如;如果一个文件被命名为somefile\1\2\3.md,当我们用它替换search 字符串时会失败——但是这可以防止这种情况发生。同样,请参阅this answer 了解更多详情。

        ${base%.*} 部分利用parameter expansionbase 变量的值(即来自文件名/基名)中省略文件扩展名部分(即.md)。

      • 最后,我们用replace_escaped 变量的值(即没有文件扩展名的文件名)。

        sed -i '' -e 's/'"$search_escaped"'/'"$replace_escaped/g"'' "$REPLY"
        

    已知问题:基本名称的任何部分都可能包含换行符 (\n),虽然此解决方案确实使用所述方法正确处理此类路径名的发现here - 当文件名包含换行符时,它当前不执行字符串替换。

    【讨论】:

    • 哇,谢谢。这似乎是一个很好的方法。
    【解决方案2】:

    如果我理解正确,以下内容将起作用:

    #!/bin/bash
    
    for file in /home/user/dir/*; do
    
        str="somestring"
        filename=$(basename "$file")
        fn=${filename%.*}
    
        LANG=C sed -i '' -e "s/${str}/${fn}/g" "$file"
    
    done
    

    问题是您在for 循环中执行find &amp; sed,它过度替换了不相关文件中的字符串。
    sed 之前的LANG=C 将是sed: RE error: illegal byte sequence 问题的常见解决方法。

    【讨论】:

    • 啊,谢谢。这对解决我的问题很有用。如果没有for 循环,有没有更好的方法来写这个?
    • 您可以使用findexecxargs 来实现,但for 似乎更直接。 everythingcli.org/find-exec-vs-find-xargs
    猜你喜欢
    • 1970-01-01
    • 2020-12-10
    • 1970-01-01
    • 2021-01-04
    • 1970-01-01
    • 1970-01-01
    • 2020-02-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多