将字符串
foo
替换为bar
?递归地对子目录执行相同的操作?匹配另一个字符串吗?
仅当在特定上下文中找到该字符串时才替换吗?
如果该字符串位于某个行号上就替换吗?
用相同的替换项替换多个字符串
用不同的替换项替换多个字符串
#1 楼
1.在当前目录的所有文件中,将所有出现的一个字符串替换为另一个字符串:这些情况适用于以下情况:您知道该目录仅包含常规文件,并且要处理所有非隐藏文件。如果不是这种情况,请使用2中的方法。此答案中的所有
sed
解决方案均假定为GNU sed
。如果使用FreeBSD或OS / X,请将-i
替换为-i ''
。另请注意,将-i
开关与任何版本的sed
一起使用都会对文件系统产生一定的安全影响,并且在您计划以任何方式分发的任何脚本中都不建议这样做。非递归文件仅在此目录中:
sed -i -- 's/foo/bar/g' *
perl -i -pe 's/foo/bar/g' ./*
(对于文件名以
perl
或空格结尾的文件,|
将会失败)。此子目录和所有子目录中的递归常规文件(包括隐藏文件)
find . -type f -exec sed -i 's/foo/bar/g' {} +
如果您使用的是zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(如果列表太大,可能会失败,请参见
zargs
可以解决)。Bash无法直接检查常规文件,需要循环(花括号避免全局设置选项):
( shopt -s globstar dotglob;
for file in **; do
if [[ -f $file ]] && [[ -w $file ]]; then
sed -i -- 's/foo/bar/g' "$file"
fi
done
)
文件是实际选择的文件(-f)并且可写(-w)。
2.。仅当文件名与另一个字符串匹配/具有特定扩展名/具有特定类型等时才替换:
非递归,仅此目录中的文件:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz
sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
此子目录和所有子目录中的递归常规文件
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
如果您使用的是bash(花括号,请避免全局设置选项):
( shopt -s globstar dotglob
sed -i -- 's/foo/bar/g' **baz*
sed -i -- 's/foo/bar/g' **.baz
)
如果您使用的是zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.)
sed -i -- 's/foo/bar/g' **/*.baz(D.)
--
可以告诉sed
在命令行中不再给出任何标志。这对于防止以-
开头的文件名很有用。如果文件是某种类型的文件,例如,可执行文件(有关更多选项,请参阅
man find
):find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
: sed -i -- 's/foo/bar/g' **/*(D*)
3。仅当在特定上下文中找到该字符串时才替换该字符串
仅当同一行中稍后存在
foo
时,才用bar
替换baz
: sed -i 's/foo\(.*baz\)/bar/' file
在
sed
中,使用\( \)
保存括号中的内容,然后可以使用
进行访问。此主题有很多变体,要了解有关此类正则表达式的更多信息,请参见此处。仅当在输入的3d列(字段)上找到
foo
时,才用bar
替换foo
文件(假设用空格分隔的字段): gawk -i inplace '{gsub(/foo/,"baz",); print}' file
(需要
gawk
4.1.0或更高版本)。对于不同的字段只需使用
$N
,其中N
是感兴趣字段的编号。对于不同的字段分隔符(在此示例中为:
),请使用: gawk -i inplace -F':' '{gsub(/foo/,"baz",);print}' file
使用
perl
的另一种解决方案: awk
解决方案将影响文件中的间距(删除开头和结尾的空格,并将空格序列转换为匹配的行中的一个空格字符)。对于不同的字段,请使用perl
,其中$F[N-1]
是您想要的字段编号,对于不同的字段分隔符,请使用(N
将输出字段分隔符设置为$"=":"
): perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo
仅在第4行用
:
替换foo
: perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
4。多种替换操作:用不同的字符串替换
您可以组合使用
bar
命令:将用sed
替换sed 's/foo/bar/g; s/bar/baz/g'
)。或Perl命令
sed -i '4s/foo/bar/g' file
gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
perl -i -pe 's/foo/bar/g if $.==4' file
如果有大量模式,则为更容易在
foo
脚本文件中保存模式及其替换: sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
或者,如果您有太多的模式对不可行,可以从文件中读取模式对(每行两个空格分隔的模式$ pattern和$ replacement): br />
对于一长串的模式和大型数据文件,这将非常慢,因此您可能希望读取模式并从中创建
baz
脚本。以下假设<<!> space <!>>定界符分隔了文件sed
中一行一行出现的MATCH <<!> space <!>> REPLACE对的列表: perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
以上格式在很大程度上是任意的,例如,在MATCH或REPLACE中都不允许使用<<!>空格<!>>。该方法非常通用:基本上,如果您可以创建一个看起来像
sed
脚本的输出流,则可以通过将patterns.txt
的脚本文件指定为sed
stdin将该源流作为sed
脚本。您可以以类似的方式组合和连接多个脚本:
#! /usr/bin/sed -f
s/foo/bar/g
s/baz/zab/g
POSIX
sed
会将所有脚本按出现在屏幕上的顺序连接在一起。命令行。所有这些都不需要以-
ewline结尾。sed
可以以相同的方式工作: while read -r pattern replacement; do
sed -i "s/$pattern/$replacement/" file
done < patterns.txt
固定工作-strings作为模式,转义正则表达式元字符是一个好习惯。您可以轻松地做到这一点:
sed 's| *\([^ ]*\) *\([^ ]*\).*|s///g|' <patterns.txt |
sed -f- ./editfile >outfile
5。多次替换操作:用同一字符串替换多个模式
将
\n
,grep
或foo
中的任何一个替换为bar
SOME_PIPELINE |
sed -e'#some expression script' \
-f./script_file -f- \
-e'#more inline expressions' \
./actual_edit_file >./outfile
或
sed -e'#generate a pattern list' <in |
grep -f- ./grepped_file
评论
@StéphaneChazelas感谢您的编辑,确实确实修复了一些问题。但是,请不要删除与bash相关的信息。并非每个人都使用zsh。一定要添加zsh信息,但是没有理由删除bash内容。另外,我知道使用shell进行文本处理不是理想的,但是在某些情况下需要使用shell。我在原始脚本的更好版本中进行了编辑,它将创建一个sed脚本,而不是实际使用shell循环进行解析。例如,如果您有数百对模式,这将很有用。
– terdon♦
2015年1月16日15:10
@terdon,您的bash错误。 4.3之前的bash下降时将遵循符号链接。 bash也没有等效于(。)globlob限定符,因此不能在此处使用。 (您也缺少一些)。 for循环不正确(缺少-r),意味着在文件中进行了多次传递,并且与sed脚本相比没有任何好处。
–StéphaneChazelas
15年1月16日在15:16
@terdon在sed -i之后和替代命令之前指示什么?
–极客
2015年9月28日在11:29
@Geek是POSIX的东西。它表示选项的结尾,并允许您传递以-开头的参数。使用它可以确保命令可以在名称为-foo的文件上使用。没有它,-f将被解析为一个选项。
– terdon♦
2015年9月28日在11:42
在git仓库中执行一些递归命令时要非常小心。例如,此答案的第1部分中提供的解决方案实际上将修改.git目录中的内部git文件,并实际上使您的结帐混乱。最好按名称在特定目录内/上操作。
–手枪
16-4-19的14:44
#2 楼
rpl是一个很好的替代Linux工具,它最初是为Debian项目编写的,因此它可以在任何Debian派生发行版中与apt-get install rpl
一起使用,也可以与其他工具一起使用,但是可以从SourceForge下载tar.gz
文件。最简单的使用示例:
$ rpl old_string new_string test.txt
请注意,如果字符串包含空格,则应将其用引号引起来。默认情况下,
rpl
使用大写字母,但不使用完整单词,但是您可以使用选项-i
(忽略大小写)和-w
(整个单词)来更改这些默认值。您还可以指定多个文件: $ rpl -i -w "old string" "new string" test.txt test2.txt
,或者甚至在目录中指定要搜索的扩展名(
-x
)或递归搜索(-R
): $ rpl -x .html -x .txt -R old_string new_string test*
也可以搜索/ q在带有
-p
(提示)选项的交互模式下:输出显示替换的文件/字符串的数量和搜索的类型(区分大小写/区分大小写),但是使用
-q
(安静模式)选项,甚至更详细,使用-v
(详细模式)选项列出包含每个文件和目录匹配项的行号。其他值得记住的选项是
-e
(荣誉转义符),它允许regular expressions
,因此您还可以搜索标签(\t
),换行(\n
)等。您可以使用-f
来强制授予权限(当然,仅当用户具有写权限时),并且可以使用-d
保留修改时间。)最后,如果您不确定会发生什么,请使用
-s
(模拟模式) )。评论
在反馈和简单性方面比sed好得多。我只是希望它允许对文件名起作用,然后再按原样进行。
– Kzqai
16 Dec 23'在17:12
我喜欢-s(模拟模式):-)
– m3nda
18年6月10日在11:08
比sed好得多。发誓
– Marc Compere
8月3日17:26
非常感谢。 sed对于简单的替换很好,但对于更复杂和更长的字符串却很糟糕
–yeah22
9月15日19:02
对于macOS,可从MacPorts获得rpl。
– Murray
9月23日20:49
#3 楼
如何搜索和替换多个文件建议:还可以使用find和sed,但是我发现这行perl很好用。 />
perl -pi -w -e 's/search/replace/g;' *.php
-e表示执行以下代码行。
-i表示就地编辑
-w写警告
-p遍历输入文件,在将脚本应用到输入文件后打印每一行。
我最好的结果是使用perl和grep(确保文件具有搜索表达式)
perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )
#4 楼
您可以在Ex模式下使用Vim:在当前目录的所有文件中用BRA替换字符串ALF?
for CHA in *
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
对子目录递归执行相同的操作?
find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'
replace
for CHA in *.txt
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
仅当在特定上下文中找到字符串时才替换?
ex -sc 'g/DEL/s/ALF/BRA/g' -cx file
如果字符串在特定行号上,该替换吗?
/>
ex -sc '2s/ALF/BRA/g' -cx file
用相同的替换项替换多个字符串
ex -sc '%s/\vALF|ECH/BRA/g' -cx file
>
用不同的替换项替换多个字符串
ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
#5 楼
我使用了以下命令:grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
列出所有包含
old_string
的文件。在结果中用空格替换换行符(以便文件列表可以被送入sed
。在这些文件上运行
sed
,用new替换旧字符串。更新:以上结果将对包含空格的文件名失败,而应使用:
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
评论
请注意,如果您的任何文件名包含空格,制表符或换行符,则此操作将失败。使用grep --null -lr“ old_string” | xargs --null sed -i's / old_string / new_string / g'将使其处理任意文件名。
– terdon♦
15-10-26在17:07
多谢你们。添加了更新并保留了旧代码,这是一个有趣的警告,对不知道此行为的人可能有用。
–o_o_o--
15-10-26在20:59
#6 楼
从用户的角度来看,可以很好地完成此工作的漂亮而简单的Unix工具是qsubst
。例如,% qsubst foo bar *.c *.h
将在我所有的C文件中将
foo
替换为bar
。 qsubst
的一个很好的功能是执行查询替换,即它将向我显示foo
的每次出现并询问我是否要替换它。 [您可以无条件地替换-go
选项(没有要求),还有其他选项,例如-w
,如果您只想在整个单词时替换foo
。] 如何获取它:
qsubst
由der Mouse(来自McGill)发明,并于1987年8月发布到comp.unix.sources 11(7)。存在更新的版本。例如,NetBSD版本qsubst.c,v 1.8 2004/11/01
可以在我的Mac上编译并完美运行。 #7 楼
ripgrep(命令名称rg
)是grep
工具,但也支持搜索和替换。 $ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky
$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky
rg
不支持就地选项,因此您必须自己进行操作 $ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky
有关正则表达式语法和功能,请参阅Rust regex文档。 。
-P
开关将启用PCRE2风格。 rg
默认支持Unicode。像
$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat
$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg '\p{L}+' -r '(grep
)'
(fox):(αλεπού),(eagle):(αετός)
$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.
$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|\w+' -r '[-F
]'
[car] bat [cod] map
一样,sed
选项将允许匹配固定的字符串,这是一个方便的选项我觉得$ printf '2.3/[4]*6\nfoo\n5.3-[4]*9\n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29
也应该实现。 -U
另一个方便的选择是
$ # (?s) flag will allow . to match newline characters as well
$ printf '42\nHi there\nHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi Day
,它可以实现多行匹配 rg
$ # same as: sed -E 's/\w+(\r?)$/123/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123
也可以处理dos样式的文件 rg
sed
的另一个优点是它可能比$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real 0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real 0m0.007s
$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt
$ time LC_ALL=C sed 's/\bcat\b/dog/g' big.txt > f1
real 0m0.060s
$ time rg --passthru '\bcat\b' -r 'dog' big.txt > f2
real 0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical
$ time LC_ALL=C sed -E 's/\b(\w+)(\s+)+\b//g' big.txt > f1
real 0m0.725s
$ time rg --no-unicode --passthru -wP '(\w+)(\s+)+' -r '' big.txt > f2
real 0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
快。q4312079q
#8 楼
我需要一些可以提供空运行选项并可以与全局递归协同工作的东西,并尝试使用awk
和sed
进行处理后,我放弃了,而是在python中进行了替换。脚本搜索递归地将所有与正则表达式的glob模式匹配的文件(例如
--glob="*.html"
)并替换为替换的正则表达式: > 每个长选项(例如
find_replace.py [--dir=my_folder] \
--search-regex=<search_regex> \
--replace-regex=<replace_regex> \
--glob=[glob_pattern] \
--dry-run
)都有一个对应的短选项,即--search-regex
。使用-s
运行以查看所有选项。例如,这会将所有日期从-h
翻转到2017-12-31
:=“ lang-bash prettyprint-override”>
31-12-2017
python replace.py --glob=myfile.txt \
--search-regex="(\d{4})-(\d{2})-(\d{2})" \
--replace-regex="--" \
--dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re
import argparse
def find_replace(cfg):
search_pattern = re.compile(cfg.search_regex)
if cfg.dry_run:
print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')
for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
for filename in fnmatch.filter(files, cfg.glob):
if cfg.print_parent_folder:
pardir = os.path.normpath(os.path.join(path, '..'))
pardir = os.path.split(pardir)[-1]
print('[%s]' % pardir)
filepath = os.path.join(path, filename)
# backup original file
if cfg.create_backup:
backup_path = filepath + '.bak'
while os.path.exists(backup_path):
backup_path += '.bak'
print('DBG: creating backup', backup_path)
shutil.copyfile(filepath, backup_path)
with open(filepath) as f:
old_text = f.read()
all_matches = search_pattern.findall(old_text)
if all_matches:
print('Found {} matches in file {}'.format(len(all_matches), filename))
new_text = search_pattern.sub(cfg.replace_regex, old_text)
if not cfg.dry_run:
with open(filepath, "w") as f:
print('DBG: replacing in file', filepath)
f.write(new_text)
else:
for idx, matches in enumerate(all_matches):
print("Match #{}: {}".format(idx, matches))
print("NEW TEXT:\n{}".format(new_text))
elif cfg.verbose:
print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='''DESCRIPTION:
Find and replace recursively from the given folder using regular expressions''',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''USAGE:
{0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
'''.format(os.path.basename(sys.argv[0])))
parser.add_argument('--dir', '-d',
help='folder to search in; by default current folder',
default='.')
parser.add_argument('--search-regex', '-s',
help='search regex',
required=True)
parser.add_argument('--replace-regex', '-r',
help='replacement regex',
required=True)
parser.add_argument('--glob', '-g',
help='glob pattern, i.e. *.html',
default="*.*")
parser.add_argument('--dry-run', '-dr',
action='store_true',
help="don't replace anything just show what is going to be done",
default=False)
parser.add_argument('--create-backup', '-b',
action='store_true',
help='Create backup files',
default=False)
parser.add_argument('--verbose', '-v',
action='store_true',
help="Show files which don't match the search regex",
default=False)
parser.add_argument('--print-parent-folder', '-p',
action='store_true',
help="Show the parent info for debug",
default=False)
config = parser.parse_args(sys.argv[1:])
find_replace(config)
是该版本的更新版本脚本,以不同的颜色突出显示搜索词和替换词。评论
我不明白您为什么要做这么复杂的事情。要进行递归,请使用bash的(或与您的外壳等效的)globstar选项和** globs或find。对于空运行,只需使用sed。除非您使用-i选项,否则它将不会进行任何更改。对于备份,请使用sed -i.bak(或perl -i .bak);对于不匹配的文件,请使用grep PATTERN文件||回声文件。而且为什么在世界上您会用python扩展glob而不是让shell这样做呢?为什么是script.py --glob = foo *而不是script.py foo *?
– terdon♦
17年11月23日在9:34
我的原因很简单:(1)首先,易于调试; (2)仅在支持社区的情况下使用单一的文档齐全的工具(3)不熟悉sed和awk,不愿意花费更多的时间来掌握它们,(4)可读性,(5)此解决方案也可用于非posix系统(不是我需要的,而是其他人)。
–ccpizza
17年11月23日在12:59
#9 楼
这里我用grep
说,如果它要(在最后,所以我可以算发改线,以及更换的次数,输出)更改一个文件,然后我用sed
实际更改该文件。请注意以下Bash函数最末端的一行sed
用法:replace_str
Bash函数用法:
gs_replace_str "regex_search_pattern" "replacement_string" "file_path"
bash函数:
# Usage: `gs_replace_str "regex_search_pattern" "replacement_string" "file_path"`
gs_replace_str() {
REGEX_SEARCH=""
REPLACEMENT_STR=""
FILENAME=""
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文件中。关闭并重新打开终端,然后使用它。
示例:
将
do
替换为bo
,以使“正在执行”变为“正在执行”(我知道,我们应该修复拼写错误错误未创建它们:)):$ 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
输出屏幕截图:
参考文献:<登记/>
https://superuser.com/questions/339522/counting-total-number-of-matches-with-grep-instead-of-just-how-many-lines-match / 339523#339523
https://stackoverflow.com/questions/12144158/how-to-check-if-sed-has-changed-a-file/61238414#61238414
评论
这旨在作为该主题的规范问答(请参阅此元讨论),请随时在下面编辑我的答案或添加您自己的答案。很棒的grep -rl(然后通过管道传输到sed)在这里回答:unix.stackexchange.com/questions/472476 / ...