17

I'm trying to make a function that basically takes in arguments like f hello there my friend and searches a directory using find for all occurences of any of those strings, so it would be find | grep 'hello\|there\|my\|friend'. I'm new to shell scripting, but my code is below:

function f { 
  cmd="find | grep '"
  for var in "$@"
  do 
    cmd="$cmd$var\\|"
  done
  cmd="${cmd%\\|}'"
  echo "$cmd"
  $cmd 
}

When I execute the command, I get this:

# f hello there my friend
find | grep 'hello\|there\|my\|friend'
find: `|': No such file or directory
find: `grep': No such file or directory
find: `\'hello\\|there\\|my\\|friend\'': No such file or directory

Why does it not work, and how can I make it work? I imagine it's something to do with the string not being converted to a command, but I don't know enough about how shell scripting works to figure it out.

4
  • 6
    mywiki.wooledge.org/BashFAQ/050 Commented Sep 25, 2015 at 17:11
  • Wait... you want to search these strings in the file name or in the content of the file? Commented Sep 25, 2015 at 18:36
  • @gniourf_gniourf the filenames. Searching the content is done with grep -r Commented Sep 25, 2015 at 18:37
  • Ok for the file name: so are you looking for the full path or just the name of the file? because your method will include all files in a given directory if the directory name matches the string. Commented Sep 25, 2015 at 18:39

5 Answers 5

11

It looks like your command syntax is correct. To run the command from a script in bash and capture the result, use this syntax:

cmd_string="ls"
result=$($cmd_string)
echo $result
Sign up to request clarification or add additional context in comments.

Comments

4

Instead of piping the output of find through grep, you might as well use the full capabilities of find. You'll want to build up a an array that contains the options:

-false -o -name '*string1*' ... -o -name '*stringn*'

to pass to find (where string1 ... stringn are the strings passed as arguments):

f() {
   local i args=( -false )
   for i; do
      args+=( -o -name "*$i*" )
   done
   find "${args[@]}"
}

We're using -false as an initializer, so that building up the array of options is simple; this also has the benefit (or flaw, depending on your point of view) that if no options are given then find exits early without listing all the content of the directory recursively.

With grep you could use regexes to have more powerful matching capabilities; here we're using find's -name option, so we can only use the basic globs: *, ? and [...]. If your find supports the -regex option (GNU find does), and if you really need regexes, then it's trivial to modify the previous function.


Another possibility is to use Bash's extended globs:

f() (
   IFS='|' eval 'glob="$*"'
   shopt -s globstar extglob nullglob
   IFS=
   printf '%s\n' **/*@($glob)*
)

A few things to note here:

  • The whole function is included in a subshell—it's not a typo. That's to simplify a few things: no need to use local variables, and no need to save the shell options to restore them at the end of the function.
  • The first line uses the evil eval but in a safe way: it's actually an idiomatic way to join the elements of the positional parameters with the first character of IFS (here a pipe character).
  • We need to set IFS to the empty string so as to avoid word splitting in the glob **/*@($glob)*.
  • The glob **/*@($glob)* uses globstar and the extglob @($glob) (with no quotes, it's not a typo). See Pattern Matching in the reference manual.

This function uses Bash's extended globs, that differ from (and aren't as powerful as) regexes (yet this should be enough for most cases).

Comments

4

I got here because it was the only google SO hit for when I wanted to build a commandline and my parameters included spaces.

The solution was to use a bash array:

#! /bin/bash
# using /bin/sh here might work if sh is broken or mapped to bash but this is not compatible with a real sh.
MY_ARGS=( "--HI" "--there" "--With spaces" )
if [ "X$OPTIONAL" != "X" ] ; then MY_ARGS+=( "$OPTIONAL" );fi
MY_ARGS+=( "$@" )

my_command "${MY_ARGS[@]}"

Adapted from: http://mywiki.wooledge.org/BashFAQ/050

Comments

3

Don't put the entire command in a string; just build the argument for grep

f () { 
  local grep_arg=''
  delim=''
  for var in "$@"; do
      grep_arg+="$delim$var"
      delim='\|'
  done
  echo "find | grep '$grep_arg'"
  find | grep "$grep_arg"
}

2 Comments

this is appending to grep_arg every time I run it (i.e. the value of grep_arg persists). why is that happening?
Sorry, variables are global by default in shell. I'll update
1

The most universal way: you can use symbols like "|" (pipe) and other variables into the input and get the result from stdout.

function runcmd(){
  eval "$1"
}

result=$(runcmd "Any command ${here}")

1 Comment

It could be intersting to add an example with the pipe you explain in the begining of tour answer?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.