我使用的是 Linux Ubuntu 18.04 和 20.04。
Ripgrep ( rg
) 可以输出包含匹配项的文件的路径列表,如下所示:
# search only .txt files
rg 'my pattern to match' -g '*.txt' -l
# long form
rg 'my pattern to match' --glob '*.txt' --files-with-matches
输出将是:
path/to/file1.txt path/to/file2.txt path/to/file3.txt
ETC。
然后,我想在每个路径上运行另一个命令,例如tree $(dirname $PATH)
,以获取包含匹配文件的目录中所有文件的列表。我怎样才能做到这一点?
我觉得xargs
可能是答案的一部分?但是以xargs
这样的管道作为开始似乎只能处理最后打印的文件:
rg 'my pattern to match' -g '*.txt' -l | xargs -0 -I {} dirname {}
注意:如果您可以使用grep
Too 进行演示,那么对于没有 ripgrep 的人来说可能也很有用ripgrep
,尽管是 ripgrep超级容易安装。
参考:
答案1
在 GNU 系统上,可能是这样的:
rg -g '*.txt' -l0 'my pattern to match' | # list files NUL-delimited
xargs -r0 dirname -z -- | # takes dirnames
LC_ALL=C sort -zu | # remove duplicates
xargs -r0 tree --
请注意,如果 和 都dir/file.txt
匹配dir/subdir/file.txt
,您最终将tree
在 和 上dir
运行dir/subdir
,因此您将看到两次的内容dir/subdir
。
您的想法是正确的,使用xargs
which is 命令将字节字符串转换为参数列表以传递给命令,并使用-0
which 是传递任意参数列表的最可靠方法,但是:
xargs -0
期望输入的格式为参数列表由 NUL 字符(0 字节)分隔的格式。您需要-0
/--null
选项才能rg
以该格式打印文件列表。- GNU
dirname
每次调用可以处理多个参数,因此-I{}
我们不使用 ,而是将它们全部传递²。我们还希望在文件列表为空时根本-r
不调用,并且(也是 GNU 特定的)选项本身打印 NUL 分隔的目录。dirname
-z
dirname
dirname
- 由于
rg
不会为每个文件添加前缀,因此重要的是对我们将文件列表作为参数传递给的命令./
使用选项分隔符,以避免文件名中出现前导 s 的问题。--
-
简而言之,对于其值可以是任何非 NUL 字节序列(例如文件路径或任意命令参数)的列表,您希望使用 NUL 分隔的记录作为交换格式,以编程方式在工具之间传递列表,并且只保留人类格式向用户提供反馈的工具(此处为 的树状输出tree
)。
在非 GNU 系统上,但使用zsh
shell,您可以执行以下操作:
files=( ${(0)"(rg -g '*.txt' -l0 'my pattern to match')"} )
typeset -U unique_dirs=( $files:h )
(( $#unique_dirs )) && tree -- $dirs
或者一口气(假设至少有一个匹配的文件):
tree -- ${(u)${(0)"$(rg -g '*.txt' -l0 'my pattern to match')"}:h}
u
( nique u
) 取代了typeset -U
。参数扩展标志0
是我们如何告诉zsh
在 NUL 上进行分割的方式。或者,我们可以设置IFS=$'\0'
并依赖分词(在未加引号的参数扩展时完成):
IFS=$'\0'
tree -- ${(u)$(rg -g '*.txt' -l0 'my pattern to match'):h}
如果您既没有 GNU 实用程序也没有zsh
,您可以随时求助于perl
:
rg -g '*.txt' -l0 'my pattern to match' |
perl -MFile::Basename -MList::Util=uniq -0 -e '
@dirs = uniq(map {dirname$_} <>);
exec "tree", "--", @dirs if @dirs'
¹ 这是唯一不能出现在命令参数中的字符/字节值(因为参数在系统execve()
调用中作为 NUL 分隔的字符串传递),但它可以出现在通过管道馈送的字节流中,因此它是一个简单的以及在那里分离任意参数的明显方法。-0
是 GNU 实现的非标准扩展xargs
,但现在在许多其他实现中都可以找到它
² 或至少一次调用中可以容纳的数量,dirname
仅在需要时调用多次。
答案2
更新:新的最终答案:
请注意,sort -zu
对以 null 分隔的 ( -z
) 列表进行排序并删除重复项。
rg 'my pattern to match' -0 -g '*.txt' -l \
| sort -zu \
| xargs -0 -I{} -- dirname {} \
| xargs -0 -I{} -- tree {}
旧答案详细信息:
请参阅此答案下面的评论。我在这里的回答并不那么有力@Stéphane Chazelas 的另一个答案。
我下面的答案最初不能正确处理任何带有空格或其他空格的文件名,也不能处理以破折号(-
)开头的文件名。以下是我的回复评论:
@StéphaneChazelas,你所有的评论都有道理。你的回答更加有力。使用
--null
(-0
) withrg
和 withxargs
肯定会更加稳健。使用--
也会。我想我并不太关心这些事情,因为我在 git 存储库中运行此命令,其中没有一个文件包含空格,也没有以破折号 (-
) 开头。至于多个dirname
&tree
调用而不是具有多个路径的一次调用,我知道这一点,但也同意这一点,部分原因是我想要一个答案,我可以轻松扩展并添加更多管道和命令,而无需彻底更改它。
所以,看看这两个答案。他在技术上更好,但就我的目的而言,我的目前“足够好”,并指出我在问题中的原始示例可以通过超级最小的更改来工作。前任:
# I should have done this (add `-0` to `rg` and add `--` to `xargs`):
rg 'my pattern to match' -0 -g '*.txt' -l | xargs -0 -I {} -- dirname {}
# instead of this:
rg 'my pattern to match' -g '*.txt' -l | xargs -0 -I {} dirname {}
这@Stéphane Chazelas 的回答以及我的问题下的评论(其中包括 ripgrep 制作者本人制作的一个!)都很有用,并帮助我找出以下内容,我认为这是最简单和最好的答案,因为它是最简单的:
的输出路径字符串rg
不是以 null 结尾的字符串,因此-0
从xargs
命令中删除(或者,相反,rg
也将其添加到命令中)。就是这样!现在可以了:
# THESE WORK to get the dirnames!
# (`--null`/`-0` are removed from both `rg` and `xargs`)
rg 'my pattern to match' -g '*.txt' -l | xargs -I {} dirname {}
# OR (same thing--remove the space after `-I` is all):
rg 'my pattern to match' -g '*.txt' -l | xargs -I{} dirname {}
或者,您可以通过在命令中添加-0
或来强制路径字符串以 null 结尾,因此这也有效:--null
rg
# ALSO WORKS
# (`--null`/`-0` are ADDED to both `rg` and `xargs`; note that for
# both `rg` and `xargs`, `--null` is the long form of `-0`)
rg 'my pattern to match' -g '*.txt' -l --null | xargs --null -I{} dirname {}
tree
现在,通过扩展,我们可以像这样传递所有路径:
最终答案:
rg 'my pattern to match' -0 -g '*.txt' -l \
| xargs -0 -I{} -- dirname {} \
| xargs -0 -I{} -- tree {}
就是这样!我只需要要么添加或者减去 -0
或者--null
来自两个rg
和所有xargs
调用,以保持它们全部一致并在解析多个路径时期望相同的轮廓符。
添加 -0
但是, or--null
更好,因为这样它允许路径中包含空格或其他空白,并且添加--
也很好,因为这样它允许以破折号 ( -
) 开头的路径。所以,这就是我上面所做的。
不过,也请参阅其他答案。它还可以排序、删除重复项并处理其他复杂问题。
也可以看看
- 更多我的
xargs
学习和例子:
关键词:如何正确使用xargs;使用 xargs 解析 grep 或 ripgrep rg 输出路径