Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I love using the following pattern for searching in files:

grep --color=auto -iRnHr --include={*.js,*.html,} --exclude-dir={release,dev,} "span" .

I'd like, however, to have this one wrapped into a bash comand like this:

findinfiles {*.js,*.html,} {release,dev,} "span"  // syntax is just a guessing

I cannot solve the problem of passing this kind of braces into a bash function and using them with $1, $2 and so on. When I use the following:

function findinfiles() {
    echo $1, $2, $3
}

Then:

me@pc ~> findinfiles {*.js,*.html,} {release,dev,} "span"
*.js, *.html, release

Ofc, passing arguments to grep won't work this way. It seems that arguments are indexed but not properly grouped.

Can anyone teach me how to deal with this kind of arguments?

share|improve this question

2 Answers 2

up vote 4 down vote accepted

When you run findinfiles {*.js,*.html,} {release,dev,} "span", here is what happens:

  1. Bash parses quotes and splits the command into words: findinfiles, {*.js,*.html,} {release,dev,} span.
  2. Bash expands the braces, resulting in the list of words findinfiles, *.js, *.html, release, dev, span.
  3. 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.
  4. 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:

    1. Replace the variable by its value.
    2. 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).
    3. 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.
share|improve this answer
    
Kudos for the... article! It covers everything ;-) –  oleq Dec 1 '12 at 11:04

bash expands {a,b,}1 to a1 b1 1..

Your command expands to grep --color=auto -iRnHr --include=*.js --include=*.html --include= --exclude-dir=release --exclude-dir=dev --exclude-dir= span (in a directory without any js and html files, otherwise it would be an include for every file)

You can quote the parameters, but that is probably not ideal... findinfiles "{*.js,*.html,}" "{release,dev,}" "span" (The globbing is still needed then)

You should then be able to properly expand it with an eval, like this version:

function findinfiles() {
    eval "grep --color=auto -iRnHr --include=$1 --exclude-dir=$2 '$3'"
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.