使用应用程序打开文件列表的过程替换

使用应用程序打开文件列表的过程替换

我希望使用进程替换将文件列表(例如,由ls或生成find)定向到特定应用程序以进行打开/查看。虽然通过管道传输这样的列表xargs适用于脚本或二进制文件,但如果对象xargs是 shell 别名,则此操作将失败,如中所述本网站的其他问题。 [我想到的具体应用是feh]

xargs鉴于bash 别名的这种限制,我转而尝试使用[script/binary/alias] <(find . -iname '*').如果文件列表定向到某些 shell 命令,例如cat(或者less,如果输入重定向,<则将 , 前置到进程替换语句),则此构造会产生所需的效果;但是,如果输入(文件路径列表)改为定向到应用程序(例如,fehgimp)来打开/查看,则它会明显失败。在feh作为进程替换语句的接收者的特定情况下,伴随此失败的错误是“ feh: No loadable images specified.”。在进程替换语句前面添加输入重定向运算符并不能缓解问题(与其他命令的情况不同,例如,less)。

因此,我的问题涉及进程替换是否可以用于所述目的(即打开文件列表),如果可以,适当的语法可能是什么。与 bash 别名(在~/.bash_aliases或类似的配置文件中指定)兼容的特定要求。

答案1

如果命令接受多个文件作为参数(而不是通过管道读取内容),则简单的命令替换应该有效:

[script/binary/alias] $(find .)

或者,如果该命令一次只接受一个文件,则 for 循环应该可以解决问题:

for file in $(find .); do [script/binary/alias] "$file"; done

在这两种情况下,包含空格、制表符、换行符、通配符的文件名都会导致问题; while/read 循环可以处理这个问题:

find . | while IFS= read -r file; do [script/binary/alias] "$file"; done

包含换行符的文件名仍然会导致问题;find的 0 分隔输出将解决这些问题(这里假设bash, zsh, ksh):

find . -print0 | while IFS= read -rd '' file; do [script/binary/alias] "$file"; done

在所有这些示例中,find . -type f如果您只想处理常规文件,则可能更合适。

答案2

如果你想在之后扩展别名xargs,你可以这样做:

alias xargs='xargs '

但请注意,只有后面的第一个单词xargs才会受到别名扩展的影响,如果您有标准命令的别名,则可能会产生意想不到的效果。

$ alias xargs='xargs ' a='echo test'
$ set -x
$ echo x | xargs a
+ echo x
+ xargs echo test
test x
$ echo x | xargs -r a
+ echo x
+ xargs -r a
xargs: a: No such file or directory

您可能想将您的别名命名为其他名称,并使用更合理的默认行为xargs。以 GNU 为例xargs

alias axargs='xargs -r -d "\n" '

<(...)现在,进程替换扩展到一个参数:包含命令输出(作为实时流)的文件的名称。

对于 的输出find,这仅对于接受预期包含换行符分隔的文件列表的文件名作为参数的命令有用。

feh是其中之一:

feh -f <(find . -mtime -1 -type f -name '*.jpg')

仅当文件名不包含换行符时才有效。

GNU 实现xargs有一个-a选项,可以从文件中获取参数列表,并与 (或 ) 选项相结合,-0-print0允许-exec printf '%s\0' {} +可靠find地传递文件名列表:

xargs -r0a <(find ... -print0) feh....

但这对解决您的别名问题没有帮助。

现在,如果您想将命令的输出作为参数传递给另一个命令,那么您可以使用命令替代而不是过程代换。但请注意一些注意事项。

cmdA "$(cmdB)"

传递 , 的输出cmdB,而不将尾随换行符作为 的一个参数cmdA。例如,如果您有三个常规文件(此处具有不寻常但完全有效的名称):* * /etc/passwd .jpgfoo.txt<nl><nl>(其中<nl>表示换行符),find . -type f( cmdB) 的输出将为./* * /etc/passwd .jpg<nl>./foo.txt<nl>./<nl><nl><nl>,因此cmdA将收到一个参数./* * /etc/passwd .jpg<nl>./foo.txt<nl>./

这可能不是你想要的。您希望cmdA接收每个文件名作为单独的参数:./* * /etc/passwd./foo.txt./<nl><nl>

因此,基本上,您需要将输出拆分find为单独的文件名。 POSIX shell 有一个操作符,即分割+全局当您离开时隐式调用的运算符命令替换(或参数扩展或算术扩展)不加引号。

cmdA $(cmdB)

cmdB将在字符(默认为空格、制表符和换行符)上拆分(不带尾随换行符)的输出$IFS,然后每个单词都将进行通配。

在上面的例子中,这不是我们想要的。这将分为./* * /etc/passwd .jpg./**/etc/passwd并且.jpg./**通配到当前目录中的文件列表中。

我们想要的是分割换行符而不是进行通配部分。这是通过以下方式完成的:

IFS='
' # newline only
set -f
cmdA $(cmdB)

这对于包含换行符的文件名仍然不起作用。的输出find通常不可进行后处理,除非您使用-print0或可以保证不会有带有换行符的文件名。

现在,只zsh允许分割\0字符:

cmdA ${(0)"$(cmdB)"}

"$(cmdB)"根据 NUL 字符序列进行拆分。或者:

IFS=$'\0'  # no need for set -f in zsh
cmdA $(cmdB)

命令替换方法的另一个问题是,如果cmdB失败和/或不输出任何内容,cmdA仍然会运行(不带参数)。可以使用以下语法来避免它(仍然使用zsh):

cmdA ${$(find...):?no file}

但如果您使用zsh,通常不需要遇到所有麻烦,因为内部zsh支持大部分find功能。例如,您可以使用:

myalias ./**/*.jpg(.m-1)

显示jpg最近 24 小时内最后修改的文件。

相关内容