遍历目录中的文件;拉出文件名以替换现有文件中的字符串

时间:2019-03-29 04:10:45

标签: linux bash macos scripting

我有一个markdown文件目录,我试图通过以下方法完成该任务:

  • 获取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

假设降价文件如下所示:

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返回sed: RE error: illegal byte sequence,但是无论如何都使用了字符串的重命名,即使它是错误的字符串。

2 个答案:

答案 0 :(得分: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变量的值应更改为要在减价(.md)文件中搜索的字符串,并且必须将其括在单引号({ {1}}。

  • 读取的行;

    '...'

    转义您的search_escaped=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search") 字符串中可能存在的潜在BRE元字符,并将结果分配给名为search的新变量。

    之所以这样做,是因为最终定义的搜索字符串将与sed的s command(即 search_escaped )一起用作 search 字符串。基本上,给定s/regexp/replacement/flags字符串中的每个字符都放在其自己的字符集search表达式中,以将其视为文字,但转义([...])字符会转义,为^。有关更多详细信息,请参见this answer

    这意味着我们可以提供一个\^字符串,例如search,即带有许多元字符的字符串,它们将被当作文字,并防止我们的程序出错。

    < / li>
  • 使用find实用程序,我们定义以下命令来获取给定s$o.m *e[s\t^ring内所有.md文件的路径名:

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

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

      < / li>
    • target_dir中包含的find部分(称为process substitution)和前面<( ... ) redirects的路径名是{{ 1}}到<

  • find循环read生成stdin命令的结果,即找到的每个while文件的路径名。

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

    • 我们从每个路径名获得 basename (注意:.md是与while关联的内置变量-在这种情况下,它持有对路径名的引用在循环的每一圈中):

      $REPLY
    • 该行显示为:

      while

      转义base=$(basename -- "$REPLY") 可能会被视为占位符的字符,例如文件名中的replace_escaped=$(sed 's/[&/\]/\\&/g' <<<"${base%.*}") 。例如;如果文件名为sed,当我们用它替换\1字符串时会失败-但是,这样做可以防止这种情况。同样,请参阅this answer以获得更多详细信息。

      somefile\1\2\3.md部分利用parameter expansionsearch变量的值(即文件名/基名)中省略了文件扩展名部分(即${base%.*})。

    • 最后,我们将Markdown文件中可能存在的所有 search 字符串实例(即.md变量的值)替换为{{ 1}}变量(即不带文件扩展名的文件名)。

      base

已知问题::基本名称的任何部分都可能包含换行符($search_escaped),尽管此解决方案可以使用以下方式正确处理此类路径名的发现here中介绍的方法-当文件名包含换行符时,它当前不执行字符串替换。

答案 1 :(得分:0)

如果我正确理解,则可以进行以下操作:

#!/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

问题是您正在find & sed循环中执行for,这会替换不相关文件中的字符串。
LANG=C之前的sed是解决sed: RE error: illegal byte sequence问题的常用解决方法。