An alias should effectively not (in general) do more than change the default options of a command. It is nothing more than simple text replacement on the command name. It can't do anything with arguments but pass them to the command it actually runs. So if you simply need to add an argument at the front of a single command, an alias will work. Common examples are
# Make ls output in color by default.
alias ls="ls --color=auto"
# make mv ask before overwriting a file by default
alias mv="mv -i"
A function should be used when you need to do something more complex than an alias but that wouldn't be of use on its own. For example, take this answer on a question I asked about changing grep
's default behavior depending on whether it's in a pipeline:
grep() {
if [[ -t 1 ]]; then
command grep -n "$@"
else
command grep "$@"
fi
}
It's a perfect example of a function because it is too complex for an alias (requiring different defaults based on a condition), but it's not something you'll need in a non-interactive script.
If you get too many functions or functions too big, put them into separate files in a hidden directory, and source them in your ~/.bashrc
:
if [ -d ~/.bash_functions ]; then
for file in ~/.bash_functions/*; do
. "$file"
done
fi
A script should stand on its own. It should have value as something that can be re-used, or used for more than one purpose.
An alias is internal to the shell where it is defined. It is not visible to other processes. The same goes for shell functions. xargs
is a separate application, which is not a shell, so doesn't have a concept of aliases or functions.
You can make xargs invoke a shell instead of invoking grep
directly. However just invoking a shell isn't enough, you have to define the alias in that shell as well. If the alias is defined in your .bashrc
, you can source that file; however this may not work your .bashrc
performs other tasks that don't make sense in a non-interactive shell.
find . -name '*.py' | xargs bash -c '. ~/.bashrc; grep -E regex_here "$@"' _
Beware of the intricacies of nested quoting when typing the regexp. You can simplify your life by passing the regexp as a parameter to the shell.
find . -name '*.py' | xargs bash -c '. ~/.bashrc; grep -E "$0" "$@"' regex_here
You can perform the alias lookup explicitly. Then xargs
will see grep -n --color=always
.
find . -name '*.py' | xargs "${BASH_ALIASES[grep]}" regex_here
In zsh:
find . -name '*.py' | xargs $aliases[grep] regex_here
By the way, note that find … | xargs …
breaks on filenames containing spaces (among others). You can fix this by changing to null-delimited records:
find . -name '*.py' -print0 | xargs -0 "${BASH_ALIASES[grep]}" regex_here
or by using -exec
:
find . -name '*.py' -exec "${BASH_ALIASES[grep]}" regex_here {} +
Instead of calling find
, you can do everything entirely inside the shell. The glob pattern **/
traverses directories recursively. In bash, you need to run shopt -s globstar
to enable this glob pattern first.
grep regex_here **/*.py
This has a few limitations:
- If a lot of files match (or if they have long paths), the command may fail because it exceeds the maximum command line length.
- In bash ≤4.2 (but not in more recent versions, nor in ksh or zsh),
**/
recurses into symbolic links to directories.
Another approach is to use process substitution, as suggested by MariusMatutiae.
grep regex_here <(find . -name '*.py')
This is useful when **/
isn't applicable: for complex find
expressions, or in bash ≤4.2 when you don't want to recurse under symbolic links. Note that this breaks on file names containing spaces; a workaround is to set IFS
and disable globbing, but it's starting to get a bit complex:
(IFS=$'\n'; set -f; grep regex_here <(find . -name '*.py') )
Best Answer
The command
xargs
is only able to run commands, not aliases. GNU parallel, however, is able to run functions:So I would recommend either:
Giving xargs the full path to the version of ls you want to use (or an unambiguous name, perhaps
gls
depending on how it was installed on your system) or, if your shell allows it,Defining
ls
as a function (function ls { gls "$@"; }; export -f ls
in bash) and using GNU parallel instead of xargs (parallel -j1
if you would like to use a single CPU).