如何從 ripgrep 輸出的路徑列表中單獨處理每個路徑
我在 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
等等
然後我想在每個路徑上執行另一個命令,例如
tree $(dirname $PATH)
,以獲取包含匹配文件的目錄中所有文件的列表。我怎樣才能做到這一點?我覺得
xargs
可能是答案的一部分?但是像這樣開始的管道xargs
似乎只處理最後列印的文件:rg 'my pattern to match' -g '*.txt' -l | xargs -0 -I {} dirname {}
注意:如果您也可以展示,
grep
那麼對於沒有 的人也可能有用ripgrep
,儘管 ripgrep非常易於安裝。參考:
更新:新的最終答案:
請注意,
sort -zu
對空分隔 (-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 repo 中執行此命令,其中沒有一個文件中有空格,也沒有以破折號 (-
) 開頭。至於多個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 的製造者本人的評論!)都很有用,並幫助我弄清楚了以下內容,我認為這是最簡單和最好的答案,因為它是最簡單的:
from 的輸出路徑字元串
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
到命令來強制路徑字元串以空值結尾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 解析 grep 或 ripgrep rg 輸出路徑
在 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
anddir
,dir/subdir
因此您將看到dir/subdir
兩次的內容。您有正確的想法,使用
xargs
which 是將字節字元串轉換為要傳遞給命令的參數列表的命令,並使用-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
foru
nique) 是替換typeset -U
.0
參數擴展標誌是我們告訴zsh
在 NUL 上拆分的方式。或者,我們可以設置IFS=$'\0'
並依賴分詞(在不帶引號的參數擴展時完成):IFS=$'\0' tree -- ${(u)$(rg -g '*.txt' -l0 'my pattern to match'):h}
如果您既沒有 GNU 實用程序也沒有 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
僅在需要時呼叫多次。