If you want aliases to be expanded after xargs
, you can do:
alias xargs='xargs '
However note that only the first word after xargs
is subject to alias expansion and that can have unexpected effects if you have aliases for standard commands.
$ 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
You may want to call your alias something else and use a more sensible default behaviour for xargs
. For instance with GNU xargs
:
alias axargs='xargs -r -d "\n" '
Now, process substitution expands to one argument: the name of a file which for <(...)
contains the output of the command (as a live stream).
With the output of find
, that's only useful for commands that accept as argument a filename expected to contain a newline delimited list of files.
feh
is one of them:
feh -f <(find . -mtime -1 -type f -name '*.jpg')
That only works if filenames don't contain newline characters.
The GNU implementation of xargs
has a -a
option to take the list of arguments from a file and combined with -0
and the -print0
(or -exec printf '%s\0' {} +
) option to find
allows to pass a list of file names reliably:
xargs -r0a <(find ... -print0) feh....
That's not helping for your alias problem though.
Now, if you want to pass the output of a command as argument(s) to another command, that's where you'd use command substitution as opposed to process substitution. Beware of a few caveats though.
cmdA "$(cmdB)"
passes the output of cmdB
, without the trailing newline characters as one argument to cmdA
. For instance, if you have three regular files (here with unusual though perfectly valid names): * * /etc/passwd .jpg
, foo.txt
and <nl><nl>
(where <nl>
means a newline character), the output of find . -type f
(cmdB
) will be ./* * /etc/passwd .jpg<nl>./foo.txt<nl>./<nl><nl><nl>
, so cmdA
will receive one argument ./* * /etc/passwd .jpg<nl>./foo.txt<nl>./
.
That's probably not what you want. You'd want cmdA
to receive each file name as separate arguments: ./* * /etc/passwd
, ./foo.txt
and ./<nl><nl>
.
So, basically, you'd need to split the output of find
into the individual filenames. POSIX shells have an operator for that, the split+glob operator which is implicitly invoked when you leave a command substitution (or parameter expansion or arithmetic expansion) unquoted.
cmdA $(cmdB)
will split the output of cmdB
(without the trailing newline characters) on $IFS
characters (by default space, tab and newline), and then each word will be subject to globbing.
In our example above, that's not what we want. That would split ./* * /etc/passwd .jpg
into ./*
, *
, /etc/passwd
and .jpg
and those ./*
and *
would be globbed into the list of files in the current directory.
What we want is to split on newline characters and not do the globbing part. That's done with:
IFS='
' # newline only
set -f
cmdA $(cmdB)
That still doesn't work for filenames containing newline characters. The output of find
is generally not post-processable unless you use -print0
or you can guarantee there will not be file names with newline characters.
Now, only zsh
allows splitting on \0
characters:
cmdA ${(0)"$(cmdB)"}
would split "$(cmdB)"
on sequences of NUL characters. Or:
IFS=$'\0' # no need for set -f in zsh
cmdA $(cmdB)
Another problem with command substitution approaches is that if cmdB
fails and/or doesn't output anything, cmdA
will still be run (without argument). It can be avoided with this syntax (still with zsh
):
cmdA ${$(find...):?no file}
But if you're using zsh
, you generally don't need to go down all that trouble anyway since zsh
supports most of find
's functionality internally. For instance, you'd use:
myalias ./**/*.jpg(.m-1)
To display the jpg
files last modified within the last 24 hours.
$IFS
and wildcard characters. Thatwhile read
loop still have some problems with spaces (trailing ones) and now also with backslash characters. – Stéphane Chazelas Jan 30 '15 at 10:42