用于替换选定目录中多个文件中的字符串的 Shell 脚本

用于替换选定目录中多个文件中的字符串的 Shell 脚本

我创建了下面的脚本,它采用单个目录的路径并替换该目录中所有文件中的搜索字符串。我想增强这个脚本,使其可以搜索和替换外部输入文件中列出的多个目录中的字符串。

外部输入文件内容:

/var/start/system1/dir1
/var/start/system2/dir2
/var/start/system3/dir3
/var/start/system4/dir4

具有一个目录的脚本:

filepath="/var/start/system/dir1"
searchstring="test"
replacestring="test01"

i=0; 

for file in $(grep -l -R $searchstring $filepath)
do
  cp $file $file.bak
  sed -e "s/$searchstring/$replacestring/ig" $file > tempfile.tmp
  mv tempfile.tmp $file

  let i++;

  echo "Modified: " $file
done

答案1

首先,可以通过sed -i与 GNUsedsed -i ''FreeBSD 一起使用(就地替换)来避免 tmpfile dance。

grep -R可以在命令行上采用多个路径,因此如果您确信所有路径都不包含空格,则可以替换$(grep -l -Re "$searchstring" "$filepath")$(grep -l -R "$searchstring" $(cat path_list)).

如果任何路径包含空格、制表符或任何通配符,则此操作将会失败,但原始路径也会失败。

更强大的方法使用findsed 并将其应用于所有文件,相信它不会修改没有匹配的文件(这里假设有一个bashshell):

# Read newline-separated path list into array from file 'path_list'
IFS=$'\n' read -d '' -r -a paths path_list

# Run sed on everything
find "${paths[@]}" \
  -exec sed -i -r -e "s/$searchstring/$replacestring/ig" '{}' ';'

但这不会向您提供有关它正在修改哪些文件的任何反馈。

一个更长的版本确实可以给你反馈:

# Read newline-separated path list into array from file 'path_list'
IFS=$'\n' read -d '' -r -a paths path_list

grep -l -R "$searchstring" "${paths[@]}" | while IFS= read -r file; do
  sed -r -i -e "s/$searchstring/$replacestring/ig" "$file"
  echo "Modified: $file"
done

答案2

使用 GNU 工具

< dir.list xargs -rd '\n' grep -rlZ -- "$searchstring" |
  xargs -r0 sed -i -e "s/$searchstring/$replacestring/ig" --

(不要忘记引用你的变量,不加引号的变量是 split+glob 运算符)

答案3

这是我能想到的最便携的方式来做到这一点,尽管它仍然依赖于大多便携式/dev/fd/0.dot。如果没有它,您可以使用单个文件。无论如何,它主要依赖于我前几天编写的这个 shell 函数:

_sed_cesc_qt() { 
    sed -n ':n;\|^'"$1"'|!{H;$!{n;bn}};{$l;x;l}' |
    sed -n '\|^'"$1"'|{:n;\|[$]$|!{
            N;s|.\n||;bn};s|||
            \|\([^\\]\)\\\([0-9]\)|{
            s||\1\\0\2|g;}'"
            s|'"'|&"&"&|g;'"s|.*|'&'|p}"

}

首先我将展示它的工作原理,然后我将解释如何操作。因此,我将创建一个测试文件库:

printf 'f=%d
    echo "$f" >./"$f"
    echo "$f" >./"$f\n$f"
    echo "$f" >./"$f\n$f\n$f"
' $(seq 10) | . /dev/fd/0

这会创建一堆文件,每个文件以其包含的数字 1-10 命名:

ls -qm 
1, 1?1, 1?1?1, 10, 10?10, 10?10?10, 2, 2?2, 2?2?2, 3, 3?3, 3?3?3, 4, 4?4, 4?4?4, 5, 5?5, 5?5?5, 6, 6?6, 6?6?6, 7,
7?7, 7?7?7, 8, 8?8, 8?8?8, 9, 9?9, 9?9?9

