首先要切断琐碎但不适用的答案:我既不能使用find
+xargs
技巧,也不能使用它的变体(如find
with -exec
),因为每次调用我需要使用很少的此类表达式。我会在最后再回到这个话题。
现在为了更好的例子,让我们考虑一下:
$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc
我如何将它们作为参数传递给program
?
仅仅这样做并不能解决问题
$ ./program $(find -L some/dir -name \*.abc | sort)
失败,因为program
得到以下参数:
[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc
可以看出,带有空格的路径被分割,并将program
其视为两个不同的参数。
报价直至生效
似乎像我这样的新手用户,在遇到此类问题时,往往会随机添加引号,直到它最终起作用 - 只是在这里它似乎没有帮助......
"$(…)"
$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc
由于引号可防止分词,因此所有文件都作为单个参数传递。
引用单独的路径
一个有前途的方法:
$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"
当然,引号就在那里。但它们不再被解释。它们只是弦的一部分。所以他们不仅没有阻止分词,还发生了争执!
改变IFS
然后我尝试玩弄IFS
.无论如何,我更喜欢find
with-print0
和sort
with -z
- 这样它们本身就不会在“有线路径”上出现问题。那么为什么不强制角色分词null
并拥有这一切呢?
$ ./program $(IFS=$'\0' find -L some/dir -name \*.abc -print0 | sort -z)
[0]: ./program
[1]: some/dir/1.abcsome/dir/2.abcsome/dir/a
[2]: space.abc
所以它仍然在空间上分裂,并且不在 上分裂null
。
我尝试将IFS
作业放在$(…)
(如上所示)和之前./program
。我还尝试了其他语法,例如\0
, \x0
,\x00
都用'
和"
以及带和不带$
.这些似乎都没有任何区别……
我现在没有主意了。我又尝试了一些事情,但似乎都遇到了所列出的相同问题。
我还能做什么?这是可行的吗?
当然,我可以让program
接受模式并自行进行搜索。但这需要大量的双重工作,同时将其固定为特定的语法。 (grep
例如,通过 a 提供文件怎么样?)。
我也可以让program
接受一个包含路径列表的文件。然后我可以轻松地将find
表达式转储到某个临时文件并仅提供该文件的路径。这可以支持沿着直接路径,以便如果用户只有一个简单的路径,则可以在没有中间文件的情况下提供它。但这看起来不太好——需要创建额外的文件并处理它们,更不用说需要额外的实现了。 (然而,从好的方面来说,对于作为参数的文件数量开始导致命令行长度问题的情况,这可能是一种救援......)
最后,让我再次提醒您,find
+ xargs
(以及类似的)技巧在我的情况下不起作用。为了描述简单起见,我仅显示一个参数。但我的真实案例看起来更像是这样的:
$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES
因此,从一个搜索中进行搜索xargs
仍然让我不知道如何处理另一个搜索......
答案1
使用数组。
如果您不需要处理文件名中换行符的可能性,那么您可以逃脱
mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)
然后
./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"
如果你做需要处理文件名中的换行符,并且 bash >= 4.4,您可以在数组构造期间使用-print0
和-d ''
来以 null 终止名称:
mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)
(对于 也类似XYZ_FILES
)。如果你不拥有较新的 bash,那么您可以使用空终止读取循环将文件名附加到数组,例如
ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
答案2
您可以使用 IFS=newline (假设没有文件名包含换行符),但必须在替换之前在外壳中设置它:
$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker
使用zsh
但不使用 null也bash
可以。$'\0'
即使在bash
你可以处理换行符,如果有一个足够奇怪的字符从未像这样使用过
IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...
但是,此方法无法处理您在 @steeldriver 的答案的评论中提出的附加请求,即如果 find a 为空,则省略 --afiles 。
答案3
我不确定我是否明白你为什么放弃xargs
。
因此,从一个搜索中进行搜索
xargs
仍然让我不知道如何处理另一个搜索......
该字符串--xyz-files
只是众多参数之一,在程序解释它之前没有理由认为它是特殊的。我认为你可以xargs
在两个find
结果中传递它:
{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files