仅仅这样做并不能解决问题

仅仅这样做并不能解决问题

首先要切断琐碎但不适用的答案:我既不能使用find+xargs技巧,也不能使用它的变体(如findwith -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.无论如何,我更喜欢findwith-print0sortwith -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

相关内容