从文件中读取带有 glob 的参数列表

Reading an argument list with a glob from a file

我正在尝试从文件中读取 ll 命令的参数。 我有文件 args.txt,内容 some?。我有名称为 some1some2some3 的文件以及一些其他文件。当我执行 cat args.txt | ll 时,我得到的结果与我执行 ll.

时的结果相同

谁能解释一下为什么会这样,我怎样才能达到预期的结果。 提前致谢。

您可以使用 xargs 将 cat 的输出传递给命令(在您的情况下为 ll):

cat args.txt | xargs ll

如果您的 args.txt 包含多个值,用换行符或 space 分隔,xargs 将分别传递每个值,执行命令的次数与 args.txt:

例如,如果您的 args.txt 具有以下内容:

/var/
/usr/
/usr /var

那么执行的结果会是这样的:

$ cat args.txt | xargs ls
/usr:
bin  etc  games  include  lib  lib64  libexec  local  sbin  share  src  tmp

/usr/:
bin  etc  games  include  lib  lib64  libexec  local  sbin  share  src  tmp

/var:
adm  cache  crash  cvs  db  empty  games  gopher  kerberos  lib  local  lock  log  mail  nis  opt  preserve  run  spool  tmp  var  yp

/var/:
adm  cache  crash  cvs  db  empty  games  gopher  kerberos  lib  local  lock  log  mail  nis  opt  preserve  run  spool  tmp  var  yp

注意:我假设 ll 别名 ls -l 或一些变体。
正如 Charles Duffy 指出的那样,(默认情况下)只有 interactive shells 知道别名,而脚本以及 xargs 等工具不知道它们。

通过管道发送到命令的内容是通过其 stdin 流接收的,这与命令的 arguments(选项例如 -l 和操作数例如 some?).

因此,需要显式转换才能将文件 args.txt 的内容转换为 参数 您可以传递给ls -l:

ls -l $(cat args.txt)   # In Bash you can simplify to: ls -l $(< args.txt)

请注意,命令替换 ($(...)) 是故意 未加引号 以确保 globbing(文件名扩展)根据需要应用于 args.txt 的内容。

你的情况很少见,这种技术 脆弱 - 实际上 需要

为了说明脆弱性:例如,如果您的 globbing 模式是 "some file"?(您需要模式的双引号仍被识别为单个参数),该命令将不起作用不再是了,因为 " 字符在它们是命令替换(或变量扩展)的结果时失去了它们的语法功能。


将 stdin 或文件内容转换为参数的 标准实用程序是 xargs。但是,在您的特定情况下,它不是一个选项,因为您的文件包含一个glob(文件名模式)必须由 shell 扩展,但 xargs 仅调用外部实用程序,不涉及 shell:

$ xargs ls -l < args.txt # !! Does NOT work as intended here.
ls: file?: No such file or directory

文件名 file? 按字面意思 传递给 ls(实际上不存在名为 file? 的文件)- 没有发生通配,因为没有shell 参与其中。

以下内容有点矫枉过正,但无论 shell 配置如何,都竭尽全力保持正确和一致,并处理所有可能的文件名(即使是带有空格的文件名):

# note f() ( ) instead of f() { }; this runs in a subshell, so its changes to IFS or
# shopt settings don't modify behavior of the larger shell.
globfiles() (
  set +f                      ## ensure that globbing is enabled
  shopt -u nullglob failglob  ## ensure that non-default options on how to handle failed
                              ## ...glob attempts are both disabled
  IFS=                        ## disable string-splitting
  while IFS= read -r -d '' filename; do
    printf '%s[=10=]' $filename   ## unquoted use -> expand as glob
                              ## ...expands format string once per argument, hence once per
                              ## ...file found. (No string-splitting due to prior IFS=)
  done
)

...此后用作(如果您的输入文件以换行符分隔):

tr '\n' '[=11=]' <args.txt | globfiles | xargs -0 ls -l --

...或者(如果您的输入文件是 NUL 分隔的——这是理想的,因为这允许引用包含文字换行符的文件名):

globfiles <args.0sv | xargs -0 ls -l --