ter*_*don 879 sed awk perl text-processing
根据某些搜索条件替换文件中的字符串是一项非常常见的任务。我怎样才能
foo用bar在当前目录下的所有文件?ter*_*don 1194
这些适用于您 知道目录仅包含常规文件并且您想要处理所有非隐藏文件的情况。如果不是这种情况,请使用 2 中的方法。
sed此答案中的所有解决方案都假定 GNU sed。如果使用 FreeBSD 或 macOS,请替换-i为-i ''. 另请注意,将-iswitch 与任何版本一起使用sed都有一定的文件系统安全隐患,并且在您计划以任何方式分发的任何脚本中都是不可取的。
非递归,仅此目录中的文件:
sed -i -- 's/foo/bar/g' *
perl -i -pe 's/foo/bar/g' ./*
Run Code Online (Sandbox Code Playgroud)
此目录和所有子目录中的递归常规文件(包括隐藏文件)
find . -type f -exec sed -i 's/foo/bar/g' {} +
Run Code Online (Sandbox Code Playgroud)
如果您使用的是 zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
Run Code Online (Sandbox Code Playgroud)
(如果列表太大,可能会失败,请参阅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
)
Run Code Online (Sandbox Code Playgroud)
当文件是实际文件 (-f) 并且它们是可写的 (-w) 时,就会选择这些文件。
非递归,仅此目录中的文件:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz
sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
Run Code Online (Sandbox Code Playgroud)
此目录和所有子目录中的递归常规文件
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
Run Code Online (Sandbox Code Playgroud)
如果您使用 bash(大括号避免全局设置选项):
( shopt -s globstar dotglob
sed -i -- 's/foo/bar/g' **baz*
sed -i -- 's/foo/bar/g' **.baz
)
Run Code Online (Sandbox Code Playgroud)
如果您使用的是 zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.)
sed -i -- 's/foo/bar/g' **/*.baz(D.)
Run Code Online (Sandbox Code Playgroud)
该--用来告诉sed没有更多的旗帜将在命令行中给出。这对于防止以 开头的文件名很有用-。
如果文件是某种类型的,例如,可执行文件(man find有关更多选项,请参阅):
find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
Run Code Online (Sandbox Code Playgroud)
zsh:
sed -i -- 's/foo/bar/g' **/*(D*)
Run Code Online (Sandbox Code Playgroud)
更换foo用bar只有在有一个baz后来在同一行:
sed -i 's/foo\(.*baz\)/bar\1/' file
Run Code Online (Sandbox Code Playgroud)
在 中sed, using\( \)保存括号中的任何内容,然后您可以使用\1. 此主题有许多变体,要了解有关此类正则表达式的更多信息,请参阅此处。
替换foo与bar只有当foo在输入文件的三维列(字段)被发现(假设空白分隔字段):
gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
Run Code Online (Sandbox Code Playgroud)
(需要gawk4.1.0 或更新版本)。
对于不同的字段,只需使用$NwhereN是感兴趣的字段的编号。对于不同的字段分隔符(:在本例中)使用:
gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Run Code Online (Sandbox Code Playgroud)
使用的另一种解决方案perl:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo
Run Code Online (Sandbox Code Playgroud)
注意:awk和perl解决方案都会影响文件中的间距(删除前导和尾随空格,并将空格序列转换为匹配行中的一个空格字符)。对于不同的字段,使用$F[N-1]whereN是您想要的字段编号,并使用不同的字段分隔符($"=":"将输出字段分隔符设置为:):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
Run Code Online (Sandbox Code Playgroud)
仅在第 4 行替换foo为bar:
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
Run Code Online (Sandbox Code Playgroud)
您可以组合sed命令:
sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Run Code Online (Sandbox Code Playgroud)
请注意,顺序很重要(sed 's/foo/bar/g; s/bar/baz/g'将替换foo为baz)。
或 Perl 命令
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Run Code Online (Sandbox Code Playgroud)
如果您有大量模式,将您的模式及其替换保存在sed脚本文件中会更容易:
#! /usr/bin/sed -f
s/foo/bar/g
s/baz/zab/g
Run Code Online (Sandbox Code Playgroud)
或者,如果您有太多的模式对使上述操作不可行,您可以从文件中读取模式对(两个空格分隔的模式,每行 $pattern 和 $replacement):
while read -r pattern replacement; do
sed -i "s/$pattern/$replacement/" file
done < patterns.txt
Run Code Online (Sandbox Code Playgroud)
对于长的模式列表和大型数据文件,这将非常慢,因此您可能想要读取模式并sed从它们创建脚本。下面假设<<!>space<!>>分隔符分隔MATCH<<!>space<!>>REPLACE对的列表,文件中每行出现一个patterns.txt:
sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <patterns.txt |
sed -f- ./editfile >outfile
Run Code Online (Sandbox Code Playgroud)
上面的格式在很大程度上是任意的,例如,在MATCH或REPLACE中都不允许有<<!>space<!>>。不过,该方法非常通用:基本上,如果您可以创建一个看起来像脚本的输出流,那么您可以通过将的脚本文件指定为stdin来将该流作为脚本源。sedsedsed-
您可以以类似的方式组合和连接多个脚本:
SOME_PIPELINE |
sed -e'#some expression script' \
-f./script_file -f- \
-e'#more inline expressions' \
./actual_edit_file >./outfile
Run Code Online (Sandbox Code Playgroud)
POSIXsed将按照它们在命令行中出现的顺序将所有脚本连接成一个。这些都不需要以\n猫线结束。
grep 可以以同样的方式工作:
sed -e'#generate a pattern list' <in |
grep -f- ./grepped_file
Run Code Online (Sandbox Code Playgroud)
使用固定字符串作为模式时,转义正则表达式元字符是一种很好的做法。你可以很容易地做到这一点:
sed 's/[]$&^*\./[]/\\&/g
s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
' <patterns.txt |
sed -f- ./editfile >outfile
Run Code Online (Sandbox Code Playgroud)
更换所有的foo,bar或baz与foobar
sed -Ei 's/foo|bar|baz/foobar/g' file
Run Code Online (Sandbox Code Playgroud)
或者
perl -i -pe 's/foo|bar|baz/foobar/g' file
Run Code Online (Sandbox Code Playgroud)
Fra*_*ran 98
一个好的[R é PL acement Linux的工具是RPL,最初为Debian项目写的,所以它可与apt-get install rpl任何Debian的发行版导出,并且可以为别人,否则你可以下载tar.gz从文件SourceForge上。
最简单的使用示例:
$ rpl old_string new_string test.txt
Run Code Online (Sandbox Code Playgroud)
请注意,如果字符串包含空格,则应将其括在引号中。默认情况下rpl处理大写字母但不处理完整单词,但您可以使用选项-i(ignore case) 和-w(whole words)更改这些默认值。您还可以指定多个文件:
$ rpl -i -w "old string" "new string" test.txt test2.txt
Run Code Online (Sandbox Code Playgroud)
甚至指定分机(-x)来搜索,甚至搜索递归(-R)目录:
$ rpl -x .html -x .txt -R old_string new_string test*
Run Code Online (Sandbox Code Playgroud)
您还可以使用(提示)选项在交互模式下搜索/替换-p:
输出显示替换的文件/字符串的数量和搜索类型(大小写/敏感,整个/部分单词),但它可以使用-q(安静模式)选项静音,或者更详细,列出包含的行号每个文件和目录与-v(详细模式)选项的匹配。
其他值得记住的选项是-e(honor e scapes) 允许regular expressions,因此您还可以搜索选项卡 ( \t)、新行 ( \n) 等。您可以使用-f,以强制许可(当然,只有当用户有写权限),并-d保存修改times`)。
最后,如果您不确定会发生什么,请使用-s(模拟模式)。
Ale*_*elo 28
如何搜索和替换多个文件建议:
您也可以使用 find 和 sed,但我发现这行 perl 效果很好。
Run Code Online (Sandbox Code Playgroud)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' )
Run Code Online (Sandbox Code Playgroud)
o_o*_*o-- 18
我用过这个:
grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
Run Code Online (Sandbox Code Playgroud)
列出所有包含old_string.
用空格替换结果中的换行符(以便文件列表可以输入到sed.
sed在这些文件上运行以用新的替换旧的字符串。
更新:上述结果将在包含空格的文件名上失败。相反,使用:
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
Ste*_*nny 18
你可以在 Ex 模式下使用 Vim:
在当前目录的所有文件中用 BRA 替换字符串 ALF?
for CHA in *
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
Run Code Online (Sandbox Code Playgroud)
对子目录递归执行相同的操作吗?
find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'
Run Code Online (Sandbox Code Playgroud)
仅当文件名匹配另一个字符串时才替换?
for CHA in *.txt
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
Run Code Online (Sandbox Code Playgroud)
仅当在特定上下文中找到字符串时才替换?
ex -sc 'g/DEL/s/ALF/BRA/g' -cx file
Run Code Online (Sandbox Code Playgroud)
替换字符串是否在某个行号上?
ex -sc '2s/ALF/BRA/g' -cx file
Run Code Online (Sandbox Code Playgroud)
用相同的替换替换多个字符串
ex -sc '%s/\vALF|ECH/BRA/g' -cx file
Run Code Online (Sandbox Code Playgroud)
用不同的替换替换多个字符串
ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
Run Code Online (Sandbox Code Playgroud)
从用户的角度来看,一个完美而简单的 Unix 工具是qsubst. 例如,
% qsubst foo bar *.c *.h
Run Code Online (Sandbox Code Playgroud)
将替换foo为bar我所有的 C 文件。一个很好的功能是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 上可以完美地编译和运行。
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
有关正则表达式语法和功能,请参阅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 '($0)'
(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 '[$0]'
[car] bat [cod] map
Run Code Online (Sandbox Code Playgroud)
就像grep,该-F选项将允许匹配固定字符串,我觉得也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
Run Code Online (Sandbox Code Playgroud)
另一个方便的选项是-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
Run Code Online (Sandbox Code Playgroud)
rg 也可以处理 dos 风格的文件
$ # same as: sed -E 's/\w+(\r?)$/123\1/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123
Run Code Online (Sandbox Code Playgroud)
的另一个优点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+\1)+\b/\1/g' big.txt > f1
real 0m0.725s
$ time rg --no-unicode --passthru -wP '(\w+)(\s+\1)+' -r '$1' big.txt > f2
real 0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
Run Code Online (Sandbox Code Playgroud)
我需要一些可以提供空运行选项的东西,并且可以递归地使用 glob 工作,在尝试使用它之后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
Run Code Online (Sandbox Code Playgroud)
每个多头选项,例如,--search-regex都有一个相应的空头选项,即-s。运行-h以查看所有选项。
例如,这会将所有日期从2017-12-31to翻转31-12-2017:
python replace.py --glob=myfile.txt \
--search-regex="(\d{4})-(\d{2})-(\d{2})" \
--replace-regex="\3-\2-\1" \
--dry-run --verbose
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
Here是脚本的更新版本,它用不同的颜色突出显示搜索词和替换。
| 归档时间: |
|
| 查看次数: |
1706121 次 |
| 最近记录: |