我有一find -exec grep
对命令,它们组合在一起路径/文件名.扩展名:ln#:行内容在一行上。我想在第二个文件中将该行分成两行连续的行。连续的行是:
path/filename/ext:ln#
contents of the line itself
我可以编写一个程序来完成这项工作,但我想知道是否有一个命令可以做到这一点?
答案1
您的问题以及我的理解
您的问题目前缺乏输入和期望输出的具体示例,因此我将尝试回答您的答案据我所知,并在您提供更多信息时进行相应的编辑。
我现在理解你的问题是,你正在进行以下一些事情:
find /path/to/directory -exec grep -H -n 'SomeString' {} \;
产生的结果如下:
$ find /home/$USER/fortesting -type f -exec grep -H -n 'HelloWorld' {} \;
/home/serg/fortesting/file3:1:HelloWorld
/home/serg/fortesting/file1:4:HelloWorld
或者一般来说/path/to/file:lineNumber:String
可能的解决方案
非常恰当地说,这是一项工作awk
:您有 3 个由冒号(字段分隔符)分隔的字段,转换为 awk 代码awk -F":" '{printf $1 FS $2 FS "\n" $3 "\n" }'
因此我们可以执行以下操作:
$ find /home/$USER/fortesting -type f -exec grep -H -n 'HelloWorld' {} \; | awk -F ":" '{printf $1 FS $2 FS "\n" $3 "\n" }'
/home/xieerqi/fortesting/file3:1:
HelloWorld
/home/xieerqi/fortesting/file1:4:
HelloWorld
现在,awk
是一个多功能工具;我们可以用“find -exec awk '(awk code here)'”来模仿输出find -exec grep
,它将已经将被处理,并节省管道。
考虑下面的代码:
$ find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"FNR"\n"$0 }' {} \;
/home/xieerqi/fortesting/file3:1
HelloWorld
/home/xieerqi/fortesting/file1:4
HelloWorld
更少的管道和内容正在被处理,因为他们被发现。此外,如果文件的名称中有冒号,此代码仍然会正确处理它,因为我们不依赖字段分隔符,而是打印变量FILENAME,后跟冒号,后跟FNR(当前输入文件中的输入记录号),以及用换行符分隔的找到的行。
效率
现在,让我们考虑文件数量增加时的效率。首先,我创建文件file1
到file1000
,然后我们用/usr/bin/time
它来测试每个版本的命令。
$ echo 'HelloWorld' | tee file{$(seq -s',' 1 1000)}
$ /usr/bin/time find /home/$USER/fortesting -type f -exec grep -H -n 'HelloWorld' {} \; | awk -F ":" '{printf $1 FS $2 FS "\n" $3 "\n" }' > /dev/null
0.04user 0.34system 0:03.09elapsed 12%CPU (0avgtext+0avgdata 2420maxresident)k
0inputs+0outputs (0major+113358minor)pagefaults 0swaps
$ /usr/bin/time find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"FNR"\n"$0 }' {} \; > /dev/null
0.82user 2.03system 0:04.25elapsed 67%CPU (0avgtext+0avgdata 2856maxresident)k
0inputs+0outputs (0major+145292minor)pagefaults 0swaps
因此,较长的版本似乎更高效,占用的时间和 CPU 百分比更少。
现在,有一个折衷方案——\;
改为+
:
/usr/bin/time find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"NR"\n"$0 }' {} +
操作员做什么+
?最大的区别是,它+
告诉 exec 列出尽可能多的文件作为输入到awk
命令中,而\;
makesawk
每次对找到的每一个文件进行调用。
$ /usr/bin/time find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"FNR"\n"$0 }' {} + > /dev/null
0.00user 0.02system 0:00.02elapsed 74%CPU (0avgtext+0avgdata 3036maxresident)k
0inputs+0outputs (0major+398minor)pagefaults 0swaps
嘿,快多了,对吧?虽然 CPU 占用仍然很大。
输出到另一个文件
至于输出到另一个文件,添加使用>
运算符进行重定向
答案2
sed 很容易做到这一点:
$ echo 'path/filename.ext:ln#:line contents' | sed -r 's/([^:]*:[^:]*):/\1\n/'
path/filename.ext:ln#
line contents
正则表达式([^:]*:[^:]*):
查找前两个以冒号分隔的字段并将它们保存在第 1 组中。替换文本\1\n
在两个字段后放置一个换行符。
改进
如果文件名本身包含冒号,这当然会产生错误的结果。正如steeldriver建议的那样,可以使用选项来避免这种情况,该-Z
选项grep
将在文件名后放置一个NUL字符,\x00
而不是冒号。例如:
grep -ZHn 'regex' * | sed -r 's/\x00([^:]*):/:\1\n/'
find
或者,如果需要以下功能:
find . -type f -exec grep -ZHn 'regex' {} + | sed -r 's/\x00([^:]*):/:\1\n/'
即使文件名或匹配的行中出现冒号,或者两者兼而有之,这也能起作用。