我创建了下面的脚本,它采用单个目录的路径并替换该目录中所有文件中的搜索字符串。我想增强这个脚本,使其可以搜索和替换外部输入文件中列出的多个目录中的字符串。
外部输入文件内容:
/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
与 GNUsed
或sed -i ''
FreeBSD 一起使用(就地替换)来避免 tmpfile dance。
grep -R
可以在命令行上采用多个路径,因此如果您确信所有路径都不包含空格,则可以替换$(grep -l -Re "$searchstring" "$filepath")
为$(grep -l -R "$searchstring" $(cat path_list))
.
如果任何路径包含空格、制表符或任何通配符,则此操作将会失败,但原始路径也会失败。
更强大的方法使用find
sed 并将其应用于所有文件,相信它不会修改没有匹配的文件(这里假设有一个bash
shell):
# 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
。它也以递归方式工作。好的,现在我将解释如何进行。
首先,我猜,这个函数:
:n
从扩展标签开始\|
地址|
参数$1
- 一个标记- 如果当前行
!
不匹配{
- 将其附加到
H
旧缓冲区 - 如果当前行不是
!
最后$
一行{
- 用扩展行覆盖
n
当前行 b
牧场回到:n
分机标签}}
- 将其附加到
- 否则,如果当前行是
$
最后一行l
,则查看模式空间 - 否则 e
x
更改保持和模式缓冲区的内容并且... 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
标记开头... - 拉入
N
ext 行,直到当前行结束$
- 如果必须执行上述操作,它将删除结尾的
\
反斜杠和\n
ewline 字符。 - 然后它删除尾随
$
- 查找任何
\
后跟数字且前面没有另一个\
反斜杠的反斜杠,并插入零 - 搜索任何
'
单引号并将其加双引号 '
最后用单引号将整个字符串括起来
所以现在,当我这样做时:
printf %s\\n ././1* |
_sed_cesc_qt '\./\./'
我得到:
'././1'
'././1\n1'
'././1\n1\n1'
'././10'
'././10\n10'
'././10\n10\n10'
剩下的就很简单了。这取决于字符串将解析的事实././
,但它只会出现在find/grep
每个路径名开头的输出中 - 所以它成为我的$1
标记。
我-exec grep
从find
并指定-l
它输出包含正则表达式的文件的文件名。
我调用该函数并获取其输出。
然后,我\
反斜杠转义其输出中的每个字符xargs
。
我将printf
一个脚本写入该|pipe
文件 - 我将其.dot
源为/dev/fd/0
.我将f
变量定义为其当前参数(我的路径名),并将cat
该$f
参数传递给<<
heredocument,该参数被馈送到sed
,并sed
写回源文件。
这可能涉及临时文件 - 这取决于您的 shell。bash
并zsh
会为每个heredocument写出一个临时文件 - 但他们也会清理它们。dash
另一方面,只会将此处的文档写入匿名的|pipe
.
但重要的是,文件在被写入之前必须被完全读取 - 这就是文档和命令替换的工作原理。