Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading an argument list with a glob from a file

I am trying to read arguments of ll command from file. I have file args.txt with content some?. I have files with names some1, some2 and some3 and some other files. when I execute cat args.txt | ll I get the same result as when I execute ll.

Can someone explain me why is that and how can I achieve the desired result. Thanks in advance.

like image 473
DPM Avatar asked Jan 22 '26 09:01

DPM


2 Answers

Note: I'm assuming that ll is an alias for ls -l or some variation.
As Charles Duffy points out, (by default) only interactive shells know about aliases, and scripts as well as tools such as xargs are unaware of them.

What you send through a pipeline to a command is received via its stdin stream, which is distinct from a command's arguments (options such as -l and operands such as some?).

Therefore, an explicit transformation is needed to convert the contents of file args.txt to an argument you can pass to ls -l:

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

Note that the command substitution ($(...)) is deliberately unquoted so as to ensure that globbing (filename expansion) is applied to the contents of args.txt, as desired.

Yours is a rare case where this technique - which is brittle - is actually needed.

To illustrate the brittleness: If your globbing pattern were "some file"?, for instance (you'd need the double quotes for the pattern to still be recognized as a single argument), the command wouldn't work anymore, because the " characters lose their syntactic function when they're the result of a command substitution (or variable expansion).


The standard utility for transforming stdin or file content into arguments is xargs. However, in your particular case it is not an option, because your file contains a glob (filename pattern) that must be expanded by the shell, but xargs only invokes external utilities, without involving a shell:

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

Filename file? was passed literally to ls (and no file actually named file? exists) - no globbing happened, because no shell was involved.

like image 87
mklement0 Avatar answered Jan 25 '26 03:01

mklement0


The following is somewhat overkill, but goes to pains to be correct and consistent regardless of shell configuration, and to work with all possible filenames (even those with whitespace):

# 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\0' $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
)

...thereafter used as (if your input file is newline-delimited):

tr '\n' '\0' <args.txt | globfiles | xargs -0 ls -l --

...or (if your input file is NUL-delimited -- which is ideal, as this permits filenames containing literal newlines to be referenced):

globfiles <args.0sv | xargs -0 ls -l --
like image 43
Charles Duffy Avatar answered Jan 25 '26 02:01

Charles Duffy