这是我的测试目录中以逗号分隔的文件列表,每个文件?代表一个换行符。

cat ./1*

1
1
1
10
10
10

每个文件仅包含一个数字。

现在我将进行替换grep

find ././ \! -type d -exec \
        grep -l '[02468]$' \{\} + |
_sed_cesc_qt '\./\./' | 
sed 's|.|\\&|g' |
xargs printf 'f=%b
        sed "/[02468]\\$/s//CHANGED/" <<-SED >"$f"
        $(cat <"$f")
        SED\n' | 
. /dev/fd/0

现在当我...

cat ./1*

1
1
1
1CHANGED
1CHANGED
1CHANGED

所有[2468]文件都是类似的CHANGED。它也以递归方式工作。好的,现在我将解释如何进行。

首先,我猜,这个函数:

  1. :n从扩展标签开始
  2. \|地址|参数$1- 一个标记
  3. 如果当前行!不匹配{
    • 将其附加到H旧缓冲区
    • 如果当前行不是!最后$一行{
    • 用扩展行覆盖n当前行
    • b牧场回到:n分机标签
    • }}
  4. 否则,如果当前行是$最后一行l,则查看模式空间
  5. 否则 ex更改保持和模式缓冲区的内容并且...
  6. l明确地看待模式空间

这是第一个sed陈述——这几乎是它的核心内容。我们根本不p打印模式空间——我们只是l看它。这是 POSIX 定义该l函数的方式:

[2地址]l (这封信是 ell。)以视觉上明确的形式将模式空间写入标准输出。 IEEE Std 1003.1-2001基本定义卷表5-1,转义序列和相关操作中列出的字符( '\\', '\a', '\b', '\f', '\r', '\t', '\v' )应写为相应的转义序列;该表中的内容'\n' 不适用。不在该表中的不可打印字符应写为一个三位数\字符中每个字节的八进制数(前面带有反斜杠)(最重要的字节在前)。长行应折叠,折叠点通过写\反斜杠后跟\n斜线来指示;折叠发生的长度未指定,但应适合输出设备。每行的末尾应标有'$'

所以如果我这样做:

printf '\e%s10\n10\n10' '\' | sed -n 'N;N;l'

我得到:

\033\\10\n10\n10$

这几乎完美地逃脱了printf。它只需要为八进制添加一个额外的零并删除尾随$- 因此下一个sed语句将其清除。

我不会做同样的细节,但基本上是下一个sed声明:

  1. 如果行以$1标记开头...
  2. 拉入Next 行,直到当前行结束$
  3. 如果必须执行上述操作,它将删除结尾的\反斜杠和\newline 字符。
  4. 然后它删除尾随$
  5. 查找任何\后跟数字且前面没有另一个\反斜杠的反斜杠,并插入零
  6. 搜索任何'单引号并将其加双引号
  7. '最后用单引号将整个字符串括起来

所以现在,当我这样做时:

printf %s\\n ././1* |
_sed_cesc_qt '\./\./'

我得到:

'././1'
'././1\n1'
'././1\n1\n1'
'././10'
'././10\n10'
'././10\n10\n10'

剩下的就很简单了。这取决于字符串将解析的事实././,但它只会出现在find/grep每个路径名开头的输出中 - 所以它成为我的$1标记。

-exec grepfind并指定-l它输出包含正则表达式的文件的文件名。

我调用该函数并获取其输出。

然后,我\反斜杠转义其输出中的每个字符xargs

我将printf一个脚本写入该|pipe文件 - 我将其.dot源为/dev/fd/0.我将f变量定义为其当前参数(我的路径名),并将cat$f参数传递给<<heredocument,该参数被馈送到sed,并sed写回源文件。

这可能涉及临时文件 - 这取决于您的 shell。bashzsh会为每个heredocument写出一个临时文件 - 但他们也会清理它们。dash另一方面,只会将此处的文档写入匿名的|pipe.

但重要的是,文件在被写入之前必须被完全读取 - 这就是文档和命令替换的工作原理。

相关内容