When you run findinfiles {*.js,*.html,} {release,dev,} "span"
, here is what happens:
- Bash parses quotes and splits the command into words:
findinfiles
, {*.js,*.html,}
{release,dev,}
span
.
- Bash expands the braces, resulting in the list of words
findinfiles
, *.js
, *.html
, release
, dev
, span
.
- Bash expands the wildcard patterns
*.js
and *.html
to the list of matching file names. If no file name matches either pattern, it is left alone.
- Bash looks up the name
findinfiles
, finds out that it's a function, and executes the function with the supplied parameters.
You can prevent the braces and wildcards from being expanded at the function call, but then the braces will appear literally in the arguments, which isn't particularly useful.
I suggest changing the format of your function call. Instead of using braces, use only commas, and manually split the arguments at commas inside the function.
findinfiles () {
local patterns="$1" excludes="$2" pattern="$3"
shift 3
typeset -a cmd dirs
if [[ $# -eq 0 ]]; then dirs=(.); else dirs=("$@"); fi
cmd=(grep --color=auto -iRnHr)
local IFS=,
for x in $patterns; do
cmd+=("--include=$x")
done
for x in $excludes; do
cmd+=("--exclude-dir=$x")
done
"${cmd[@]}" "${dirs}"
}
Explanations:
- Store the first three parameters in local variables. Any extra parameters are directories to search in.
- Set
dirs
to the list of extra parameters. If there are none, use .
(current directory) instead.
Set IFS
to a comma. When a script contains an unquoted variable expansion like $patterns
and $excludes
above, the shell performs the following:
- Replace the variable by its value.
- Split the variable into separate words wherever it contains a character that is present in
IFS
. By default, IFS
contains whitespace characters (space, tab and newline).
- Treat each word as a wildcard pattern and expand it to the list of matching files. If there is no matching file, leave the pattern alone.
(To avoid these expansion steps, use double quotes around the variable substitution, as in patterns="$1"
and so on in the script above.)
The function builds the command line to execute incrementally in the array variable cmd
. At the end, the command is executed.
Alternatively, you could build on the following settings:
shopt -s extglob globstar
fif_excludes='release dev'
alias fif='grep --color=auto -inH $fif_excludes'
Run commands like
fif span **/*.@(js|html)
Explanations:
shopt -s extglob
activates the @(js|html)
form of wildcard pattern, which matches either js
or html
. (This option activates other pattern forms, see the manual for details.)
shopt -s globstar
actives the pattern **/
which matches subdirectories at any depth (i.e. it performs a recursive traversal).
- To change the exclude list (which I expect doesn't happen often), modify the
fif_excludes
variable.
Hmm. @don_crissti already gave the answer for grep in a comment. But since you said it wasn't really about grep
or ls
, I'm going to rewrite the command in question to not use those commands.
What I think you want is:
do-something-with $(produce-list-of-files)
where the output of one command should be dropped in as command line parameters to another command. There just happens to be a utility just for that, it is called xargs
(man page).
If the file names are "nice", we could do just
produce-list-of-files | xargs do-something-with
If the file names can contain spaces but are separated by newlines, we have to tell xargs
to not split on any whitespace, but only newlines:
produce-list-of-files | xargs -d '\n' do-something-with
If the file names can contain newlines too, the list has to be separated by NULs ('\0', byte with value zero), and we need an xargs
that supports it. At least some versions of various utilities support listing files separated by NULs instead of newlines, there's at least find -print0
, sort -z
and grep -Z
in the GNU versions of those tools. In xargs
the flag is --null
or -0
. So:
produce-list-of-files -0 | xargs -0 do-something-with
An example run with cat
and, well, ls -l
:
$ touch "abba acdc" "foo bar" $'new\nline'
$ echo -en "abba acdc\0foo bar\0new\nline\0" > list
$ cat list | xargs -0 ls -l
-rw-r--r-- 1 itvirta itvirta 0 Aug 2 01:23 abba acdc
-rw-r--r-- 1 itvirta itvirta 0 Aug 2 01:23 foo bar
-rw-r--r-- 1 itvirta itvirta 0 Aug 2 01:23 new?line
Best Answer
These curly braces are left alone by bash; they belong to
find
andxargs
, respectively, and are described in their man-pages.man find
man xargs
Edit: and here WHY bash ignores those curly braces:
man bash
For emphasis: list must be terminated with a newline or semicolon.