我有一个非常大的文件,其中包含以下几行:
start :234
modify 123 directory1/directory2/file.txt
delete directory3/file2.txt
modify 899 directory4/file3.txt
每个块以模式#34开始:#"并以空行结束。在块内,每一行都以"修改#"或"删除"。
我需要修改每一行的路径,特别是在前面附加一个目录。我只想使用一般的正则表达式覆盖整个文件,以便#34;修改#"或"删除",但由于该文件中的大量其他数据,可能会有其他匹配这种有些模糊的模式。所以我需要使用多行匹配来查找整个块,然后在该块中执行编辑。这可能会在一次通过中导致大约10,000次修改,因此我也尝试将执行时间缩短到不到30分钟。
我目前的尝试是sed one-liner:
sed '/^start :[0-9]\+$/ { :a /^[modify|delete] .*$/ { N; ba }; s/modify [0-9]\+ /&Appended_DIR\//g; s/delete /&Appended_DIR\//g }' file_to_edit
这是为了找到"开始"行,循环,而行以"修改"或者"删除,"然后应用sed替换。
但是,当我执行此命令时,不会进行任何更改,并且输出与原始文件相同。
我所形成的命令是否存在问题?在perl中这会更容易/更有效吗?任何帮助将不胜感激,我将澄清我能在哪里。
答案 0 :(得分:0)
我认为用perl
特别是因为您可以按照记录工作'设置$/
- 如果您的记录由空行分隔,请将其设置为\n\n
。
这样的事情:
#!/usr/bin/env perl
use strict;
use warnings;
local $/ = "\n\n";
while (<>) {
#multi-lines of text one at a time here.
if (m/^start :\d+/) {
s/(modify \d+)/$1 Appended_DIR\//g;
s/(delete) /$1 Appended_DIR\//g;
}
print;
}
循环的每次迭代都会选出一个空行分隔的块,检查它是否以模式开头,如果它是,则应用一些变换。
它会通过管道或STDIN
从myscript.pl somefile
获取数据。
输出到STDOUT
,您可以按正常方式重定向。
以这种方式处理文件的限制因素通常是:
模式越复杂,特别是如果它正在进行变量匹配,正则表达式引擎必须做的回溯越多,这可能会变得昂贵。您的转换很简单,因此打包它们并没有太大的区别,而您的限制因素可能就是磁盘IO。
(如果你想进行就地编辑,可以使用这种方法)
如果 - 如上所述 - 你不能依赖记录分隔符,那么你可以使用的是perl
s range operator(其他答案已经这样做,我&#39; m只是稍微扩展一下:
#!/usr/bin/env perl
use strict;
use warnings;
while (<>) {
if ( /^start :/ .. /^$/)
s/(modify \d+)/$1 Appended_DIR\//g;
s/(delete) /$1 Appended_DIR\//g;
}
print;
}
我们不再更改$/
,因此它仍然默认为“每行”。我们添加的是一个范围运算符,它测试&#34;我目前是否在这两个正则表达式中#34;当你点击&#34;开始&#34;时切换true
和false
当你点击一个空白行(假设你想要停止的地方?)。
如果此条件为真,则应用模式转换,如果不是,则忽略并进行打印。
答案 1 :(得分:0)
我还建议使用perl
。虽然我会尝试将其保持为单线形式:
perl -i -pe 'if ( /^start :/ .. /^$/){s/(modify [0-9]+ )/$1Append_DIR\//;s/(delete )/$1Append_DIR\//; }' file_to_edit
或者你可以使用stdout的重定向:
perl -pe 'if ( /^start :/ .. /^$/){s/(modify [0-9]+ )/$1Append_DIR\//;s/(delete )/$1Append_DIR\//; }' file_to_edit > new_file
答案 2 :(得分:0)
使用gnu sed(使用BRE语法):
sed '/^start :[0-9][0-9]*$/{:a;n;/./{s/^\(modify [0-9][0-9]* \|delete \)/\1NewDir\//;ba}}' file.txt
这里的方法不是存储整个块并继续进行替换。在这里,当找到块的开始时,下一行被加载到模式空间中,如果该行不为空,则执行替换并加载下一行等,直到块结束。
注意:gnu sed具有可用的替换功能|
,对于其他一些sed版本可能不是这种情况。
使用awk的方式:
awk '/^start :[0-9]+$/,/^$/{if ($1=="modify"){$3="newdirMod/"$3;} else if ($1=="delete"){$2="newdirDel/"$2};}{print}' file.txt
答案 3 :(得分:0)
sed的模式范围是你的朋友:
"Condition": {
"StringLike": {
"aws:Referer": [
"http://my_bucket.s3.amazonaws.com/*",
"https://my_bucket.s3.amazonaws.com/*",
"http://www.example.com/*",
"https://www.example.com/*",
]
}
}
这个技巧的核心是sed -r '/^start :[0-9]+$/,/^$/ s/^(delete |modify [0-9]+ )/&prepended_dir\//' filename
,它被读作一个条件,在该条件下执行它后面的/^start :[0-9]+$/,/^$/
命令。如果sed当前发现自己位于第一行匹配开始模式s
并且最后一个匹配结束模式^start:[0-9]+$
(空行)的行中,则条件为真。 ^$
用于扩展正则表达式语法(旧{BSD seds的-r
),这使得正则表达式更易于编写。
答案 4 :(得分:0)
这在Perl中非常简单,并且可能比sed等价物快得多。
此单行程序在行开头出现Appended_DIR/
或modify 999
后插入delete
。它使用范围运算符将这些更改限制为以start :999
开头并以不包含可打印字符的行结束的文本块
perl -pe"s<^(?:modify\s+\d+|delete)\s+\K><Appended_DIR/> if /^start\s+:\d+$/ .. not /\S/" file_to_edit
答案 5 :(得分:0)
好悲伤。 sed用于单个行上的简单替换,即全部。一旦开始使用s,g和p以外的构造(使用-n),您使用的是错误的工具。只需使用awk:
awk '
/^start :[0-9]+$/ { inBlock=1 }
inBlock { sub(/^(modify [0-9]+|delete) /,"&Appended_DIR/") }
/^$/ { inBlock=0 }
{ print }
' file
start :234
modify 123 Appended_DIR/directory1/directory2/file.txt
delete Appended_DIR/directory3/file2.txt
modify 899 Appended_DIR/directory4/file3.txt
你可以通过各种方式在awk中完成上述操作,但为了清晰起见,我用上面的方式编写了它,因为我认为你不熟悉awk但是应该没有麻烦,因为它重用您自己的sed脚本regexp和替换文本。